Name | Type | is_array | initial_value |
MAP_NAME = "Invictus"
MAX_PLAYERS = 27
--// This is NOT pairs, and trying to use it as such may cause issues down the line. Who cares!
do
oldPairs = pairs
local _k = {}
local i
local t
local function iter()
i = i + 1
if _k[i] then
local val = _k[i]
_k[i] = nil
return val, t[val]
end
end
function pairs(tab, s)
t = tab
for k in oldPairs(tab) do
_k[#_k+1] = k
end
table.sort(_k, s)
i = 0
return iter
end
end
function GetTableSize(tbl)
local i = 0
for _ in oldPairs(tbl) do
i = i + 1
end
return i
end
START_G = GetTableSize(_G)
do
local prefixes = {"war3map.lua", "blizzard.j.lua"}
local n = #prefixes
local prefixesLen = {}
for i = 1, n do
prefixesLen[i] = string.len(prefixes[i])
end
local list = {}
local lastMsg = nil
local function getPos(msg, pos)
error(msg, pos)
end
local function store(msg)
lastMsg = msg
end
local function checkPrefixes()
for i = 1, n do
if string.sub(lastMsg, 1, prefixesLen[i]) == prefixes[i] then
return true
end
end
return false
end
---Returns stack trace, but only the position of the called functions, not its names
---@return string
function GetStackTrace()
local stack = ""
local i = 4
local p = 1
while true do
xpcall(getPos, store, "- " .. p, i)
if not checkPrefixes() then break end
table.insert(list, lastMsg)
i = i + 1
p = p + 1
end
for j = #list, 1, -1 do
stack = stack .. list[j] .. "\n"
end
list = {}
return stack
end
end
do
Noise = {}
Noise.permutation = {}
local function floor(value)
local n = math.floor(value)
if value < 0. and value - n ~= 0. then n = n - 1 end
return n
end
local function fade(t)
return t^3 * (t * (t * 6. - 15.) + 10.)
end
function lerp(t, a, b)
return a + t * (b - a)
end
local function grad1D(hash, x)
local h = hash & 15
return (h & 1 == 0 and x or -x)
end
function Noise.perlin1D (x)
local X = floor(x) & 255
x = x - floor(x)
return lerp(fade(x), grad1D(Noise.permutation[X], x), grad1D(Noise.permutation[X + 1], x - 1)) * 2
end
local function grad2D(hash, x, y)
local h = hash & 15
local u, v = h < 8 and x or y, h < 4 and y or x
return (h & 1 == 0 and u or -u) + (h & 2 == 0 and v or -v)
end
function Noise.perlin2D (x, y)
local X, Y = floor(x) & 255, floor(y) & 255
x, y = x - floor(x), y - floor(y)
local u, v = fade(x), fade(y)
local A = Noise.permutation[X] + Y
local B = Noise.permutation[X + 1] + Y
local a1 = lerp(u, grad2D(Noise.permutation[A], x, y), grad2D(Noise.permutation[B], x - 1, y))
local a2 = lerp(u, grad2D(Noise.permutation[A + 1], x, y - 1), grad2D(Noise.permutation[B + 1], x - 1, y - 1))
return lerp(v, a1, a2)
end
local function grad3D(hash, x, y, z)
local h = hash & 15
local u, v = h < 8 and x or y, h < 4 and y or ((h == 12 or h == 14) and x or z)
return (h & 1 == 0 and u or -u) + (h & 2 == 0 and v or -v)
end
function Noise.perlin3D (x, y, z)
local X, Y, Z = floor(x) & 255, floor(y) & 255, floor(z) & 255
x, y, z = x - floor(x), y - floor(y), z - floor(z)
local u, v, w = fade(x), fade(y), fade(z)
local A = Noise.permutation[X] + Y
local AA = Noise.permutation[A] + Z
local AB = Noise.permutation[A + 1] + Z
local B = Noise.permutation[X + 1] + Y
local BA = Noise.permutation[B] + Z
local BB = Noise.permutation[B + 1] + Z
local a1 = lerp(u, grad3D(Noise.permutation[AA], x, y, z), grad3D(Noise.permutation[BA], x - 1, y, z))
local a2 = lerp(u, grad3D(Noise.permutation[AB], x, y - 1, z), grad3D(Noise.permutation[BB], x - 1, y - 1, z))
local b1 = lerp(u, grad3D(Noise.permutation[AA + 1], x, y, z - 1), grad3D(Noise.permutation[BA + 1], x - 1, y, z - 1))
local b2 = lerp(u, grad3D(Noise.permutation[AB + 1], x, y - 1, z - 1), grad3D(Noise.permutation[BB + 1], x - 1, y - 1, z - 1))
return lerp(w, lerp(v, a1, a2), lerp(v, b1, b2))
end
function Noise.generatePermutationTable(getRandomIntInterface)
for i = 0, 255 do
Noise.permutation[i] = type(getRandomIntInterface) == "function" and getRandomIntInterface(0, 255) or GetRandomInt(0, 255)
Noise.permutation[i + 256] = Noise.permutation[i]
end
end
Noise.STRETCH_CONSTANT_2D = -0.211324865405187
Noise.SQUISH_CONSTANT_2D = 0.366025403784439
Noise.NORM_CONSTANT_2D = 47
Noise.PMASK = 255
Noise.SQUISH_CONSTANT_2D_X, Noise.SQUISH_CONSTANT_2D_Y = 2. * Noise.SQUISH_CONSTANT_2D, 2. * Noise.SQUISH_CONSTANT_2D
Noise.gradTable2D =
{
[0] =
{ 5, 2}, { 2, 5},
{-2, 5}, {-5, 2},
{-5, -2}, {-2, -5},
{ 2, -5}, { 5, -2}
}
local function extrapolate2D(xsb, ysb, dx, dy)
local index = Noise.permutation[Noise.permutation[xsb & Noise.PMASK] ~ (ysb & Noise.PMASK)] & 7
return Noise.gradTable2D[index][1] * dx + Noise.gradTable2D[index][2] * dy
end
function Noise.openSimplex2D(x, y)
local strechOffset = (x + y) * Noise.STRETCH_CONSTANT_2D
local xs = x + strechOffset
local ys = y + strechOffset
local xsb = floor(xs)
local ysb = floor(ys)
local squishOffset = (xsb + ysb) * Noise.SQUISH_CONSTANT_2D
local xb = xsb + squishOffset
local yb = ysb + squishOffset
local xins = xs - xsb
local yins = ys - ysb
local inSum = xins + yins
local dx0 = x - xb
local dy0 = y - yb
local dx_ext
local dy_ext
local xsv_ext
local ysv_ext
local value = 0.
local dx1 = dx0 - 1. - Noise.SQUISH_CONSTANT_2D
local dy1 = dy0 - Noise.SQUISH_CONSTANT_2D
local attn1 = 2. - dx1 * dx1 - dy1 * dy1
local dx2 = dx0 - Noise.SQUISH_CONSTANT_2D
local dy2 = dy0 - 1. - Noise.SQUISH_CONSTANT_2D
local attn2 = 2. - dx2 * dx2 - dy2 * dy2
local ins
local attn0
local attn_ext
if attn1 > 0. then
attn1 = attn1 * attn1
value = attn1 * attn1 * extrapolate2D(xsb + 1, ysb, dx1, dy1)
end
if attn2 > 0. then
attn2 = attn2 * attn2
value = value + attn2 * attn2 * extrapolate2D(xsb, ysb + 1, dx2, dy2)
end
if inSum <= 1 then
zins = 1. - inSum
if zins > xins or zins > yins then
if xins > yins then
xsv_ext = xsb + 1
ysv_ext = ysb - 1
dx_ext = dx0 - 1.
dy_ext = dy0 + 1.
else
xsv_ext = xsb - 1
ysv_ext = ysb + 1
dx_ext = dx0 + 1.
dy_ext = dy0 - 1.
end
else
xsv_ext = xsb + 1
ysv_ext = ysb + 1
dx_ext = dx0 - 1. - Noise.SQUISH_CONSTANT_2D_X
dy_ext = dy0 - 1. - Noise.SQUISH_CONSTANT_2D_Y
end
else
zins = 2. - inSum
if zins < xins or zins < yins then
if xins > yins then
xsv_ext = xsb + 2
ysv_ext = ysb
dx_ext = dx0 - 2. - Noise.SQUISH_CONSTANT_2D_X
dy_ext = dy0 - Noise.SQUISH_CONSTANT_2D_Y
else
xsv_ext = xsb
ysv_ext = ysb + 2
dx_ext = dx0 - Noise.SQUISH_CONSTANT_2D_X
dy_ext = dy0 - 2. - Noise.SQUISH_CONSTANT_2D_Y
end
else
dx_ext = dx0
dy_ext = dy0
xsv_ext = xsb
ysv_ext = ysb
end
xsb = xsb + 1
ysb = ysb + 1
dx0 = dx0 - 1. - Noise.SQUISH_CONSTANT_2D_X
dy0 = dy0 - 1. - Noise.SQUISH_CONSTANT_2D_Y
end
attn0 = 2. - dx0 * dx0 - dy0 * dy0
if attn0 > 0. then
attn0 = attn0 * attn0
value = value + attn0 * attn0 * extrapolate2D(xsb, ysb, dx0, dy0)
end
attn_ext = 2. - dx_ext * dx_ext - dy_ext * dy_ext
if attn_ext > 0. then
attn_ext = attn_ext * attn_ext
value = value + attn_ext * attn_ext * extrapolate2D(xsv_ext, ysv_ext, dx_ext, dy_ext)
end
return value / Noise.NORM_CONSTANT_2D
end
Noise.generatePermutationTable()
end
do
function Noise.octavePerlin1D(x, octaves, persistence)
local total, frequency, amplitude, maxValue = 0., 1., 1., 0.
for i = 0, octaves - 1 do
total = Noise.perlin1D(x * frequency) * amplitude
maxValue = maxValue + amplitude
amplitude = amplitude * persistence
frequency = frequency * 2
end
return total / maxValue
end
function Noise.octavePerlin2D(x, y, octaves, persistence)
local total, frequency, amplitude, maxValue = 0., 1., 1., 0.
for i = 0, octaves - 1 do
total = Noise.perlin2D(x * frequency, y * frequency) * amplitude
maxValue = maxValue + amplitude
amplitude = amplitude * persistence
frequency = frequency * 2
end
return total / maxValue
end
function Noise.octavePerlin3D(x, y, z, octaves, persistence)
local total, frequency, amplitude, maxValue = 0., 1., 1., 0.
for i = 0, octaves - 1 do
total = Noise.perlin3D(x * frequency, y * frequency, z * frequency) * amplitude
maxValue = maxValue + amplitude
amplitude = amplitude * persistence
frequency = frequency * 2
end
return total / maxValue
end
end
do
local map = {
I = 1,
V = 5,
X = 10,
L = 50,
C = 100,
D = 500,
M = 1000,
}
local numbers = { 1, 5, 10, 50, 100, 500, 1000 }
local chars = { "I", "V", "X", "L", "C", "D", "M" }
local RomanNumerals = { }
function ToRomanNumerals(s)
s = math.floor(tonumber(s))
if s <= 0 then return s end
local ret = ""
for i = #numbers, 1, -1 do
local num = numbers[i]
while s - num >= 0 and s > 0 do
ret = ret .. chars[i]
s = s - num
end
for j = 1, i - 1 do
local n2 = numbers[j]
if s - (num - n2) >= 0 and s < num and s > 0 and num - n2 ~= n2 then
ret = ret .. chars[j] .. chars[i]
s = s - (num - n2)
break
end
end
end
return ret
end
function RomanNumerals.ToNumber(s)
s = s:upper()
local ret = 0
local i = 1
while i <= s:len() do
local c = s:sub(i, i)
if c ~= " " then
local m = map[c]
local next = s:sub(i + 1, i + 1)
local nextm = map[next]
if next and nextm then
if nextm > m then
ret = ret + (nextm - m)
i = i + 1
else
ret = ret + m
end
else
ret = ret + m
end
end
i = i + 1
end
return ret
end
end
do
WorldBounds = {}
function InitWorldBounds()
WorldBounds.r = GetWorldBounds()
WorldBounds.maxX = GetRectMaxX(WorldBounds.r)
WorldBounds.maxY = GetRectMaxY(WorldBounds.r)
WorldBounds.minX = GetRectMinX(WorldBounds.r)
WorldBounds.minY = GetRectMinY(WorldBounds.r)
WorldBounds.reg = CreateRegion()
WorldBounds.length = WorldBounds.maxX - WorldBounds.minX
WorldBounds.width = WorldBounds.maxY - WorldBounds.minY
RegionAddRect(WorldBounds.reg,WorldBounds.r)
end
function DuplicateRect(r)
return Rect(GetRectMinX(r),GetRectMinY(r),GetRectMaxX(r),GetRectMaxY(r))
end
end
do
local DefendAbil = FourCC("A002")
local indexCur = 0
local indexNext = {}
UnitData = {}
Index2Unit = {}
IndexerActions ={ add = {}, remove = {} }
function InitUnitIndexer()
local FilterEnter = Filter(function()
local u = GetFilterUnit()
UnitAddAbility(u,DefendAbil)
UnitMakeAbilityPermanent(u, true, DefendAbil)
local index
if #indexNext == 0 then
indexCur = indexCur + 1
index = indexCur
else
index = indexNext[#indexNext]
indexNext[#indexNext] = nil
end
SetUnitUserData(u,index)
Index2Unit[index] = u
UnitData[index] = NewKeyTable()
for i = 1, #IndexerActions.add do
IndexerActions.add[i](u,index)
end
return false
end)
local g = NewGroup()
TriggerRegisterEnterRegion(CreateTrigger(),WorldBounds.reg,FilterEnter)
local t = CreateTrigger()
TriggerAddAction(t, function()
local u = GetTriggerUnit()
local i = GetUnitUserData(u)
if GetIssuedOrderId() == 852056 and GetUnitAbilityLevel(u,DefendAbil) == 0 and UnitData[i] then
indexNext[#indexNext+1] = i
for j = 1, #IndexerActions.remove do
IndexerActions.remove[j](u,i)
end
ReleaseKeyTable(UnitData[i])
UnitData[i] = nil
end
end)
for i = 1, #PlayerData do
local p = PlayerData[i].p
TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_ISSUED_ORDER, nil)
GroupEnumUnitsOfPlayer(g, p, FilterEnter)
SetPlayerAbilityAvailable(p, DefendAbil, false)
end
ReleaseGroup(g)
end
end
do
local pi2 = 2*math.pi
local c4 = pi2 / 3
local n1 = 7.5625
local d1 = 2.75
local c1 = 1.70158
local c2 = c1 * 1.525
function easeInElastic(x)
if x == 0 then
return 0
elseif x == 1 then
return 1
else
return -(2^(10*x-10))* math.sin((x * 10 - 10.75) * c4)
end
end
function SineWave(amp,freq,time,phase)
return amp * math.sin(pi2*freq*time+phase)
end
function easeOutCubic(x)
return 1 - (1-x)^3
end
function easeInExpo(x)
return x == 0 and 0 or 2^(10 * x - 10)
end
function easeOutQuint(x)
return 1 - (1-x)^5
end
function easeOutQuad(x)
return 1 - (1 - x) * (1 - x)
end
function easeInOutBack(x)
if x < .5 then
return ((2 * x)^2 * ((c2 + 1) * 2 * x - c2)) / 2
else
return ((2 * x - 2)^ 2 * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2
end
end
function easeOutBounce(x)
if (x < 1 / d1) then
return n1 * x * x
elseif (x < 2 / d1) then
x = x - 1.5 / d1
return n1 * x * x + 0.75
elseif (x < 2.5 / d1) then
x = x - 2.25 / d1
return n1 * x * x + 0.9375
else
x = x - 2.625 / d1
return n1 * x * x + 0.984375
end
end
function easeInOutBounce(x)
return x < 0.5 and (1 - easeOutBounce(1 - 2 * x)) / 2 or (1 + easeOutBounce(2 * x - 1)) / 2
end
function easeInSine(x)
return 1 - math.cos((x * math.pi) / 2)
end
function quadBezier(t, p0, p1, p2)
return (1 - t)^2 * p0 + 2 * (1 - t) * t * p1 + t^2 * p2
end
function SetUnitAttackRange(u, desired_range, weapon_index)
local current_range = BlzGetUnitWeaponRealField(u, UNIT_WEAPON_RF_ATTACK_RANGE, weapon_index) --// index is correct, returned range is correct.
local current_range_second = BlzGetUnitWeaponRealField(u, UNIT_WEAPON_RF_ATTACK_RANGE, weapon_index + 1) --// yes, we should get the 2nd attack range and count it too
BlzSetUnitWeaponRealField(u, UNIT_WEAPON_RF_ATTACK_RANGE, weapon_index + 1, desired_range - current_range + current_range_second)
end
function CreateWarningEffect(x,y,radius)
local sfx = AddSpecialEffect("Widgets\\WarningIndicator",x,y)
BlzSetSpecialEffectScale(sfx,radius/80)
BlzSetSpecialEffectColor(sfx,255,0,0)
return sfx
end
function DestroyWarningEffect(sfx)
BlzSetSpecialEffectZ(sfx,-99999)
DestroyEffect(sfx)
end
local Sound_Error = nil
function ErrorMessage(error,p)
Sound_Error = Sound_Error or CreateSoundFromLabel("InterfaceError",false,false,false,10,10)
if LocalPlayer == p then
ClearTextMessages()
StartSound(Sound_Error)
end
DisplayTimedTextToPlayer(p,0.52,0.96,2,"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n|cffffcc00"..error.."|r")
end
function math.round(x)
return x>=0 and math.floor(x+0.5) or math.ceil(x-0.5)
end
function math.clamp(x,lower,upper)
if x < lower then
return lower
elseif x > upper then
return upper
else
return x
end
end
function MinMax(n,min,max)
if n < min then
return min
elseif n > max then
return max
else
return n
end
end
function PolarProjection(sourceX,sourceY, dist, angle)
angle = angle * bj_DEGTORAD
return sourceX + dist * math.cos(angle), sourceY + dist * math.sin(angle)
end
local dx, dy, dz
function IsInRange(x1,y1,x2,y2,range)
dx = x1-x2
dy = y1-y2
return dx*dx + dy*dy <= range*range
end
function RangeRatio(x1,y1,x2,y2,range)
dx = x1-x2
dy = y1-y2
return (dx*dx + dy*dy)/(range*range)
end
function DistanceBetween(x1,y1,x2,y2)
dx = x1-x2
dy = y1-y2
return math.sqrt(dx*dx + dy*dy)
end
function FastDistance(x1,y1,x2,y2)
dx = x1-x2
dy = y1-y2
return dx*dx + dy*dy
end
function LineDistance(x1,x2)
return math.abs(x1-x2)
end
function IsInSquare(x,y,size,posX,posY)
return x > posX - size and x < posX + size and y > posY - size and y < posY + size
end
function AngleBetween(x1,y1,x2,y2)
return math.atan(x2-x1,y2-y1)
end
function AngleBetween3D(x1,y1,z1,x2,y2,z2)
dx = x1-x2
dy = y1-y2
dz = z1-z2
return math.atan(dy,dx), math.atan(math.sqrt(dy*dy + dx*dx), dz) +math.pi
end
function Asymptotic(base,offset)
return base / (base + offset)
end
function IsPlusOrMinus(target, base, range)
return base + range < target or base - range > target
end
function varagToTable(...)
local tbl = NewTable()
for i = select("#",...), 1, -1 do
tbl[i] = select(i,...)
end
return tbl
end
function table.removeElement(tab, element)
for i = 1, #tab do
if tab[i] == element then
table.remove(tab,i)
break
end
end
end
function table.removeElementFast(tab, element)
for i = 1, #tab do
if tab[i] == element then
tab[i] = tab[#tab]
tab[#tab] = nil
break
end
end
end
function table.shuffle(tbl)
for i = #tbl, 2, -1 do
local j = math.random(i)
tbl[i], tbl[j] = tbl[j], tbl[i]
end
return tbl
end
function table.hasElement(tab, element)
for i = 1, #tab, 1 do
local v = tab[i]
if v == element then
return true
end
end
return false
end
function table.safeInsert(tab, element)
for i = 1, #tab, 1 do
local v = tab[i]
if v == element then
return
end
end
table.insert(tab,element)
end
function ActionAdd(tab, add,action)
if add then
tab[#tab+1] = action
else
table.removeElement(tab,action)
end
end
function enum(tbl)
local length = #tbl
for i = 1, length do
local v = tbl[i]
tbl[v] = i
end
return tbl
end
local timerTable = {}
local timerData = {}
function NewTimer(data)
local newTimer = timerTable[#timerTable] or CreateTimer()
timerData[newTimer] = data
timerTable[#timerTable] = nil
return newTimer
end
function ReleaseTimer(t)
t = t or GetExpiredTimer()
timerData[t] = nil
PauseTimer(t)
table.insert(timerTable,t)
end
function SetTimerData(t,data)
timerData[t] = data
end
function GetTimerData(t)
t = t or GetExpiredTimer()
return timerData[t]
end
local groupTable = {}
function NewGroup()
local newGroup = groupTable[#groupTable] or CreateGroup()
groupTable[#groupTable] = nil
return newGroup
end
function ReleaseGroup(g)
GroupClear(g)
table.insert(groupTable,g)
end
function AttachUnitEffect(u,sfx)
local udata = UnitData[GetUnitUserData(u)]
udata.attachedEffects = udata.attachedEffects or NewTable()
table.insert(udata.attachedEffects,sfx)
return sfx
end
function RemoveUnitEffect(u,sfx)
local udata = UnitData[GetUnitUserData(u)]
table.removeElement(udata.attachedEffects,sfx)
DestroyEffect(sfx)
end
function SetUnitMaxHP(u,hp)
local lastMax = BlzGetUnitMaxHP(u)
local fragile = UnitData[GetUnitUserData(u)].fragileHP or 0
hp = hp + fragile
BlzSetUnitMaxHP(u,math.round(hp))
SetWidgetLife(u,GetWidgetLife(u)-math.min(lastMax-hp,0))
end
function SetUnitRGBA(u,r,g,b,a)
local udata = UnitData[GetUnitUserData(u)]
SetUnitVertexColor(u,r,g,b,a)
udata.col_r = r
udata.col_g = g
udata.col_b = b
udata.col_a = a
end
function SoftSetUnitRGBA(u,r,g,b,a)
local udata = UnitData[GetUnitUserData(u)]
udata.col_r = udata.col_r or BlzGetUnitIntegerField(u,UNIT_IF_TINTING_COLOR_RED)
udata.col_g = udata.col_g or BlzGetUnitIntegerField(u,UNIT_IF_TINTING_COLOR_GREEN)
udata.col_b = udata.col_b or BlzGetUnitIntegerField(u,UNIT_IF_TINTING_COLOR_BLUE)
SetUnitVertexColor(u,r,g,b,a)
end
function ApplyUnitRGBA(u)
local udata = UnitData[GetUnitUserData(u)]
SetUnitVertexColor(u,udata.col_r or 255,udata.col_g or 255,udata.col_b or 255,udata.col_a or 255)
end
function InitMisc()
local moveLoc = Location(0,0)
function LocationZ(x,y)
MoveLocation(moveLoc,x,y)
return GetLocationZ(moveLoc)
end
local function removeEffect(u,index)
local attachedSFX = UnitData[index].attachedEffects
if attachedSFX then
for i = 1, #attachedSFX, 1 do
DestroyEffect(attachedSFX[i])
end
ReleaseTable(attachedSFX)
UnitData[index].attachedEffects = nil
end
end
ActionAdd(DeathActions, true,function(u,_,index)
removeEffect(u,index)
end)
ActionAdd(IndexerActions.remove, true, removeEffect)
end
end
do
local bitTbl = {}
function bit(p)
bitTbl[p] = bitTbl[p] or math.floor(2 ^ (p -1))
return bitTbl[p]
end
function hasbit(x, p)
return math.fmod(x,p + p) >= p
end
function setbit(x, p)
return hasbit(x, p) and x or x + p
end
function clearbit(x, p)
return hasbit(x, p) and x - p or x
end
end
do
RNG = {seed = 0, mult = 1140671485, mod = 16777216, inc = 12820163}
function RNG:new(tbl)
tbl = tbl or {}
setmetatable(tbl,RNG)
tbl.div = 1/tbl.mod
return tbl
end
function RNG.__index(tbl,key)
return RNG[key]
end
local d, m
function RNG:__call(low,high)
d = self.seed * self.mult + self.inc
m = d - math.floor(d * self.div) * self.mod
if m < 0 then
m = m + self.mod
end
self.seed = m
m = m * self.div
if low then
if high then
m = m*(high-low)+low
else
m = m *(low-1) + 1
end
if math.type(low) == "integer" then
m = m>=0 and math.floor(m+0.5) or math.ceil(m-0.5)
end
end
return m
end
function math.randomRange(low,high)
return math.random()*(high-low)+low
end
function math.GetPlayerRandom(pdata,chance)
return GetRandomReal(0,1) < chance * (pdata.WhoNeedsLuck or 1)
end
end
do
alias_table = {}
function alias_table:new(weights)
local total = 0
for i = 1, #weights do
total = total + weights[i]
end
local normalize = #weights / total
local norm = NewKeyTable()
local small_stack = NewKeyTable()
local big_stack = NewKeyTable()
for i = 1, #weights do
local w = weights[i]
norm[i] = w * normalize
if norm[i] < 1 then
small_stack[#small_stack+1] = i
else
big_stack[#big_stack+1] = i
end
end
local prob = NewKeyTable()
local alias = NewKeyTable()
while small_stack[1] and big_stack[1] do -- both non-empty
small = table.remove(small_stack)
large = table.remove(big_stack)
prob[small] = norm[small]
alias[small] = large
norm[large] = norm[large] + norm[small] - 1
if norm[large] < 1 then
small_stack[#small_stack+1] = large
else
big_stack[#big_stack+1] = large
end
end
for i = 1, #big_stack do prob[big_stack[i]] = 1 end
for i = 1, #small_stack do prob[small_stack[i]] = 1 end
ReleaseKeyTable(norm)
ReleaseKeyTable(small_stack)
ReleaseKeyTable(big_stack)
self.__index = self
local tbl = NewTable()
tbl.alias = alias
tbl.prob = prob
tbl.n = #weights
ReleaseTable(weights)
return setmetatable(tbl, self)
end
function alias_table:destroy()
ReleaseKeyTable(self.alias)
ReleaseKeyTable(self.prob)
self.alias = nil
self.prob = nil
self.n = nil
setmetatable(self,nil)
ReleaseTable(self)
end
function alias_table:__call()
local index = math.random(self.n)
return math.random() < self.prob[index] and index or self.alias[index]
end
end
do
local SoundPitch = {}
SOUND = {
path = "",
pitch = 1,
duration = 0,
looping = false,
is3D = false,
outOfRangeStop = false,
minDist = 4000,
maxDist = 10000,
fadeIn = 1,
fadeOut = 1,
eaxSetting = "DefaultEAXON",
volume = 127,
}
function SOUND:new(t)
t = t or {}
setmetatable(t,self)
self.__index = self
return t
end
local function recycle()
local tbl = GetTimerData()
table.insert(tbl[1].instances,tbl[2])
ReleaseTimer()
ReleaseTable(tbl)
end
local function loading()
local tbl = GetTimerData()
table.insert(tbl.self.instances,tbl.snd)
tbl.self(tbl.isLocal,tbl.x,tbl.y,tbl.target)
ReleaseTimer()
ReleaseKeyTable(tbl)
end
function SOUND:stop(snd,fade)
StopSound(snd,false,fade)
table.insert(self.instances,snd)
end
function SOUND:__call(isLocal,x,y,target,pitch)
self.instances = self.instances or {}
local pos = #self.instances
local snd
if pos == 0 then
snd = CreateSound(
self.path,
self.looping,
self.is3D,
self.outOfRangeStop,
self.fadeIn,
self.fadeOut,
self.eaxSetting
)
SetSoundVolume(snd,self.volume)
SetPitch(snd,pitch or self.pitch)
if self.is3D then
SetSoundDistances(snd,self.minDist,self.maxDist)
end
local tbl = NewKeyTable()
tbl.snd,tbl.self,tbl.isLocal,tbl.x,tbl.y,tbl.target = snd,self,isLocal,x,y,target
TimerStart(NewTimer(tbl),0,false,loading)
else
snd = self.instances[pos]
table.remove(self.instances,pos)
if x and y then
SetSoundPosition(snd,x,y,0)
elseif target then
AttachSoundToUnit(snd,target)
end
if isLocal == nil or isLocal then
StartSound(snd)
end
if not self.looping then
local tbl = NewTable()
tbl[1],tbl[2] = self,snd
TimerStart(NewTimer(tbl),self.duration*SoundPitch[snd],false,recycle)
end
end
return snd
end
function SetPitch(snd,pitch)
if GetSoundIsPlaying(snd) or GetSoundIsLoading(snd) then
local last = SoundPitch[snd] or 1
SetSoundPitch(snd,1/last)
SetSoundPitch(snd,pitch)
elseif pitch == 1 then
SetSoundPitch(snd,1.0001)
pitch = 1.0001
else
SetSoundPitch(snd,pitch)
end
SoundPitch[snd] = pitch
end
end
do
local function NumberString(Number)
local String = ''
repeat
local Remainder = Number % 2
String = Remainder .. String
Number = (Number - Remainder) / 2
until Number == 0
return String
end
function FromBinary(String)
if (#String % 8 ~= 0)
then
error('Malformed binary sequence', 2)
end
local Result = ''
for i = 1, (#String), 8 do
Result = Result..string.char(tonumber(String:sub(i, i + 7), 2))
end
return Result
end
function ToBinary(String)
if (#String > 0) then
local Result = ''
for i = 1, (#String)
do
Result = Result .. string.format('%08d', NumberString(string.byte(string.sub(String, i, i))))
end
return Result
else
return nil
end
end
function RandomString(length,character_set)
local character_set_amount = #character_set
local random_string = NewTable()
for int = 1, length do
local random_number = math.random(1, character_set_amount)
local character = string.sub(character_set, random_number, random_number)
random_string[#random_string + 1] = character
end
character_set = table.concat(random_string)
ReleaseTable(random_string)
return character_set
end
function string.toCharWhole(str)
local rtn = ""
for w in str:gmatch("%%d+") do
rtn = rtn..string.char(w)
end
return rtn
end
do
local rng = RNG:new()
function string.scramble(s,seed)
rng.seed = seed - 10263
local newS = ""
for i = #s, 1, -1 do
local pos = rng(i)
newS = newS..string.sub(s,pos,pos)
s = string.sub(s,1,pos-1)..string.sub( s, pos+1, i)
end
return newS
end
end
end
function sha256(message)
-- magic numbers
local h0 = 0x67452301;
local h1 = 0xEFCDAB89;
local h2 = 0x98BADCFE;
local h3 = 0x10325476;
local h4 = 0xC3D2E1F0;
-- padding, etc
local bits = #message * 8;
message = message .. '\x80';
local paddingAmount = (120 - (#message %% 64)) %% 64;
message = message .. string.rep('\0', paddingAmount);
message = message .. string.pack('>I8', bits);
-- rotate function
local function rol(value, bits)
return (((value) << (bits)) | ((value) >> (32 - (bits))));
end;
-- process each chunk
for i=1,#message,64 do
local chunk = string.sub(message, i, i+63);
local parts = {};
-- split chunk into 16 parts
for i=0,15 do
parts[i] = string.unpack('>I4',string.sub(chunk, 1+i*4, 4+i*4));
end;
-- extend into 80 parts
for i=16,79 do
parts[i] = rol(parts[i-3] ~ parts[i-8] ~ parts[i-14] ~ parts[i-16], 1) & 0xFFFFFFFF;
end;
-- initialise hash values
local a,b,c,d,e = h0,h1,h2,h3,h4;
local f,k;
-- main loop
for i=0,79 do
if 0 <= i and i <= 19 then
f = (b & c) | ((~b) & d)
k = 0x5A827999
elseif 20 <= i and i <= 39 then
f = b ~ c ~ d
k = 0x6ED9EBA1
elseif 40 <= i and i <= 59 then
f = (b & c) | (b & d) | (c & d)
k = 0x8F1BBCDC
elseif 60 <= i and i <= 79 then
f = b ~ c ~ d
k = 0xCA62C1D6
end
local temp = (rol(a, 5) + f + e + k + parts[i]) & 0xFFFFFFFF
e = d;
d = c;
c = rol(b, 30);
b = a;
a = temp;
end;
h0 = (h0 + a) & 0xFFFFFFFF;
h1 = (h1 + b) & 0xFFFFFFFF;
h2 = (h2 + c) & 0xFFFFFFFF;
h3 = (h3 + d) & 0xFFFFFFFF;
h4 = (h4 + e) & 0xFFFFFFFF;
end;
return string.format('%%08x%%08x%%08x%%08x%%08x', h0, h1, h2, h3, h4);
end
do
local list = {}
local plist = {}
function AttachFunctionToUnitType(unitid,func)
list[unitid] = list[unitid] or {}
table.insert(list[unitid],func)
for i = 1, #EveryUnit do
local u = EveryUnit[i]
if GetUnitTypeId(u) == unitid then
func(u,UnitData[GetUnitUserData(u)])
end
end
end
function AttachFunctionPlayerToUnitType(pdata,unitid,func)
local playerList
plist[pdata] = plist[pdata] or {}
plist[pdata][unitid] = plist[pdata][unitid] or {}
table.insert(plist[pdata][unitid],func)
for i = 1, #pdata.AllUnits do
local u = pdata.AllUnits[i]
if GetUnitTypeId(u) == unitid then
func(u,UnitData[GetUnitUserData(u)])
end
end
end
function InitAttachFuncToUnitType()
ActionAdd(IndexerActions.add, true,function(u,index)
local id = GetUnitTypeId(u)
if list[id] then
local udata = UnitData[index]
for i = 1, #list[id] do
list[id][i](u,udata)
end
end
local pdata = Player2Data(GetOwningPlayer(u))
if plist[pdata] and plist[pdata][id] then
local udata = UnitData[index]
for i = 1, #plist[id] do
plist[id][i](u,udata)
end
end
end)
end
end
do
ChatActions = {}
function InitChat()
local t = CreateTrigger()
for _, p in ipairs(PlayerUsers) do
TriggerRegisterPlayerChatEvent(t,p,"",false)
end
TriggerAddAction(t,function()
local pdata = Player2Data(GetTriggerPlayer())
local msg = GetEventPlayerChatString()
for i = 1, #ChatActions do
ChatActions[i](pdata,msg)
end
end)
end
end
function InitLoading()
for i = 0, MAX_PLAYERS, 1 do
local p = Player(i)
if p ~= GetLocalPlayer() then
SetPlayerName(p,"Swarm")
end
end
CinematicFadeCommonBJ(0, 0, 0, 0, "ReplaceableTextures\\CameraMasks\\White_mask", 100, 0)
LoadMusic()
end
function InitGame()
EnableUserUI(true)
FogEnable(false)
FogMaskEnable(false)
SetDayNightModels("CustomLight-Terrain", "CustomLight-Unit")
BlzFrameSetAllPoints(BlzGetOriginFrame(ORIGIN_FRAME_WORLD_FRAME, 0), BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0))
BlzHideOriginFrames(true)
BlzFrameSetVisible(BlzGetFrameByName("ConsoleUIBackdrop",0),false)
BlzChangeMinimapTerrainTex("Minimap.tga")
math.randomseed(GetRandomInt(0,2147483647))
InitTimedLightning()
InitPlayerData()
InitMouseUtils()
InitPrimarySelection()
InitWorldBounds()
InitCosts()
InitSytheticGroups()
InitDynamicAbils()
InitBuilding()
InitObjectGrouping()
InitUnitIndexer()
InitOSKey()
InitUI()
InitDeathEvent()
InitDamageEvent()
InitOrderEvent()
InitAttackEvent()
InitMining()
InitDungeon()
InitTurretAttacks()
InitWaves()
InitCorruption()
InitAttachFuncToUnitType()
InitDayNight()
InitMusic()
InitMisc()
InitPathing()
InitDecode()
GAME_TIME = 0
GAME_TICK = 0.03125
File.onInit()
InitChat()
AddActionInTimerQueue(GAME_TICK,function()
GAME_TIME = GAME_TIME + GAME_TICK
end)
FogModifierStart(CreateFogModifierRadius(Player(0),FOG_OF_WAR_VISIBLE,0,0,512,true,true))
SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, 6000, 0)
SetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK,300,0)
SetCameraField(CAMERA_FIELD_FARZ, 30000, 0)
end
do
ABILITY_ID_FLIGHT = FourCC('Arav')
Players_Alive_Count = 0
GAME_STARTED = false
function SynthesizerDies()
local u = GetTriggerUnit()
local pdata = Player2Data(GetOwningPlayer(u))
if not pdata.Revival then
local x,y = GetUnitX(u),GetUnitY(u)
RemoveUnit(u)
local s = (PlayerDataLocal == pdata and "|cffff0000You have died.|r" or "|cffff0000"..pdata.name.." has died.|r")
DisplayTextToPlayer(LocalPlayer,0,0,s)
Players_Alive_Count = Players_Alive_Count - 1
local g = NewGroup()
GroupEnumUnitsOfPlayer(g,pdata.p,nil)
ForGroup(g,function()
local enum = GetEnumUnit()
local udata = UnitData[GetUnitUserData(enum)]
if udata.cost then
RecycleUnit(pdata,enum,udata)
else
KillUnit(enum)
end
end)
ReleaseGroup(g)
if Players_Alive_Count > 0 then
for j = 1, #ResourceTypes do
pdata.resource[j] = math.floor(pdata.resource[j] / Players_Alive_Count)
end
pdata.PlayerAlive = false
for i = 1, #PlayerData do
local pdata2 = PlayerData[i]
if pdata2.PlayerAlive then
local x2, y2 = GetUnitX(pdata2.playerMiner), GetUnitY(pdata2.playerMiner)
for j = 1, #ResourceTypes, 1 do
MiningTextTag(pdata2,pdata.resource[j], ResourceTypes[j].color, miningStartColor, x, y, x2, y2, j*.5)
AddMinerals(pdata2,j,pdata.resource[j])
end
end
end
else
for i = 1, #EveryUnit do
local u = EveryUnit[i]
PauseUnit(u,true)
SetUnitTimeScale(u,0)
BlzSetUnitRealField(u,UNIT_RF_HIT_POINTS_REGENERATION_RATE,0)
BlzSetUnitRealField(u,UNIT_RF_MANA_REGENERATION,0)
SetUnitState(u,UNIT_STATE_MANA,0)
SetUnitInvulnerable(u,true)
end
SuspendTimeOfDay(true)
DisplayTextToPlayer(LocalPlayer,0,0,"|cffff0000All sythesizers have been destroyed. The Thrul will overrun this sector.|r")
FogEnable(not PlayerDataLocal.user)
FogMaskEnable(not PlayerDataLocal.user)
end
end
end
function Starter_moveStarter()
local tbl = GetTimerData()
tbl.dur = math.max(tbl.dur - 0.02,0)
local time = easeOutQuad(1-(tbl.dur/3))
SetUnitFlyHeight(tbl.u,lerp(time,3000,32),0)
SetUnitX(tbl.u,lerp(time,tbl.x,tbl.x2))
SetUnitY(tbl.u,lerp(time,tbl.y,tbl.y2))
if tbl.dur == 0 then
ReleaseTimer()
PauseUnit(tbl.u,false)
if tbl.pdata.skinId then
BlzSetUnitSkin(tbl.u,tbl.pdata.skinId)
SetUnitFlyHeight(tbl.u,0,0)
end
if tbl.pdata.ConsecutiveDaysPlayed == 5 then
Achievement.get(tbl.pdata,Achievement["Dedicated"])
end
if not GAME_STARTED then
GAME_STARTED = true
ResetToGameCamera(1)
BlzHideOriginFrames(false)
BlzFrameSetVisible(BlzGetFrameByName("ConsoleUIBackdrop",0),true)
soundMiningUnitActive()
FogEnable(true)
FogMaskEnable(true)
InitTips()
end
ReleaseKeyTable(tbl)
end
end
function Starter_CreateStarter(p,x,y,angle,dur)
local u = CreateUnit(p,UNIT_ID_SYNTHESIZER,x,y,angle*bj_RADTODEG)
TriggerRegisterUnitEvent(PlayerDeathTrigger,u,EVENT_UNIT_DEATH)
local pdata = Player2Data(p)
ShowUnit(u,false)
BlzSetHeroProperName(u,pdata.name)
pdata.playerMiner = u
local x2, y2 = x + 256*math.cos(angle), y + 256*math.sin(angle)
UnitAddAbility(u,ABILITY_ID_FLIGHT)
UnitRemoveAbility(u,ABILITY_ID_FLIGHT)
SetUnitFlyHeight(u,30000,0)
ShowUnit(u,true)
PauseUnit(u,true)
UnitAddType(u,UNIT_TYPE_PEON)
if LocalPlayer == p then
SelectUnit(u,true)
end
local tbl = NewKeyTable()
tbl.dur = dur
tbl.u = u
tbl.pdata = pdata
tbl.x = x
tbl.y = y
tbl.x2 = x2
tbl.y2 = y2
TimerStart(NewTimer(tbl),0.03125,true,Starter_moveStarter)
end
function InitGameStart()
AddActionInTimerQueue(1,function()
BlzFrameSetText(BlzGetFrameByName("ResourceBarUpkeepText" , 0) , string.gsub(os.date("%%I:%%M %%p"),"0*","",1))
end)
PlayerDeathTrigger = CreateTrigger()
TriggerAddAction(PlayerDeathTrigger,SynthesizerDies)
soundMiningUnitActive = SOUND:new({
path = "Sounds\\MiningUnitsActivated",
duration = 2.280
})
TimerStart(CreateTimer(),5,true,WriteSave)
local x,y = GetPortalCenter()
CinematicFadeCommonBJ(0, 0, 0, 5, "ReplaceableTextures\\CameraMasks\\White_mask", 0, 100)
EnableUserUI(true)
local anglePer = #PlayerUsers > 1 and math.pi*2/#PlayerUsers or math.pi*2*math.random()
SetCameraPosition(x,y)
for i = 1, #PlayerUsers do
Starter_CreateStarter(PlayerUsers[i],x,y,i*anglePer,3)
end
end
end
do
cell_size = 128
cell_half = cell_size * .5
cross_cut_size = 1
Cell_Data = {}
Arena_orientation_Data = {}
MINERAL_TYPES = {FourCC("h000"),FourCC("h001"),FourCC("h002"),FourCC("h003")}
DestructableDeathEffect = {}
Arena_orientation_enum ={
{x=0, y=1, pos = 1},
{x=-1, y=0, pos = 2},
{x=1, y=0, pos = 4},
{x=0, y=-1, pos = 8}
}
CellUpdateActions = {}
function GetArenaCenter()
return (Arena_maxX+Arena_minX)*.5, (Arena_maxY+Arena_minY)*.5
end
function GetArenaSize()
return ARENA_LENGTH, ARENA_WIDTH
end
function NewOrientationBlock(dir,id,facing, func)
Arena_orientation_Data[dir] = {
TypeId = id,
Angle = facing,
Func = func
}
end
do
local index = {}
local mt = {
__index = function (t,k)
return t[index][k]
end,
__newindex = function (t,k,v)
t[index][k] = v
if t[index].ignore then return end
for i = 1, #CellUpdateActions do
CellUpdateActions[i](t)
end
end
}
function trackCell(t)
local proxy = {}
proxy[index] = t
setmetatable(proxy, mt)
return proxy
end
end
function InitDungeon()
MINERAL_OWNER = Player(PLAYER_NEUTRAL_PASSIVE)
Arena_maxX = GetRectMaxX(gg_rct_dung)
Arena_minX = GetRectMinX(gg_rct_dung)
Arena_maxY = GetRectMaxY(gg_rct_dung)
Arena_minY = GetRectMinY(gg_rct_dung)
ARENA_LENGTH = math.floor((math.abs(Arena_maxX) + math.abs(Arena_minX)) / cell_size)
ARENA_WIDTH = math.floor((math.abs(Arena_maxY) + math.abs(Arena_minY)) / cell_size)
ARENA_SIZE = (ARENA_LENGTH + ARENA_WIDTH)/2 * cell_size
function Arena_delayStopOrder()
local tbl = GetTimerData()
IssueImmediateOrder(tbl.u,"stop")
tbl.u=nil
ReleaseTimer()
ReleaseTable(tbl)
end
function CellNotAlive(cell)
return not cell.alive
end
function Arena_targetDest(data)
local pdata = Player2Data(data.owner)
if data.tarDest and pdata.playerMiner == data.source then
local x, y = Cord2Cell(GetWidgetX(data.tarDest),GetWidgetY(data.tarDest))
local cell = Cell_Data[x][y]
x,y = GetUnitX(data.source), GetUnitY(data.source)
local clock = os.clock()
if IsConfined(x,y,CellNotAlive) or IsConfined(x,y,IsCellEmpty) then
cell.ignore = true
cell.alive = false
if not (IsConfined(x,y,CellNotAlive) or IsConfined(x,y,IsCellEmpty)) then
local tbl = NewTable()
tbl.u = data.source
TimerStart(NewTimer(tbl),0,false,Arena_delayStopOrder)
ErrorMessage("This object is keeping you protected! Issue an attack order to confirm destruction.",pdata.p)
end
cell.alive = true
cell.ignore = false
end
end
end
function Arena_moveDestSfx()
local tbl = GetTimerData()
tbl.r = tbl.r + 1
if tbl.r < 32 then
BlzSetSpecialEffectZ(tbl.sfx,-tbl.r*8+tbl.height)
else
ReleaseTimer()
BlzSetSpecialEffectZ(tbl.sfx,-3000)
DestroyEffect(tbl.sfx)
ReleaseKeyTable(tbl)
end
end
ActionAdd(OrderIdToTable("smart"), true, Arena_targetDest)
GENERATE_DEST_DEATH_TRIG = CreateTrigger()
TriggerAddAction(GENERATE_DEST_DEATH_TRIG, function()
local d = GetTriggerDestructable()
local x, y = Cord2Cell(GetWidgetX(d),GetWidgetY(d))
local cell = Cell_Data[x][y]
cell.alive = false
if DestructableDeathEffect[d] then
DestroyEffect(AddSpecialEffect(DestructableDeathEffect[d],GetWidgetX(d),GetWidgetY(d)))
DestructableDeathEffect[d] = nil
RemoveDestructable(d)
end
local sfx = cell.sfx
local height = cell.sfxHeight or 0
if sfx == nil then
return
end
local tbl = NewKeyTable()
tbl.r = 0
tbl.sfx = sfx
tbl.height = height
TimerStart(NewTimer(tbl),0.03125,true,Arena_moveDestSfx)
end)
end
function IsCellFilled(x,y)
return Cell_Data[x] and Cell_Data[x][y] and Cell_Data[x][y].alive
end
function IsCellEmpty(cell)
return not (cell.alive or cell.built)
end
function DestroyCellFoliage(cell)
local sfx = cell.foliage
if sfx then
DestroyEffect(AddSpecialEffect("Objects\\Spawnmodels\\Naga\\NagaBlood\\NagaBloodWindserpent",BlzGetLocalSpecialEffectX(sfx),BlzGetLocalSpecialEffectY(sfx)))
BlzSetSpecialEffectZ(sfx,-99999)
DestroyEffect(sfx)
cell.foliage = nil
end
end
function GetCellNeighbors(x,y)
local count = 0
for i = -1, 1 do
for j = -1, 1 do
if not (i == 0 and j == 0) and IsCellFilled(x + i,y + j) then
count = count + 1
end
end
end
return count
end
function GetDiagonalNeighbors(x,y)
return (IsCellFilled(x - 1,y + 1) and 1 or 0) + (IsCellFilled(x + 1,y - 1) and 1 or 0) + (IsCellFilled(x + 1,y + 1) and 1 or 0) + (IsCellFilled(x - 1,y - 1) and 1 or 0)
end
function SetGridPathing(size,x,y,which,flag)
x = x - 16
y = y - 16
for posX = -size+1, size, 1 do
for posY = -size+1, size, 1 do
SetTerrainPathable(x + posX*32, y + posY*32, which, flag)
end
end
end
function GetCellOrientation(x,y)
local mask = 0
for i = 1, #Arena_orientation_enum do
if IsCellFilled(x + Arena_orientation_enum[i].x,y + Arena_orientation_enum[i].y) then
mask = mask + Arena_orientation_enum[i].pos
end
end
return mask
end
function Arena_CreatePortal()
local x2,y2 = Cord2Cell(GetArenaCenter())
local x,y = Cell2Cord(x2,y2)
function GetPortalCenter()
return x,y
end
CENTER_CORRUPTION = SpreadCorruption(x,y,2000)
end
local rand_chance
local function default_rand(x,y)
return rand_chance
end
function DungeonStartGen(rand, rub_chance, gens, popDeath, popBirth,generation)
generation.init(1, 2 ,4, 8)
rand_chance = rand
if type(rand) == "number" then
rand = default_rand
end
--Inital Conditions
for x = 1, ARENA_LENGTH do
Cell_Data[x] = {}
local xPlus = IsPlusOrMinus(cell_half,x,cross_cut_size)
for y = 1, ARENA_WIDTH do
local cell = Vector(x,y)
Cell_Data[x][y] = cell
cell.alive = math.random() < rand(Arena_minX+x* cell_size,Arena_minY + y * cell_size)
cell.cross = not (xPlus and IsPlusOrMinus(cell_half,y,cross_cut_size))
end
end
--Generations
for g = 1, gens do
for x = 1, ARENA_LENGTH do
for y = 1, ARENA_WIDTH do
local nbs = GetCellNeighbors(x,y)
if Cell_Data[x][y].alive then
Cell_Data[x][y].aliveNew = not (nbs < popDeath)
else
Cell_Data[x][y].aliveNew = nbs > popBirth
end
end
end
for x = 1, ARENA_LENGTH do
for y = 1, ARENA_WIDTH do
local cell = Cell_Data[x][y]
cell.alive, cell.aliveNew = cell.aliveNew, nil
end
end
end
--Final Creation
generation.generate(GENERATE_DEST_DEATH_TRIG,Arena_orientation_Data,rub_chance)
for x = 1, ARENA_LENGTH, 1 do
for y = 1, ARENA_WIDTH, 1 do
local cell = Cell_Data[x][y]
if cell.aliveNew then
cell.alive, cell.aliveNew = cell.aliveNew, nil
elseif cell.cross then
cell.alive = false
end
Cell_Data[x][y] = trackCell(cell)
end
end
Arena_CreatePortal()
end
function SetMineralQuality(u,lvl,animate)
BlzSetUnitRealField(u,UNIT_RF_HIT_POINTS_REGENERATION_RATE,lvl * .5 + lvl*lvl*.3)
local scale = math.min(lvl * .05 + .75,1.5)
local hpCur = GetWidgetLife(u)
local hp = 50 + lvl*lvl * 9
BlzSetUnitMaxHP(u, hp)
SetWidgetLife(u, hpCur + hp-hpCur)
local udata = UnitData[GetUnitUserData(u)]
if animate then
SetScaleTimed(u,scale,1,easeInOutBounce)
else
SetUnitScale(u,scale,scale,scale)
end
udata.mineralQuality = lvl
udata.scale=scale
end
function CreateRandomMineral(x,y)
local u = CreateUnit(MINERAL_OWNER,MINERAL_TYPES[math.random(#MINERAL_TYPES)],x,y,math.random()*360)
local unitData = UnitData[GetUnitUserData(u)]
local minId = math.random(#ResourceTypes)
local minType = ResourceTypes[minId]
local color = minType.color
unitData.mineralType = minId
SetMineralQuality(u, math.ceil(easeInExpo(math.random()) * 10)+GemformerBonus)
BlzSetUnitName(u,color.s .. minType.name .. "|r")
SetUnitRGBA(u,color.r,color.g,color.b,255)
UnitRemoveAbility(u,ABILITY_HUMAN_FIRE)
local x2, y2 = Cord2Cell(x,y)
local cell = Cell_Data[x2][y2]
if not cell.aliveNew then
cell.alive = true
end
end
function Cell2Cord(x,y,toVec)
if toVec then
return Vector(Arena_minX + x * cell_size, Arena_minY + y * cell_size)
else
return Arena_minX + x * cell_size, Arena_minY + y * cell_size
end
end
function ClampCords(x,y)
if x > Arena_maxX-64 then
x = Arena_maxX-64
elseif x < Arena_minX+64 then
x = Arena_minX+64
end
if y > Arena_maxY-64 then
y = Arena_maxY-64
elseif y < Arena_minY+64 then
y = Arena_minY+64
end
return x,y
end
function Cord2Cell(x,y,toCell)
if toCell then
return Cell_Data[math.round((MinMax(x,Arena_minX,Arena_maxX) - Arena_minX)/cell_size)][math.round((MinMax(y,Arena_minY,Arena_maxY) - Arena_minY)/cell_size)]
else
return math.round((MinMax(x,Arena_minX,Arena_maxX) - Arena_minX)/cell_size), math.round((MinMax(y,Arena_minY,Arena_maxY) - Arena_minY)/cell_size)
end
end
function GetClosestCross(x,y,toCell)
if math.abs(x) > math.abs(y) then
return Cord2Cell(x,0,toCell)
else
return Cord2Cell(0,y,toCell)
end
end
function isCellValid(x,y)
local cell = Cell_Data[x]
if cell then
return cell[y]
end
return false
end
end
do
TEMPLE_GENERATION = {
init = function(N, W, E ,S)
local torchTable = {}
local function hasNoTorch(x,y)
local id = math.round(x/386) * WorldBounds.length + math.round(y/386)
if torchTable[id] then
return false
else
torchTable[id+1] = true
torchTable[id-1] = true
torchTable[id+WorldBounds.length] = true
torchTable[id-WorldBounds.length] = true
torchTable[id+WorldBounds.length+1] = true
torchTable[id-WorldBounds.length-1] = true
torchTable[id+WorldBounds.length-1] = true
torchTable[id-WorldBounds.length+1] = true
torchTable[id] = true
return true
end
end
function NS_and_EW_Effects(x,y,orient,cell)
local rand = math.random()
if hasNoTorch(x,y) and rand < .4 then
local sfx = AddSpecialEffect("Doodads\\WallLamp",x,y)
BlzSetSpecialEffectYaw(sfx,0.0174533*(orient.Angle + 180*math.random(0,1)))
BlzSetSpecialEffectTimeScale(sfx,math.random()*.75 + .25)
cell.sfx = sfx
elseif rand < .5 then
local sfx = AddSpecialEffect("Doodads\\WallRune",x,y)
BlzSetSpecialEffectTimeScale(sfx,0)
BlzSetSpecialEffectTime(sfx,math.random(1,25)*.001)
BlzSetSpecialEffectAlpha(sfx,math.random(200,255))
BlzSetSpecialEffectColor(sfx,math.random(100,255),0,math.random(100,255))
BlzSetSpecialEffectYaw(sfx,0.0174533*(orient.Angle + 180*math.random(0,1)))
cell.sfx = sfx
elseif rand < .6 then
local angle = 0.0174533*(orient.Angle + 180*math.random(0,1))
local sfx = AddSpecialEffect("Doodads\\azjol_shelfmushroom_0"..math.random(6),x + 64 * math.cos(angle),y + 64 *math.sin(angle))
cell.sfxHeight = math.random(150,220)
BlzSetSpecialEffectZ(sfx,cell.sfxHeight)
BlzSetSpecialEffectScale(sfx,math.randomRange(.5,1))
BlzSetSpecialEffectYaw(sfx,angle)
cell.sfx = sfx
end
end
NewOrientationBlock(N+S+E,FourCC("B001"),0)
NewOrientationBlock(N+W+E,FourCC("B001"),0)
NewOrientationBlock(N+S+W,FourCC("B001"),0)
NewOrientationBlock(N+E+W,FourCC("B001"),0)
NewOrientationBlock(N+E+W+S,FourCC("B001"),0)
NewOrientationBlock(E+W+S,FourCC("B001"),0)
NewOrientationBlock(N+S,FourCC("B001"),360,NS_and_EW_Effects)
NewOrientationBlock(W+E,FourCC("B001"),270,NS_and_EW_Effects)
NewOrientationBlock(E,FourCC("B002"),90)
NewOrientationBlock(W,FourCC("B002"),270)
NewOrientationBlock(N,FourCC("B002"),180)
NewOrientationBlock(S,FourCC("B002"),0)
NewOrientationBlock(N+E,FourCC("B003"),90)
NewOrientationBlock(S+E,FourCC("B003"),0)
NewOrientationBlock(S+W,FourCC("B003"),270)
NewOrientationBlock(N+W,FourCC("B003"),180)
NewOrientationBlock(0,FourCC("B000"),0)
end,
generate = function(DestDeathTrig,orientation_Data,rub_chance)
local offx, offy = math.random(99999),math.random(99999)
local function CreateTerrain(x,y, scale)
local r = Noise.perlin2D ((x+offx)*scale, (y+offy)*scale)
if r < 0 then
SetTerrainType(x-cell_half, y-cell_half, FourCC("Zdtr"), -1, 1, 1)
else
SetTerrainType(x-cell_half, y-cell_half, FourCC("Zbks"), -1, 1, 1)
end
end
SetTerrainType(0, 0, FourCC('Zbkl'), -1, 9999, 1)
for x = 1, ARENA_LENGTH, 1 do
for y = 1, ARENA_WIDTH, 1 do
local cell = Cell_Data[x][y]
cell.barrier = x == 1 or y == 1 or x == ARENA_LENGTH or y == ARENA_WIDTH
if cell.barrier then
cell.alive = true
end
end
end
for x = 1, ARENA_LENGTH, 1 do
for y = 1, ARENA_WIDTH, 1 do
local x2, y2 = Cell2Cord(x,y)
local cell = Cell_Data[x][y]
if not (x2 < 512 and x2 > -512 and y2 < 512 and y2 > -512) then
if cell.barrier then
SetDestructableInvulnerable(CreateDestructable(FourCC("B001"),x2,y2,0,1,1),true)
elseif not cell.cross then
if cell.alive then
local orient = orientation_Data[GetCellOrientation(x,y)]
if orient.Func ~= nil then
orient.Func(x2,y2,orient,cell)
end
TriggerRegisterDeathEvent(DestDeathTrig,CreateDestructable(orient.TypeId,x2,y2,orient.Angle,1,0))
SetTerrainType(x2, y2, FourCC("Ztil"), -1, 1, 1)
else
local nbs = GetCellNeighbors(x,y)
cell.aliveNew = math.random() < rub_chance and nbs > 0
if cell.aliveNew then
TriggerRegisterDeathEvent(DestDeathTrig,CreateDestructable(FourCC("LTrc"),x2,y2,math.random()*360,1,math.random(5)))
elseif math.random() < .1 - 0.0125 * nbs and IsPlusOrMinus(cell_half,x,cross_cut_size+5) and IsPlusOrMinus(cell_half,y,cross_cut_size+5)then
cell.aliveNew = true
CreateRandomMineral(x2,y2)
end
CreateTerrain(x2,y2, 0.005)
end
else
SetTerrainType(x2, y2, FourCC("Zbkl"), -1, 1, 1)
end
else
CreateTerrain(x2,y2, 0.005)
cell.alive = false
end
end
end
end
}
end
do
DESERT_GENERATION = {
init = function(N, W, E ,S)
end,
generate = function(DestDeathTrig,orientation_Data,rub_chance)
local offx, offy = math.random(99999),math.random(99999)
local function CreateTerrain(x,y, scale)
local r = Noise.perlin2D ((x+offx)*scale, (y+offy)*scale)
if r < 0 then
SetTerrainType(x-cell_half, y-cell_half, FourCC('Bflr'), -1, 1, 1)
else
SetTerrainType(x-cell_half, y-cell_half, FourCC( 'Bdsr'), -1, 1, 1)
end
end
SetTerrainType(0, 0, FourCC('Bdsr'), -1, 9999, 1)
local foliage = {"Doodads\\agave"}
for x = 1, ARENA_LENGTH, 1 do
for y = 1, ARENA_WIDTH, 1 do
local x2, y2 = Cell2Cord(x,y)
local cell = Cell_Data[x][y]
if not (x2 < 512 and x2 > -512 and y2 < 512 and y2 > -512) then
local barrier = x == 1 or y == 1 or x == ARENA_LENGTH or y == ARENA_WIDTH
if barrier then
cell.alive = true
SetDestructableInvulnerable(CreateDestructable(FourCC("B006"),x2,y2,math.random()*360,1.5,math.random(10)),true)
elseif not cell.cross then
if cell.alive then
TriggerRegisterDeathEvent(DestDeathTrig,CreateDestructable(FourCC('DTrc'),x2,y2,math.random()*360,1,math.random(6)))
SetTerrainType(x2, y2, FourCC('Bdrr'), -1, 1, 1)
else
local nbs = GetCellNeighbors(x,y)
cell.aliveNew = math.random() < rub_chance and nbs > 0
if cell.aliveNew then
TriggerRegisterDeathEvent(DestDeathTrig,CreateDestructable(FourCC("BTtw"),x2,y2,math.random()*360,1,math.random(10)))
elseif math.random() < 0.025 * nbs and IsPlusOrMinus(cell_half,x,cross_cut_size+5) and IsPlusOrMinus(cell_half,y,cross_cut_size+5)then
cell.aliveNew = true
CreateRandomMineral(x2,y2)
else
cell.aliveNew = math.random() < rub_chance*.25 and nbs == 0
if cell.aliveNew then
local d = CreateDestructable(FourCC('B007'),x2,y2,math.random()*360,1.5,math.random(10))
DestructableDeathEffect[d] = "Objects\\Spawnmodels\\Naga\\NagaBlood\\NagaBloodWindserpent"
TriggerRegisterDeathEvent(DestDeathTrig,d)
end
end
CreateTerrain(x2,y2, 0.008)
end
else
SetTerrainType(x2, y2, FourCC('Bdsd'), -1, 1, 1)
end
else
CreateTerrain(x2,y2, 0.008)
cell.alive = false
end
if not cell.alive and not cell.aliveNew and not cell.cross and math.random() > .98 then
cell.foliage = AddSpecialEffect(foliage[math.random(#foliage)],x2+math.randomRange(-48,48),y2+math.randomRange(-48,48))
BlzSetSpecialEffectScale(cell.foliage,math.randomRange(1,1.2))
BlzSetSpecialEffectYaw(cell.foliage,math.randomRange(0,6.28319))
end
end
end
end
}
end
do
FOREST_GENERATION = {
init = function(N, W, E ,S)
end,
generate = function(DestDeathTrig,orientation_Data,rub_chance)
local offx, offy = math.random(99999),math.random(99999)
local function CreateTerrain(x,y, scale)
local r = Noise.perlin2D ((x+offx)*scale, (y+offy)*scale)
if r < -.5 then
SetTerrainType(x-cell_half, y-cell_half, FourCC('Adrd'), -1, 1, 1)
elseif r < .9 then
SetTerrainType(x-cell_half, y-cell_half, FourCC('Agrs'), -1, 1, 1)
else
SetTerrainType(x-cell_half, y-cell_half, FourCC('Agrd'), -1, 1, 1)
end
end
local trees = {FourCC('B008'),FourCC('B009'),FourCC('B00A')}
local bigTrees = {FourCC('B00H'),FourCC('B00J'),FourCC('B00I')}
local foliage = {"Doodads\\AshenBush0","Doodads\\AshenBush1","Doodads\\AshenShrooms"}
EnableWeatherEffect(AddWeatherEffect(WorldBounds.r, FourCC('RAlr')),true)
SetTerrainType(0, 0, FourCC('Lrok'), -1, 9999, 1)
for x = 1, ARENA_LENGTH, 1 do
for y = 1, ARENA_WIDTH, 1 do
local x2, y2 = Cell2Cord(x,y)
local cell = Cell_Data[x][y]
if not (x2 < 512 and x2 > -512 and y2 < 512 and y2 > -512) then
local barrier = x == 1 or y == 1 or x == ARENA_LENGTH or y == ARENA_WIDTH
local rock = false
if barrier then
cell.alive = true
if math.random() > .5 then
SetDestructableInvulnerable(CreateDestructable(FourCC('B00B'),x2,y2,math.random()*360,1.5,math.random(10)),true)
else
SetDestructableInvulnerable(CreateDestructable(bigTrees[math.random(#bigTrees)],x2,y2,math.random()*360,1.5,0),true)
end
elseif not cell.cross then
local nbs = GetCellNeighbors(x,y)
if cell.alive then
if nbs == 8 and math.random() >.9 then
CreateDestructable(bigTrees[math.random(#bigTrees)],x2,y2,math.random()*360,math.randomRange(1.2,1.5),0)
else
if math.random() > .5 then
TriggerRegisterDeathEvent(DestDeathTrig,CreateDestructable(FourCC('LTrc'),x2,y2,math.random()*360,1,math.random(6)))
else
TriggerRegisterDeathEvent(DestDeathTrig,CreateDestructable(trees[math.random(3)],x2,y2,math.random()*360,1,0))
end
end
SetTerrainType(x2, y2, FourCC('Agrd'), -1, 1, 1)
else
cell.aliveNew = math.random() < rub_chance and nbs > 0
if cell.aliveNew then
TriggerRegisterDeathEvent(DestDeathTrig,CreateDestructable(trees[math.random(3)],x2,y2,math.random()*360,1,0))
elseif math.random() < 0.05 * nbs and IsPlusOrMinus(cell_half,x,cross_cut_size+5) and IsPlusOrMinus(cell_half,y,cross_cut_size+5)then
cell.aliveNew = true
CreateRandomMineral(x2,y2)
else
cell.aliveNew = math.random() < rub_chance*.04 and nbs == 0
if cell.aliveNew then
if math.random() > .25 then
local d = CreateDestructable(FourCC('B00B'),x2,y2,math.random()*360,1,math.random(10))
DestructableDeathEffect[d] = "Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust"
TriggerRegisterDeathEvent(DestDeathTrig,d)
SetTerrainType(x2, y2, FourCC('Lrok'), -1, 1, 1)
rock = true
else
CreateDestructable(bigTrees[math.random(#bigTrees)],x2,y2,math.random()*360,math.randomRange(1.2,1.5),0)
end
end
end
if not rock then
CreateTerrain(x2,y2, 0.002)
end
end
else
SetTerrainType(x2, y2, FourCC('Lrok'), -1, 1, 1)
end
else
CreateTerrain(x2,y2, 0.002)
cell.alive = false
end
if not cell.alive and not cell.aliveNew and not cell.cross and math.random() > .85 then
cell.foliage = AddSpecialEffect(foliage[math.random(#foliage)],x2+math.randomRange(-48,48),y2+math.randomRange(-48,48))
BlzSetSpecialEffectScale(cell.foliage,math.randomRange(1,1.2))
BlzSetSpecialEffectYaw(cell.foliage,math.randomRange(0,6.28319))
end
end
end
end
}
end
do
LAVA_GENERATION = {
init = function(N, W, E ,S)
end,
generate = function(DestDeathTrig,orientation_Data,rub_chance)
EnableWeatherEffect(AddWeatherEffect(WorldBounds.r, FourCC('MEla')),true)
EnableWeatherEffect(AddWeatherEffect(WorldBounds.r, FourCC('WNla')),true)
SetDoodadAnimation(0,0,512,FourCC('D000'),false,"death",false)
local offx, offy = math.random(99999),math.random(99999)
local lavaGroup = {}
local lavaReg = CreateRegion()
local lavaPoints = {}
local oldFilter = CorruptionFilter
function CorruptionFilter(cell)
return oldFilter(cell) and not cell.lava
end
local function CreateTerrain(x,y, scale, cell)
local r = Noise.perlin2D ((x+offx)*scale, (y+offy)*scale)
if r < -.3 and not cell.alive and not cell.aliveNew then
cell.lava = true
MoveRectTo(tempRect,x, y)
RegionAddRect(lavaReg,tempRect)
SetTerrainType(x, y, TILE_ID_ALPHA, -1, 1, 1)
table.insert(lavaPoints,Vector(x,y))
elseif r < 0.2 then
SetTerrainType(x-cell_half, y-cell_half, FourCC("Bflr"), -1, 1, 1)
else
SetTerrainType(x-cell_half, y-cell_half, FourCC("Bdsd"), -1, 1, 1)
end
end
SetSkyModel("")
SetTerrainType(0, 0, FourCC('Bflr'), -1, 9999, 1)
AddActionInTimerQueue(1,function()
local vec = lavaPoints[math.random(#lavaPoints)]
AddEffectDestroyQueue(AddSpecialEffect("Doodads\\Cinematic\\FireTrapUp\\FireTrapUp",vec.x,vec.y),3)
end)
function lava_damage()
for i = 1, #lavaGroup do
local u = lavaGroup[i]
local udata = UnitData[GetUnitUserData(u)]
if not udata.skipLava then
UnitDamageTarget(u,u,BlzGetUnitMaxHP(u)*.02,false,false,ATTACK_TYPE_MAGIC,DAMAGE_TYPE_FIRE,WEAPON_TYPE_WHOKNOWS)
else
udata.skipLava = udata.skipLava - 1
if udata.skipLava == 0 then
udata.skipLava = nil
end
end
end
if #lavaGroup == 0 then
RemoveActionInTimerQueue()
end
end
function lava_enter(u,inside)
local udata = UnitData[GetUnitUserData(u)]
if not udata.waveType then
if inside then
if not udata.lavaFireSFX then
if #lavaGroup == 0 then
AddActionInTimerQueue(1,lava_damage)
end
AddUnitToSynthGroup(u,lavaGroup)
if IsUnitType(u,UNIT_TYPE_STRUCTURE) then
udata.lavaFireSFX = AttachUnitEffect(u,AddSpecialEffect("Environment\\LargeBuildingFire\\LargeBuildingFire1",GetUnitX(u),GetUnitY(u)))
udata.skipLava = 5
else
udata.lavaFireSFX = AttachUnitEffect(u,AddSpecialEffectTarget("Environment\\LargeBuildingFire\\LargeBuildingFire2",u,"origin"))
end
end
elseif udata.lavaFireSFX then
RemoveUnitEffect(u,udata.lavaFireSFX)
udata.lavaFireSFX = nil
RemoveUnitFromSynthGroup(u,lavaGroup)
if #lavaGroup == 0 then
RemoveActionInTimerQueue(1,lava_damage)
end
end
end
end
local t = CreateTrigger()
TriggerAddAction(t,function()
lava_enter(GetTriggerUnit(),true)
end)
local t2 = CreateTrigger()
TriggerAddAction(t2,function()
lava_enter(GetTriggerUnit(),false)
end)
TriggerRegisterEnterRegion(t,lavaReg,nil)
TriggerRegisterLeaveRegion(t2,lavaReg,nil)
for x = 1, ARENA_LENGTH, 1 do
for y = 1, ARENA_WIDTH, 1 do
local x2, y2 = Cell2Cord(x,y)
local cell = Cell_Data[x][y]
if not (x2 < 512 and x2 > -512 and y2 < 512 and y2 > -512) then
local barrier = x == 1 or y == 1 or x == ARENA_LENGTH or y == ARENA_WIDTH
if barrier then
cell.alive = true
SetDestructableInvulnerable(CreateDestructable(FourCC('B00D'),x2,y2,math.random()*360,.6,math.random(10)),true)
elseif not cell.cross then
if cell.alive then
local d
if math.random() < .5 then
d = CreateDestructable(FourCC('B00D'),x2,y2,math.random()*360,.5,math.random(10))
else
d = CreateDestructable(FourCC('B00G'),x2,y2,math.random()*360,1,math.random(10))
end
DestructableDeathEffect[d] = "Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust"
TriggerRegisterDeathEvent(DestDeathTrig,d)
else
local nbs = GetCellNeighbors(x,y)
cell.aliveNew = math.random() < rub_chance and nbs > 0
if cell.aliveNew then
local d
if math.random() < .3 then
d = CreateDestructable(FourCC('B00C'),x2,y2,math.random()*360,.75,math.random(5))
else
d = CreateDestructable(FourCC('B00F'),x2,y2,math.random()*360,1,math.random(6))
end
DestructableDeathEffect[d] = "Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust"
TriggerRegisterDeathEvent(DestDeathTrig,d)
elseif math.random() < 0.025 * nbs and IsPlusOrMinus(cell_half,x,cross_cut_size+5) and IsPlusOrMinus(cell_half,y,cross_cut_size+5)then
cell.aliveNew = true
CreateRandomMineral(x2,y2)
else
cell.aliveNew = math.random() < rub_chance*.125 and nbs == 0
if cell.aliveNew then
CreateDestructable(FourCC('B00E'),x2,y2,math.random()*360,.75,math.random(5))
end
end
CreateTerrain(x2,y2, 0.002,cell)
end
end
else
CreateTerrain(x2,y2, 0.002,cell)
cell.alive = false
end
end
end
end
}
end
do
MOON_GENERATION = {
init = function(N, W, E ,S)
end,
generate = function(DestDeathTrig,orientation_Data,rub_chance)
local offx, offy = math.random(99999),math.random(99999)
local moonRocks = {FourCC('B004'),FourCC('B00K'),FourCC('B00L'),FourCC('B00M'),FourCC('B00N'),FourCC('B00O'),FourCC('B00P'),FourCC('B00Q'),FourCC('B00R')}
local bigMoonRocks = {FourCC('B00U'),FourCC('B00V'),FourCC('B00W'),FourCC('B00X'),FourCC('B00Y'),FourCC('B00Z'),FourCC('B010'),FourCC('B011'),FourCC('B012')}
SetTerrainType(0, 0, FourCC('Zgrs'), -1, 9999, 1)
EnableWeatherEffect(AddWeatherEffect(WorldBounds.r, FourCC('WOln')),true)
for x = 1, ARENA_LENGTH, 1 do
for y = 1, ARENA_WIDTH, 1 do
local x2, y2 = Cell2Cord(x,y)
local cell = Cell_Data[x][y]
if not (x2 < 256 and x2 > -256 and y2 < 256 and y2 > -256) then
if not cell.cross then
if cell.alive and not cell.skip then
if math.random() < .05 and x < ARENA_LENGTH and y < ARENA_WIDTH then
local cell2 = Cell_Data[x+1][y]
local cell3 = Cell_Data[x+1][y+1]
local cell4 = Cell_Data[x][y+1]
if not cell2.skip and not cell3.skip and not cell4.skip and math.random() < .2 then
cell2.skip = true
cell3.skip = true
cell4.skip = true
cell2.alive = true
cell3.alive = true
cell4.alive = true
local x2,y2 = x2 + 64, y2 + 64
if math.random() < .5 then
CreateDestructable(FourCC('B00S'),x2,y2,math.random()*360,1,0)
else
CreateDestructableZ(bigMoonRocks[math.random(#bigMoonRocks)],x2,y2,math.randomRange(-64,0),math.random()*360,math.randomRange(1.5,2.2),0)
end
else
CreateDestructable(FourCC('B00T'),x2,y2,math.random()*360,.5,0)
end
else
local d = CreateDestructableZ(moonRocks[math.random(#moonRocks)],x2,y2,math.randomRange(-64,0),math.random()*360,math.randomRange(.65,1.1),0)
DestructableDeathEffect[d] = "Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust"
TriggerRegisterDeathEvent(DestDeathTrig,d)
end
elseif math.random() < 0.012 * GetCellNeighbors(x,y) and IsPlusOrMinus(cell_half,x,cross_cut_size+5) and IsPlusOrMinus(cell_half,y,cross_cut_size+5)then
cell.aliveNew = true
CreateRandomMineral(x2,y2)
end
end
else
cell.alive = false
end
end
end
end
}
end
do
STATION_GENERATION = {
init = function(N, W, E ,S)
end,
generate = function(DestDeathTrig,orientation_Data,rub_chance)
local offx, offy = math.random(99999),math.random(99999)
local function CreateTerrain(x,y, scale)
local r = Noise.perlin2D ((x+offx)*scale, (y+offy)*scale)
if r < 0 then
SetTerrainType(x-cell_half, y-cell_half, FourCC('Bflr'), -1, 1, 1)
else
SetTerrainType(x-cell_half, y-cell_half, FourCC( 'Bdsr'), -1, 1, 1)
end
end
for x = 1, ARENA_LENGTH, 1 do
for y = 1, ARENA_WIDTH, 1 do
local x2, y2 = Cell2Cord(x,y)
local cell = Cell_Data[x][y]
local barrier = x == 1 or y == 1 or x == ARENA_LENGTH or y == ARENA_WIDTH
cell.alive = cell.alive and not barrier
if cell.alive then
CreateDestructableZ(FourCC('B014'),x2,y2,-127,0,1,0)
else
SetTerrainType(x2, y2, FourCC('Itbk'), -1, 1, 1)
end
cell.alive = not cell.alive
end
end
for x = 1, ARENA_LENGTH, 1 do
for y = 1, ARENA_WIDTH, 1 do
local x2, y2 = Cell2Cord(x,y)
local cell = Cell_Data[x][y]
local n = GetCellNeighbors(x,y)
if not cell.alive then
cell.alive = GetRandomReal(0.0,1.0) < 0.025
if cell.alive then
TriggerRegisterDeathEvent(DestDeathTrig,CreateDestructable(FourCC("LTrc"),x2,y2,math.random()*360,.8,math.random(5)))
elseif GetRandomReal(0.0,1.0) < 0.02 * n then
CreateRandomMineral(x2,y2)
end
elseif n < 8 then
CreateDestructable(FourCC('B013'),x2,y2,0,1,0)
end
end
end
end
}
end
do
Planet = {
name = "",
description = "",
difficulty = "",
model = "",
scale = 1,
}
Planet_Planets = {}
Planet_PlanetsFX = false
function Planet:new(tbl)
tbl = tbl or {}
setmetatable(tbl,self)
self.__index = self
table.insert(Planet_Planets,tbl)
return tbl
end
Planet:new({
name = "Xeros 9LV",
description = "A planet with a temple-like structure. Lots of minerals and debris; not suitable for biological life.",
difficulty = "Easy",
model = "PlanetHard.mdx",
scale = 15,
nights = 30,
callback = function()
DungeonStartGen(.62,.15,4,6,3,TEMPLE_GENERATION)
end
})
Planet:new({
name = "Chypso 2RV",
description = "A harsh environment for biological life. The surface has very little water and the atmosphere is thin.",
difficulty = "Medium",
model = "PlanetMedium.mdx",
scale = 15,
nights = 40,
callback = function()
DungeonStartGen(.3,.15,3,2,4,DESERT_GENERATION)
end
})
Planet:new({
name = "Terra Flora X-6",
description = "A lush planet with vegetation, suitable for biological life.",
difficulty = "Hard",
model = "PlanetEasy.mdx",
scale = 15,
nights = 50,
callback = function()
DungeonStartGen(.77,.35,5,6,5,FOREST_GENERATION)
end
})
Planet:new({
name = "Tar Hades VL7",
description = "A volcanic planet covered in soot, ash and death.",
difficulty = "Medium",
model = "PlanetMedium2.mdx",
scale = 15,
nights = 35,
condition = function()
return Player2Data(HOST_PLAYER).GamesPlayed >= 10, "Play 10 games."
end,
callback = function()
DungeonStartGen(.5,.15,2,4,4,LAVA_GENERATION)
end
})
Planet:new({
name = "Epsilon 5L",
description = "A moon of Terra Flora X-6, not suitable for biological life with sparse debris.",
difficulty = "Brutal",
model = "PlanetLunar.mdx",
scale = 14,
nights = 50,
condition = function()
return Achievement.has(Player2Data(HOST_PLAYER),Achievement["Planetary Defender"]), "Unlock the achievement Planetary Defender."
end,
callback = function()
DungeonStartGen(.14,.15,4,0,4,MOON_GENERATION)
Adaptation_Speed = true
end
})
Planet:new({
name = "Aurora Proxima Relay",
description = "A long-abandoned orbital relay station, drifting near the Proxima star, its corridors echoing with the hum of ancient machinery and the silence of lost transmissions.",
difficulty = "Brutal",
model = "SpaceStation_v1.mdx",
scale = 2.5,
nights = 35,
condition = function()
return Achievement.has(Player2Data(HOST_PLAYER),Achievement["Planetary Defender"]), "Unlock the achievement Planetary Defender."
end,
callback = function()
local function distCalc(x,y)
local d = (x * x + y * y)/(ARENA_SIZE * ARENA_SIZE)
return 1.0 - d
end
DungeonStartGen(distCalc,.0,5,7,4,STATION_GENERATION)
end
})
Planet_dir = 1
function RotateCamera()
local rot = GetCameraField(CAMERA_FIELD_ROTATION) *bj_RADTODEG
if rot > 154 or rot < 60 then
Planet_dir = -Planet_dir
end
AdjustCameraField(CAMERA_FIELD_ROTATION,.025*Planet_dir,.1)
SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, 6000, 0.1)
SetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK,300,0.1)
SetCameraField(CAMERA_FIELD_FARZ, 30000, 0.1)
PanCameraToTimed(0,0,.1)
end
function FinishPlanet()
BlzFrameSetVisible(PerkSelectionBackDrop,false)
SetCameraField(CAMERA_FIELD_ROTATION,90,0)
SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, 6000, 0)
SetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK,300,0)
SetCameraField(CAMERA_FIELD_FARZ, 30000, 0)
PLANET_MAX_NIGHTS = Planet_Planets[Planet_CurrentPlanet].nights
for i = 1, #PlayerUsers do
local pdata = Player2Data(PlayerUsers[i])
if pdata.PerksSelected then
for j = 1, #pdata.PerksSelected do
pdata.PerksSelected[j].action(pdata)
end
end
end
Planet_Planets[Planet_CurrentPlanet].callback()
MAX_NIGHTS = Planet_Planets[Planet_CurrentPlanet].nights
RemoveActionInTimerQueue(.1,RotateCamera)
PauseTimer(Planet_Timer)
DestroyEffect(Planet_PlanetsFX)
BlzSetSpecialEffectZ(Planet_PlanetsFX,-99999)
TimerStart(NewTimer(),1,false,function()
InitGameStart()
SuspendTimeOfDay(false)
ReleaseTimer(GetExpiredTimer())
end)
end
function GetPlanet()
return Planet_Planets[Planet_CurrentPlanet]
end
function ShowPlanet(planet)
if Planet_PlanetsFX then
DestroyEffect(Planet_PlanetsFX)
BlzSetSpecialEffectZ(Planet_PlanetsFX,-99999)
PauseTimer(Planet_Timer)
end
Planet_PlanetsFX = AddSpecialEffect(planet.model,0,0)
BlzSetSpecialEffectScale(Planet_PlanetsFX,0)
BlzSetSpecialEffectYaw(Planet_PlanetsFX,4.71239)
BlzSetSpecialEffectTimeScale(Planet_PlanetsFX,.1)
local curTime = 0
local maxTime = 1
TimerStart(Planet_Timer,0.01,true,function()
curTime = math.min(curTime + 0.01,maxTime)
if curTime == maxTime then
PauseTimer(Planet_Timer)
end
BlzSetSpecialEffectScale(Planet_PlanetsFX,lerp(easeInExpo(curTime/maxTime),0,planet.scale))
end)
local cond, msg = true, ""
if planet.condition then
cond, msg = planet.condition()
end
if cond then
BlzFrameSetText(Planet_TextFrame,string.format("|cffc2f3ff"..Planet_selectText.."Difficulty: %%s\nName: %%s\nSurvival Time: %%s nights\nDescription: %%s|r",
planet.difficulty,
planet.name,
planet.nights,
planet.description
))
BlzFrameSetVisible(Planet_BackdropPlanet_SelectConfirm,true)
else
BlzFrameSetVisible(Planet_BackdropPlanet_SelectConfirm,false)
BlzSetSpecialEffectColor(Planet_PlanetsFX,0,0,0)
BlzFrameSetText(Planet_TextFrame,string.format("|cffc2f3ff"..Planet_selectText.."Locked: %%s\nDifficulty: %%s\nName: %%s\nSurvival Time: %%s nights\nDescription: %%s|r",
msg,
"???",
"???",
"???",
planet.description
))
end
end
function InitPlanetSelection()
--ClearTextMessages()
Planet_Timer = NewTimer()
Planet_CurrentPlanet = 1
Planet_selectText = Player2Data(HOST_PLAYER).name.." is selecting the target.\n\n"
CinematicFadeCommonBJ(0, 0, 0, 5, "ReplaceableTextures\\CameraMasks\\White_mask", 0, 100)
SetCameraPosition(0,0)
SetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK,334,0)
SetSkyModel("GameSky2")
AddActionInTimerQueue(.1,RotateCamera)
Planet_SelectConfirm = BlzCreateFrame("IconButtonTemplate", BlzGetOriginFrame(ORIGIN_FRAME_WORLD_FRAME, 0), 0, 0)
BlzFrameSetAbsPoint(Planet_SelectConfirm, FRAMEPOINT_TOPLEFT, 0.303760, 0.0707180)
BlzFrameSetAbsPoint(Planet_SelectConfirm, FRAMEPOINT_BOTTOMRIGHT, 0.503780, 0.00000)
Planet_BackdropPlanet_SelectConfirm = BlzCreateFrameByType("BACKDROP", "Planet_BackdropPlanet_SelectConfirm", Planet_SelectConfirm, "", 1)
BlzFrameSetAllPoints(Planet_BackdropPlanet_SelectConfirm, Planet_SelectConfirm)
BlzFrameSetTexture(Planet_BackdropPlanet_SelectConfirm, "UISelectionConfirm.tga", 0, true)
local t = CreateTrigger()
BlzTriggerRegisterFrameEvent(t, Planet_SelectConfirm, FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(t, function()
BlzFrameSetEnable(Planet_SelectConfirm, false)
BlzFrameSetVisible(Planet_SelectConfirm,false)
BlzFrameSetVisible(Planet_TextFrame,false)
CinematicFadeCommonBJ(0, 0, 0, .5, "ReplaceableTextures\\CameraMasks\\White_mask", 100, 0)
InitPerkSelection()
end)
Planet_SelectLeft = BlzCreateFrame("IconButtonTemplate", Planet_SelectConfirm, 0, 0)
BlzFrameSetAbsPoint(Planet_SelectLeft, FRAMEPOINT_TOPLEFT, 0.000412250, 0.391530)
BlzFrameSetAbsPoint(Planet_SelectLeft, FRAMEPOINT_BOTTOMRIGHT, 0.100422, 0.291530)
BlzFrameSetTexture(Planet_SelectLeft, "UISelectionArrowLeft.dds", 0, true)
local BackdropPlanet_SelectLeft = BlzCreateFrameByType("BACKDROP", "BackdropPlanet_SelectLeft", Planet_SelectLeft, "", 1)
BlzFrameSetAllPoints(BackdropPlanet_SelectLeft, Planet_SelectLeft)
BlzFrameSetTexture(BackdropPlanet_SelectLeft, "UISelectionArrowLeft.dds", 0, true)
t = CreateTrigger()
BlzTriggerRegisterFrameEvent(t, Planet_SelectLeft, FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(t, function()
BlzFrameSetEnable(Planet_SelectLeft, false)
BlzFrameSetEnable(Planet_SelectLeft, true)
Planet_CurrentPlanet = Planet_CurrentPlanet - 1
if Planet_CurrentPlanet == 0 then
Planet_CurrentPlanet = #Planet_Planets
end
BlzFrameSetEnable(Planet_SelectConfirm, Planet_Planets[Planet_CurrentPlanet].callback)
ShowPlanet(Planet_Planets[Planet_CurrentPlanet])
end)
Planet_SelectRight = BlzCreateFrame("IconButtonTemplate", Planet_SelectConfirm, 0, 0)
BlzFrameSetAbsPoint(Planet_SelectRight, FRAMEPOINT_TOPLEFT, 0.700490, 0.391530)
BlzFrameSetAbsPoint(Planet_SelectRight, FRAMEPOINT_BOTTOMRIGHT, 0.800500, 0.291530)
local BackdropPlanet_SelectRight = BlzCreateFrameByType("BACKDROP", "BackdropPlanet_SelectRight", Planet_SelectRight, "", 1)
BlzFrameSetAllPoints(BackdropPlanet_SelectRight, Planet_SelectRight)
BlzFrameSetTexture(BackdropPlanet_SelectRight, "UISelectionArrowRight.dds", 0, true)
t = CreateTrigger()
BlzTriggerRegisterFrameEvent(t, Planet_SelectRight, FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(t, function()
BlzFrameSetEnable(Planet_SelectRight, false)
BlzFrameSetEnable(Planet_SelectRight, true)
Planet_CurrentPlanet = Planet_CurrentPlanet + 1
if Planet_CurrentPlanet > #Planet_Planets then
Planet_CurrentPlanet = 1
end
BlzFrameSetEnable(Planet_SelectConfirm, Planet_Planets[Planet_CurrentPlanet].callback)
ShowPlanet(Planet_Planets[Planet_CurrentPlanet])
end)
Planet_TextFrame = BlzCreateFrameByType("TEXT", "name", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), "", 0)
BlzFrameSetAbsPoint(Planet_TextFrame, FRAMEPOINT_TOPLEFT, 0.0104122, 0.275691)
BlzFrameSetAbsPoint(Planet_TextFrame, FRAMEPOINT_BOTTOMRIGHT, 0.389462, 0.0712710)
BlzFrameSetEnable(Planet_TextFrame, false)
BlzFrameSetScale(Planet_TextFrame, 1.71)
BlzFrameSetTextAlignment(Planet_TextFrame, TEXT_JUSTIFY_TOP, TEXT_JUSTIFY_LEFT)
BlzFrameSetVisible(Planet_SelectConfirm,LocalPlayer == HOST_PLAYER)
BlzFrameSetVisible(Planet_TextFrame,true)
ShowPlanet(Planet_Planets[Planet_CurrentPlanet])
end
end
do
WARP_VORTEX_CHANCE = 30
Perk_frameTooltips = {}
Perk_skinsUsed = 0
Perk_perksUsed = 0
Perk_SkinButtons = {}
Perk_PerkButtons = {}
GemformerBonus = 0
Perk_SelectTimer = false
function UpdateWaitingFor()
local s = "|cff70fffdWaiting For:\n"
local stillWaiting = false
for i = 1, #PlayerUsers do
local pdata = Player2Data(PlayerUsers[i])
if not pdata.Perk_ConfirmedPerks then
stillWaiting = true
s = PlayerDataLocal == pdata and s.."You, " or s..pdata.name.. ", "
end
end
BlzFrameSetText(Perk_WaitingText,string.sub(s,1,#s-2))
if not stillWaiting then
if Perk_SelectTimer then
ReleaseTimer(Perk_SelectTimer)
end
FinishPlanet()
return false
end
return true
end
function PerkPointGain(pdata,msg,amount)
local s = amount == 1 and "a perk point" or amount.." perk points"
DisplayTextToPlayer(pdata.p,0,0,"|cff00c800You have gained "..s.." for "..msg..".|r")
pdata.PerkPoints = pdata.PerkPoints + amount
pdata.SpendPerks = pdata.SpendPerks + amount
end
function PerkPointGainEveryone(msg,amount)
for i = 1, #PlayerUsers do
PerkPointGain(Player2Data(PlayerUsers[i]),msg,amount)
end
end
function Perk_FrameAddTooltip(frame, title, description)
Perk_frameTooltips[frame] = Perk_frameTooltips[frame] or {}
Perk_frameTooltips[frame].title = title
Perk_frameTooltips[frame].description = description
BlzTriggerRegisterFrameEvent(Perk_tooltipTrigger, frame, FRAMEEVENT_MOUSE_ENTER)
BlzTriggerRegisterFrameEvent(Perk_tooltipTrigger, frame, FRAMEEVENT_MOUSE_LEAVE)
BlzFrameSetVisible(frame,true)
end
function Perk_AddSkin(icon, skinId, title, description, condition)
Perk_skinsUsed = Perk_skinsUsed + 1
Perk_FrameAddTooltip(Perk_SkinButtons[Perk_skinsUsed], title, description)
Perk_frameTooltips[Perk_SkinButtons[Perk_skinsUsed]].skinId = skinId
local child = BlzFrameGetChild(Perk_SkinButtons[Perk_skinsUsed],0)
BlzFrameSetTexture(child,icon,0,true)
if not condition or condition() then
else
BlzFrameSetEnable(Perk_SkinButtons[Perk_skinsUsed],false)
BlzFrameSetAlpha(Perk_SkinButtons[Perk_skinsUsed],100)
end
end
function Perk_AddPerk(icon, title, description, cost, condition, action)
Perk_perksUsed = Perk_perksUsed + 1
Perk_FrameAddTooltip(Perk_PerkButtons[Perk_perksUsed], title.."\nPerk Cost: "..cost, description)
local data = Perk_frameTooltips[Perk_PerkButtons[Perk_perksUsed]]
data.cost = cost
data.condition = condition
data.action = action
data.icon = icon
BlzFrameSetTexture(BlzFrameGetChild(Perk_PerkButtons[Perk_perksUsed],0),icon,0,true)
if not condition or condition() then
else
BlzFrameSetEnable(Perk_PerkButtons[Perk_perksUsed],false)
BlzFrameSetAlpha(Perk_PerkButtons[Perk_perksUsed],100)
end
end
function InitPerkSelection()
Perk_tooltipTrigger = CreateTrigger()
TriggerAddAction(Perk_tooltipTrigger,function()
local f = BlzGetTriggerFrame()
if LocalPlayer == GetTriggerPlayer() then
if BlzGetTriggerFrameEvent() == FRAMEEVENT_MOUSE_ENTER then
BlzFrameSetText(Perk_TooltipText,"|cff70fffd"..Perk_frameTooltips[f].title.."\n\n"..Perk_frameTooltips[f].description.."|r")
else
BlzFrameSetText(Perk_TooltipText,"")
end
end
end)
PerkSelectionBackDrop = BlzCreateFrameByType("BACKDROP", "PerkSelectionBackDrop", BlzGetOriginFrame(ORIGIN_FRAME_WORLD_FRAME, 0), "", 1)
BlzFrameSetAbsPoint(PerkSelectionBackDrop, FRAMEPOINT_TOPLEFT, 0.110370, 0.601100)
BlzFrameSetAbsPoint(PerkSelectionBackDrop, FRAMEPOINT_BOTTOMRIGHT, 0.710440, 0.00000)
BlzFrameSetTexture(PerkSelectionBackDrop, "UIPannelPerkSelection.dds", 0, true)
PerkSelectionText = BlzCreateFrameByType("TEXT", "name", PerkSelectionBackDrop, "", 0)
BlzFrameSetAbsPoint(PerkSelectionText, FRAMEPOINT_TOPLEFT, 0.286630, 0.592009)
BlzFrameSetAbsPoint(PerkSelectionText, FRAMEPOINT_BOTTOMRIGHT, 0.524780, 0.534550)
BlzFrameSetText(PerkSelectionText, "|cff70fffdPerk Selection|r")
BlzFrameSetEnable(PerkSelectionText, false)
BlzFrameSetScale(PerkSelectionText, 3)
BlzFrameSetTextAlignment(PerkSelectionText, TEXT_JUSTIFY_CENTER, TEXT_JUSTIFY_MIDDLE)
local BackDropSkins = BlzCreateFrameByType("BACKDROP", "BackDropSkins", PerkSelectionBackDrop, "", 1)
BlzFrameSetAbsPoint(BackDropSkins, FRAMEPOINT_TOPLEFT, 0.141310, 0.520440)
BlzFrameSetAbsPoint(BackDropSkins, FRAMEPOINT_BOTTOMRIGHT, 0.365640, 0.403310)
BlzFrameSetTexture(BackDropSkins, "UIPannelSkins.dds", 0, true)
Perk_WaitingText = BlzCreateFrameByType("TEXT", "name", BackDropSkins, "", 0)
BlzFrameSetAbsPoint(Perk_WaitingText, FRAMEPOINT_TOPLEFT, 0.134130, 0.0618780)
BlzFrameSetAbsPoint(Perk_WaitingText, FRAMEPOINT_BOTTOMRIGHT, 0.555720, 0.0209940)
BlzFrameSetEnable(Perk_WaitingText, false)
BlzFrameSetScale(Perk_WaitingText, 1.00)
BlzFrameSetTextAlignment(Perk_WaitingText, TEXT_JUSTIFY_CENTER, TEXT_JUSTIFY_MIDDLE)
UpdateWaitingFor()
Perk_Confirm = BlzCreateFrame("IconButtonTemplate", PerkSelectionBackDrop, 0, 0)
BlzFrameSetAbsPoint(Perk_Confirm, FRAMEPOINT_TOPLEFT, 0.557930, 0.583354)
BlzFrameSetAbsPoint(Perk_Confirm, FRAMEPOINT_BOTTOMRIGHT, 0.689440, 0.542470)
local BackdropPerk_Confirm = BlzCreateFrameByType("BACKDROP", "BackdropPerk_Confirm", Perk_Confirm, "", 1)
BlzFrameSetAllPoints(BackdropPerk_Confirm, Perk_Confirm)
BlzFrameSetTexture(BackdropPerk_Confirm, "UISelectionConfirm.dds", 0, true)
local t = CreateTrigger()
BlzTriggerRegisterFrameEvent(t, Perk_Confirm, FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(t, function()
local p = GetTriggerPlayer()
BlzFrameSetEnable(Perk_Confirm,false)
BlzFrameSetEnable(Perk_Confirm,true)
if LocalPlayer == p then
BlzFrameSetVisible(Perk_Confirm,false)
for i = 1, #Perk_SkinButtons do
BlzFrameSetEnable(Perk_SkinButtons[i],false)
end
for i = 1, #Perk_PerkButtons do
BlzFrameSetEnable(Perk_PerkButtons[i],false)
end
end
Player2Data(p).Perk_ConfirmedPerks = true
if UpdateWaitingFor() and not Perk_SelectTimer then
local countdown = 60
Perk_SelectTimer = NewTimer()
TimerStart(Perk_SelectTimer,1,true,function()
BlzFrameSetText(PerkSelectionText, string.format("|cff70fffdPerk Selection (%%d)|r",
countdown
))
if countdown == 0 then
FinishPlanet()
end
countdown = countdown - 1
end)
end
end)
local PerkSkinsText = BlzCreateFrameByType("TEXT", "name", BackDropSkins, "", 0)
BlzFrameSetAbsPoint(PerkSkinsText, FRAMEPOINT_TOPLEFT, 0.140760, 0.550279)
BlzFrameSetAbsPoint(PerkSkinsText, FRAMEPOINT_BOTTOMRIGHT, 0.365650, 0.512710)
BlzFrameSetText(PerkSkinsText, "|cff70fffdSkins|r")
BlzFrameSetEnable(PerkSkinsText, false)
BlzFrameSetScale(PerkSkinsText, 2.43)
BlzFrameSetTextAlignment(PerkSkinsText, TEXT_JUSTIFY_CENTER, TEXT_JUSTIFY_MIDDLE)
local Tooltipbackdrop = BlzCreateFrameByType("BACKDROP", "Tooltipbackdrop", PerkSelectionBackDrop, "", 1)
BlzFrameSetAbsPoint(Tooltipbackdrop, FRAMEPOINT_TOPLEFT, 0.481680, 0.166853)
BlzFrameSetAbsPoint(Tooltipbackdrop, FRAMEPOINT_BOTTOMRIGHT, 0.688330, 0.0629830)
BlzFrameSetTexture(Tooltipbackdrop, "UIPannelSkins.dds", 0, true)
Perk_TooltipText = BlzCreateFrameByType("TEXT", "name", Tooltipbackdrop, "", 0)
BlzFrameSetAbsPoint(Perk_TooltipText, FRAMEPOINT_TOPLEFT, 0.490520, 0.160773)
BlzFrameSetAbsPoint(Perk_TooltipText, FRAMEPOINT_BOTTOMRIGHT, 0.682250, 0.0696130)
BlzFrameSetEnable(Perk_TooltipText, false)
BlzFrameSetScale(Perk_TooltipText, 1.00)
BlzFrameSetTextAlignment(Perk_TooltipText, TEXT_JUSTIFY_TOP, TEXT_JUSTIFY_LEFT)
local sizeX = 0.179988 - 0.150150 + .005
local sizeY = 0.509834 - 0.480000 + .005
t = CreateTrigger()
for y = 0, 2 do
for x = 0, 5 do
local SkinButton = BlzCreateFrame("IconButtonTemplate", BackDropSkins, 0, 0)
BlzFrameSetAbsPoint(SkinButton, FRAMEPOINT_TOPLEFT, 0.150150+sizeX*x, 0.509834-sizeY*y)
BlzFrameSetAbsPoint(SkinButton, FRAMEPOINT_BOTTOMRIGHT, 0.179988+sizeX*x, 0.480000-sizeY*y)
local BackdropSkinButton = BlzCreateFrameByType("BACKDROP", "BackdropSkinButton1", SkinButton, "", 1)
BlzFrameSetAllPoints(BackdropSkinButton, SkinButton)
BlzTriggerRegisterFrameEvent(t, SkinButton, FRAMEEVENT_CONTROL_CLICK)
BlzFrameSetVisible(SkinButton,false)
table.insert(Perk_SkinButtons,SkinButton)
end
end
local model = BlzCreateFrameByType("SPRITE", "SpriteName", BackDropSkins, "", 0)
BlzFrameSetAllPoints(model, Perk_SkinButtons[1])
BlzFrameSetModel(model, "UI\\Feedback\\Autocast\\UI-ModalButtonOn.mdl", 0)
BlzFrameSetScale(model, BlzFrameGetWidth(Perk_SkinButtons[1])/0.039)
TriggerAddAction(t, function()
local p = GetTriggerPlayer()
local fh = BlzGetTriggerFrame()
if LocalPlayer == p then
BlzFrameSetEnable(fh,false)
BlzFrameSetEnable(fh,true)
BlzFrameSetAllPoints(model, fh)
end
if Perk_frameTooltips[fh].skinId then
Player2Data(p).skinId = Perk_frameTooltips[fh].skinId
end
end)
local PerkPerkBackdrop = BlzCreateFrameByType("BACKDROP", "PerkPerkBackdrop", PerkSelectionBackDrop, "", 1)
BlzFrameSetAbsPoint(PerkPerkBackdrop, FRAMEPOINT_TOPLEFT, 0.115890, 0.378)
BlzFrameSetAbsPoint(PerkPerkBackdrop, FRAMEPOINT_BOTTOMRIGHT, 0.704350, 0.171)
BlzFrameSetTexture(PerkPerkBackdrop, "UIPannelPerkSelection.dds", 0, true)
local function UpdatePerk_PerkText()
BlzFrameSetText(Perk_PerkText, "|cff70fffdPerks: "..PlayerDataLocal.SpendPerks.."/"..PlayerDataLocal.PerkPoints.."|r")
end
Perk_PerkText = BlzCreateFrameByType("TEXT", "name", PerkPerkBackdrop, "", 0)
BlzFrameSetAbsPoint(Perk_PerkText, FRAMEPOINT_TOPLEFT, 0.292160, 0.399159)
BlzFrameSetAbsPoint(Perk_PerkText, FRAMEPOINT_BOTTOMRIGHT, 0.517050, 0.361590)
UpdatePerk_PerkText()
BlzFrameSetEnable(Perk_PerkText, false)
BlzFrameSetScale(Perk_PerkText, 2.43)
BlzFrameSetTextAlignment(Perk_PerkText, TEXT_JUSTIFY_CENTER, TEXT_JUSTIFY_MIDDLE)
sizeX = 0.183854 - 0.144070 + .005
sizeY = 0.361561 - 0.321230 + .005
t = CreateTrigger()
for y = 0, 3 do
for x = 0, 11 do
local PerkButton = BlzCreateFrame("IconButtonTemplate", PerkPerkBackdrop, 0, 0)
BlzFrameSetAbsPoint(PerkButton, FRAMEPOINT_TOPLEFT, 0.144070+sizeX*x, 0.361561-sizeY*y)
BlzFrameSetAbsPoint(PerkButton, FRAMEPOINT_BOTTOMRIGHT, 0.183854+sizeX*x, 0.321230-sizeY*y)
local BackdropPerkButton = BlzCreateFrameByType("BACKDROP", "BackdropPerkButton", PerkButton, "", 1)
BlzFrameSetAllPoints(BackdropPerkButton, PerkButton)
BlzTriggerRegisterFrameEvent(t, PerkButton, FRAMEEVENT_CONTROL_CLICK)
BlzFrameSetVisible(PerkButton,false)
table.insert(Perk_PerkButtons,PerkButton)
end
end
TriggerAddAction(t, function()
local p = GetTriggerPlayer()
local isLocal = LocalPlayer == p
local fh = BlzGetTriggerFrame()
local data = Perk_frameTooltips[fh]
local pdata = Player2Data(p)
pdata.PerksSelected = pdata.PerksSelected or {}
if isLocal then
BlzFrameSetEnable(fh,false)
BlzFrameSetEnable(fh,true)
end
fh = BlzFrameGetChild(fh,0)
if pdata.PerksSelected[data] then
if isLocal then
BlzFrameSetTexture(fh,data.icon,0,true)
end
table.removeElement(pdata.PerksSelected, data)
pdata.SpendPerks = pdata.SpendPerks + data.cost
pdata.PerksSelected[data] = nil
elseif pdata.SpendPerks >= data.cost then
if isLocal then
BlzFrameSetTexture(fh,"ReplaceableTextures\\CommandButtons\\BTNCancel",0,true)
end
pdata.PerksSelected[data] = true
table.insert(pdata.PerksSelected,data)
pdata.SpendPerks = pdata.SpendPerks - data.cost
end
UpdatePerk_PerkText()
end)
Perk_AddSkin("ReplaceableTextures\\CommandButtons\\BTNSynthesizer", FourCC('H00C'), "Default", "This is the default skin for the synthesizer.")
Perk_AddSkin("ReplaceableTextures\\CommandButtons\\BTNPeasant",FourCC('hpea'),"Miner","This skin is unlocked by playing MineralZ",function()
return FileIO_Read("MineralZ".."\\"..PlayerDataLocal.trueName.."\\PlayerData.txt")
end)
Perk_AddSkin("ReplaceableTextures\\CommandButtons\\BTNArmorGolem",FourCC('nwrg'),"Mechanoid","This skin is unlocked by playing 250 games.",function()
return PlayerDataLocal.GamesPlayed >= 250
end)
Perk_AddSkin("ReplaceableTextures\\CommandButtons\\BTNArchon",FourCC('hfoo'),"Archon","This skin is unlocked by killing 10000 Thrul.",function()
return PlayerDataLocal.WaveKills >= 10000
end)
Perk_AddSkin("ReplaceableTextures\\CommandButtons\\BTNZergling",FourCC('zzrg'),"Zergling","This skin is unlocked by getting the achievement Mutation Complete.",function()
return Achievement.has(PlayerDataLocal,Achievement["Mutation Complete"])
end)
Perk_AddSkin("ReplaceableTextures\\CommandButtons\\BTNMarine",FourCC('zcso'),"Space Marine","This skin is unlocked by getting the achievement Planetary Defender.",function()
return Achievement.has(PlayerDataLocal,Achievement["Planetary Defender"])
end)
Perk_AddSkin("ReplaceableTextures\\CommandButtons\\BTNStaffOfNegation",FourCC('hkni'),"Crystaline Synthesizer","This skin is unlocked by getting the achievement Galatic Defender",function()
return Achievement.has(PlayerDataLocal,Achievement["Galatic Defender"])
end)
Perk_AddSkin("ReplaceableTextures\\CommandButtons\\BTNDeepLordRevenant.blp",FourCC('nrvi'),"Lunar Spirit","This skin is unlocked by getting the achievement Lunar Fortress",function()
return Achievement.has(PlayerDataLocal,Achievement["Lunar Fortress"])
end)
Perk_AddPerk("ReplaceableTextures\\CommandButtons\\BTNGatherGold", "True Miner", "Adds +0.5 to the mining rate of all minerals, but increases the initial cost of the power drill by 200%%.", 3, nil, function(pdata)
local pdrill = GetPlayerAbility(pdata.p,AbilityList.pdrill)
for i = 1, #pdrill.cost do
pdrill.cost[i] = math.floor(pdrill.cost[i]*2)
end
for i = 1, #ResourceTypes do
pdata.mineRate[i] = pdata.mineRate[i] + .5
end
end)
Perk_AddPerk("ReplaceableTextures\\CommandButtons\\BTNWhoNeedsLuck", "Who Needs Luck?", "Increases your chances of positive outcomes by 25%%.", 10, nil, function(pdata)
pdata.WhoNeedsLuck = 1.25
end)
Perk_AddPerk("ReplaceableTextures\\CommandButtons\\BTNRevivalPerk", "Revival", "When your synthesizer dies, it will revive the following day. This effect can only happen once.", 20, nil ,function(pdata)
pdata.Revival = true
end)
Perk_AddPerk("ReplaceableTextures\\CommandButtons\\BTNEnchantedGemstone", "Perfect Gems", "When you mine a mineral, it will automatically increase in quality for free. This effect can occur once every 60 seconds.", 5, nil, function(pdata)
pdata.PerfectGems = -60
end)
Perk_AddPerk("ReplaceableTextures\\CommandButtons\\BTNHealingWave", "Precision Lasers", "Your Power Drills deal 75%% less damage to minerals.",10, nil, function(pdata)
pdata.PrecisionLasers = true
end)
Perk_AddPerk("ReplaceableTextures\\CommandButtons\\BTNPhilosophersStone", "Gemformer", "All minerals on the map have their quality increased by 1 to start.", 5, nil, function(pdata)
GemformerBonus = GemformerBonus + 1
end)
Perk_AddPerk("ReplaceableTextures\\CommandButtons\\BTNPurge", "Teleportist", "Your teleporters have 200%% more range.", 5, nil ,function(pdata)
local teleporterPad = GetPlayerAbility(pdata.p,AbilityList.teleporterPad)
teleporterPad.range = teleporterPad.range * 2
end)
Perk_AddPerk("ReplaceableTextures\\CommandButtons\\BTNStoneForm", "Ablative Plating", "Your synthesizer gains 900 hit points that diminishes when damage is taken.", 10, nil, function(pdata)
AttachFunctionPlayerToUnitType(pdata,UNIT_ID_SYNTHESIZER,function(u,udata)
local max = BlzGetUnitMaxHP(u)+900
BlzSetUnitMaxHP(u,max)
SetWidgetLife(u,max)
udata.fragileHP = 900
end)
end)
Perk_AddPerk("ReplaceableTextures\\CommandButtons\\BTNWarpVortex", "Warp Vortex", "Your barriers gain a "..WARP_VORTEX_CHANCE.."%% chance to phase out of attacks, making them miss.", 15, nil,function(pdata)
local abl = FourCC('ACes')
BlzSetAbilityTooltip(abl,SPELL_NAME_COLOR.."Warp Vortex|r",0)
BlzSetAbilityExtendedTooltip(abl,StringToSpellDescription("Attacks against this target have a "..WARP_VORTEX_CHANCE.."%% chance to miss."),0)
BlzSetAbilityIcon(abl,"ReplaceableTextures\\PassiveButtons\\PASWarpVortex")
BlzSetAbilityPosX(abl,1)
BlzSetAbilityPosY(abl,1)
local wall = GetPlayerAbility(pdata.p,AbilityList.wall)
local oldAction = wall.buildAction
wall.buildAction = function(u,udata,bdata)
if oldAction then
oldAction(u,udata,bdata)
end
UnitAddAbility(u,abl)
BlzSetAbilityRealLevelField(BlzGetUnitAbility(u,abl),ABILITY_RLF_CHANCE_TO_EVADE_EEV1,0,WARP_VORTEX_CHANCE*.01)
end
end)
Perk_AddPerk("ReplaceableTextures\\CommandButtons\\BTNGem", "Head Start", "You gain 10 of each mineral when arriving on the planet. But the resource center now costs 5 of each mineral.",20,nil,function(pdata)
local center = GetPlayerAbility(pdata.p,AbilityList.resourceCenter)
for j = 1, #ResourceTypes do
center.cost[j] = 5
AddMinerals(pdata,j,10)
end
end)
Perk_AddPerk("ReplaceableTextures\\CommandButtons\\BTNPrecisionMiningBeam", "Drill Master", "Your power drills gain 50%% additional efficiency (Massive output increase), but you can only upgrade the mineral quantity at the resource center 2 times per mineral type.", 25, nil, function(pdata)
pdata.DrillMaster = true
end)
Perk_AddPerk("ReplaceableTextures\\CommandButtons\\BTNFire", "The Best Defense is Offense", "You can no longer build barriers, but your towers have 200%% additional hit points and 25%% additional damage.\n\nRequires the achievement Who Needs Walls Anyway.", 25,
function()
return Achievement.has(PlayerDataLocal,Achievement["Who Needs Walls Anyway"])
end,
function(pdata)
local wall = GetPlayerAbility(pdata.p,AbilityList.wall)
pdata.TowerDamageBonus = pdata.TowerDamageBonus + .25
pdata.TowerHealthBonus = pdata.TowerHealthBonus + 2.0
local oldAction = wall.gainAction
wall.gainAction = function(u,abl)
if oldAction then
oldAction(u,abl)
end
RemoveUnitAbility(u,abl,true)
end
end)
Perk_AddPerk("ReplaceableTextures\\CommandButtons\\BTNMetalPlatingLevel3", "Ablative Coating", "All of your structures gain 500 hit points that diminishes when damage is taken.",25,nil,function(pdata)
pdata.AlbativeCoating = true
end)
Perk_AddPerk("ReplaceableTextures\\CommandButtons\\BTNBiggerWalls","Bigger Walls", "Your walls are now twice as big and have 250%% more health, but cost 50%% more to create and upgrade.", 30, nil, function(pdata)
local wall = GetPlayerAbility(pdata.p,AbilityList.wall)
wall.scale = wall.scale * 2
wall.buildGrid = BUILD_GRID_2X2
wall.unitid = BUILDING_ID_BIGWALL
pdata.BiggerWalls = true
for i = 1, #wall.cost do
wall.cost[i] = math.floor(wall.cost[i] * 1.5)
end
oldAction = wall.buildAction
wall.buildAction = function(u,udata,bdata)
oldAction(u,udata,bdata)
wall = GetUnitAbility(u,AbilityList.upwallHP)
for i = 1, #wall.cost do
wall.cost[i] = math.floor(wall.cost[i] * 1.5)
end
end
end)
Perk_AddPerk("ReplaceableTextures\\CommandButtons\\BTNEldrichHorror", "Eldritch Horror", [[Use Thrul biology to construct a new "turret." But at what cost?]], 35, nil, function(pdata)
InitEldritch()
pdata.EldritchHorror = true
end)
Perk_AddPerk("ReplaceableTextures\\CommandButtons\\BTNRecycle", "Reduce, Reuse, Recycle", "Your turrets now generate minerals for each Thrul that they kill, but you can no longer build the power drill.", 25, nil, function(pdata)
local pdrill = GetPlayerAbility(pdata.p,AbilityList.pdrill)
local oldAction = pdrill.gainAction
pdrill.gainAction = function(u,abl)
if oldAction then
oldAction(u,abl)
end
RemoveUnitAbility(u,abl,true)
end
pdata.RecyclerPerk = true
InitRecycler()
end)
Perk_AddPerk("ReplaceableTextures\\CommandButtons\\BTNLuckyUpgrader", "Subsidies", "5%% chance for a building or upgrade to return minerals spent.", 25, nil, function(pdata)
pdata.LuckyDuplication = true
end)
end
end
do
DummyList = {active = {}, passive = {}, bookActive = {}, target = {}}
AbilityList = {
attachedType = {},
attachedUnit = {},
attachedPlayer = {}
}
SPELL_NAME_COLOR = "|cffc8ffff"
SPELL_DESC_HEAD_COLOR = "|cffc8c8c8"
SPELL_DESC_COLOR = "|cffc8c8e6"
abilInitFuncList = {}
function AddAbilityInit(func)
abilInitFuncList[#abilInitFuncList+1] = func
end
function CopyAbility(abl)
local tbl = NewKeyTable()
if abl.cost then
local newCost = NewTable()
for i = 1, #abl.cost do
newCost[i] = abl.cost[i]
end
tbl.cost = newCost
end
tbl.id = abl.id
tbl.name = abl.name
tbl.icon = abl.icon
tbl.description = abl.description
tbl.abil = abl.abil
tbl.action = abl.action
tbl.updateAction = abl.updateAction
tbl.gainAction = abl.gainAction
tbl.playerUnique = abl.playerUnique
tbl.unitUnique = abl.unitUnique
tbl.range = abl.range
tbl.unitid = abl.unitid
tbl.model = abl.model
tbl.scale = abl.scale
tbl.buildAction = abl.buildAction
tbl.prebuildAction = abl.prebuildAction
tbl.buildGrid = abl.buildGrid
tbl.buildTime = abl.buildTime
return tbl
end
function NewAbility(o)
o = o or {}
AbilityList[o.id] = o
o.description = o.description or ""
if o.playerUnique then
for i = 1, #PlayerUsers do
AbilityList.attachedPlayer[GetPlayerId(PlayerUsers[i])][o.id] = {CopyAbility(o)}
end
end
return o
end
function AttachAbilityToUnitType(id, ...)
local arg = varagToTable(...)
AbilityList.attachedType[id] = AbilityList.attachedType[id] or {}
for i = 1, #arg do
table.insert(AbilityList.attachedType[id],arg[i])
end
if ABILITIES_INITIALIZED then
for i = 1, #EveryUnit do
local u = EveryUnit[i]
if GetUnitTypeId(u) == id then
for _, abil in ipairs(arg) do
AddUnitAbility(u,abil)
end
end
end
end
ReleaseTable(arg)
end
function AttachAbilityToUnitTypeForPlayer(p,id, ...)
local arg = varagToTable(...)
AbilityList.attachedType[id] = AbilityList.attachedType[id] or {}
local pid = GetPlayerId(p)
AbilityList.attachedType[id][pid] = AbilityList.attachedType[id][pid] or {}
for i = 1, #arg do
table.insert(AbilityList.attachedType[id][pid],arg[i])
end
if ABILITIES_INITIALIZED then
local pdata = Player2Data(p)
for i = 1, #pdata.AllUnits do
local u = pdata.AllUnits[i]
if GetUnitTypeId(u) == id then
for _, abil in ipairs(arg) do
AddUnitAbility(u,abil)
end
end
end
end
ReleaseTable(arg)
end
function InitDynamicAbils()
for x = 0, 3, 1 do
DummyList.passive[x] = {}
DummyList.active[x] = {}
DummyList.bookActive[x] = {}
DummyList.target[x] = {}
end
DummyList.bookActive[0][0] = FourCC("A00G")
DummyList.bookActive[1][0] = FourCC("A00H")
DummyList.bookActive[2][0] = FourCC("A00I")
DummyList.bookActive[3][0] = FourCC("A00J")
DummyList.bookActive[0][1] = FourCC("A00K")
DummyList.bookActive[1][1] = FourCC("A00L")
DummyList.bookActive[2][1] = FourCC("A00N")
DummyList.bookActive[3][1] = FourCC("A00M")
DummyList.bookActive[0][2] = FourCC("A00P")
DummyList.bookActive[1][2] = FourCC("A00O")
DummyList.bookActive[2][2] = FourCC("A00Q")
DummyList.bookActive[3][2] = FourCC("A00R")
DummyList.active[0][0] = FourCC("A00F")
DummyList.active[1][0] = FourCC("A00S")
DummyList.active[2][0] = FourCC("A00T")
DummyList.active[3][0] = FourCC("A00U")
DummyList.active[0][1] = FourCC("A00V")
DummyList.active[1][1] = FourCC("A00W")
DummyList.active[2][1] = FourCC("A00X")
DummyList.active[3][1] = FourCC("A00Y")
DummyList.active[0][2] = FourCC("A00Z")
DummyList.active[1][2] = FourCC("A010")
DummyList.active[2][2] = FourCC("A011")
DummyList.active[3][2] = FourCC("A012")
DummyList.passive[0][0] = FourCC("A004")
DummyList.passive[1][0] = FourCC("A000")
DummyList.passive[2][0] = FourCC("A005")
DummyList.passive[3][0] = FourCC("A006")
DummyList.passive[0][1] = FourCC("A007")
DummyList.passive[1][1] = FourCC("A008")
DummyList.passive[2][1] = FourCC("A009")
DummyList.passive[3][1] = FourCC("A00A")
DummyList.passive[0][2] = FourCC("A00B")
DummyList.passive[1][2] = FourCC("A00C")
DummyList.passive[2][2] = FourCC("A00D")
DummyList.passive[3][2] = FourCC("A00E")
DummyList.target[0][0] = FourCC("A016")
DummyList.target[1][0] = FourCC("A018")
DummyList.target[2][0] = FourCC("A019")
DummyList.target[3][0] = FourCC("A01A")
DummyList.target[0][1] = FourCC("A01B")
DummyList.target[1][1] = FourCC("A01C")
DummyList.target[2][1] = FourCC("A01D")
DummyList.target[3][1] = FourCC("A01E")
DummyList.target[0][2] = FourCC("A01F")
DummyList.target[1][2] = FourCC("A01G")
DummyList.target[2][2] = FourCC("A01H")
DummyList.target[3][2] = FourCC("A01I")
DummyList.all = {FourCC("A00G"),FourCC("A00H"),FourCC("A00I"),FourCC("A00J"),FourCC("A00K"),FourCC("A00L"),FourCC("A00N"),FourCC("A00M"),FourCC("A00P"),FourCC("A00O"),FourCC("A00Q"),FourCC("A00R")}
local t = CreateTrigger()
for i = 1, #PlayerData do
local data = PlayerData[i]
if data.user then
AbilityList.attachedPlayer[data.id] = {}
end
TriggerRegisterPlayerUnitEvent(t, data.p, EVENT_PLAYER_UNIT_SPELL_EFFECT, nil)
end
for i = 1, #abilInitFuncList do
abilInitFuncList[i]()
end
ABILITIES_INITIALIZED = true
local targetSpells = {}
for x = 0, 3 do
for y = 0, 2 do
targetSpells[DummyList.target[x][y]] = true
end
end
TriggerAddAction(t,function()
local u = GetTriggerUnit()
local target = GetSpellTargetUnit()
local abil = AbilityList.attachedUnit[GetUnitUserData(u)][GetSpellAbilityId()]
local udata = UnitData[GetUnitUserData(u)]
if udata.mineralTarget and not targetSpells[abil] then
IssueTargetOrder(u,"smart",udata.mineralTarget)
end
if abil and not UnitGetAbilityData(u,abil,"hidden") and (not abil.cost or CanAffordCost(Player2Data(GetTriggerPlayer()),abil.cost)) then
abil.action(u,abil,udata,target)
end
end)
ActionAdd(IndexerActions.add,true,function(u,i)
AddUnitTypeAbilities(u)
end)
ActionAdd(IndexerActions.remove,true,function(u,i)
ReleaseTable(UnitData[i].cost)
AbilityCleanIndex(i)
end)
end
function StringToSpellDescription(s)
return SPELL_DESC_HEAD_COLOR.."Description:|n|r" .. SPELL_DESC_COLOR .. s .. "|r"
end
function UpdateSpellDescriptionSimple(canUpdate, v)
local s1,s2,s3 = v.icon, SPELL_NAME_COLOR..v.name.."|r", StringToSpellDescription(v.description)
if canUpdate then
BlzSetAbilityIcon(v.abil,s1)
BlzSetAbilityTooltip(v.abil,s2,0)
BlzSetAbilityExtendedTooltip(v.abil,s3,0)
end
end
function UpdateSpellDescription(pdata,v,u)
local isLocal = MainSelectedUnit==u
UpdateSpellDescriptionSimple(isLocal,v)
if v.updateAction then
v.updateAction(pdata,v,u,isLocal)
end
end
function UpdateAllSpellDescriptions(pdata,u)
local abilList = AbilityList.attachedUnit[GetUnitUserData(u)]
if abilList ~= nil then
for i = 1, #abilList do
UpdateSpellDescription(pdata,abilList[i],u)
end
end
end
function CheckAttachedAbilities(pdata,u)
local abilList = AbilityList.attachedUnit[GetUnitUserData(u)]
if abilList ~= nil then
for i = 1, #DummyList.all do
SetPlayerAbilityAvailable(pdata.p, DummyList.all[i], false)
end
local tbl = NewKeyTable()
for i = 1, #abilList do
local v = abilList[i]
if abilList[v.abil] == v and not UnitGetAbilityData(u,v,"hidden") then
SetPlayerAbilityAvailable(pdata.p, v.abil, true)
tbl[v.abil] = true
table.insert(tbl,v.abil)
UpdateSpellDescription(pdata,v,u)
elseif not tbl[v.abil] then
SetPlayerAbilityAvailable(pdata.p, v.abil, false)
end
end
ReleaseKeyTable(tbl)
end
end
function SoftAbilityCheck(u)
local abilList = AbilityList.attachedUnit[GetUnitUserData(u)]
if abilList ~= nil then
for i = 1, #abilList do
if not UnitGetAbilityData(u,abilList[i],"hidden") then
UpdateSpellDescriptionSimple(true,abilList[i])
end
end
end
end
function GetUnitAbility(u,v)
local abil = AbilityList.attachedUnit[GetUnitUserData(u)]
if not v or not abil or not abil[v.id] then return end
return abil[v.id].abil
end
function UnitHasAbility(u,v)
local abil = AbilityList.attachedUnit[GetUnitUserData(u)]
if not v or not abil then return false end
return abil[v.id]
end
function UnitSetAbilityData(u,abl,Key, val)
AbilityList.attachedUnit[GetUnitUserData(u)][abl.id][Key] = val
return val
end
function UnitGetAbilityData(u,abl,Key)
return AbilityList.attachedUnit[GetUnitUserData(u)][abl.id][Key]
end
function PlayerSetAbilityData(p,abl,Key, val)
AbilityList.attachedPlayer[GetPlayerId(p)][abl.id][Key] = val
end
function PlayerGetAbilityData(p,abl,Key)
if not abl.playerUnique then return end
return AbilityList.attachedPlayer[GetPlayerId(p)][abl.id][Key]
end
function GetPlayerAbility(p,abl)
if not abl.playerUnique then return end
return AbilityList.attachedPlayer[GetPlayerId(p)][abl.id][1]
end
function AddUnitAbility(u,v)
local uid = GetUnitUserData(u)
local p = GetOwningPlayer(u)
AbilityList.attachedUnit[uid] = AbilityList.attachedUnit[uid] or NewKeyTable()
local list = AbilityList.attachedUnit[uid]
local hidden
if v.playerUnique then
v = AbilityList.attachedPlayer[GetPlayerId(p)][v.id][1]
end
if v.unitUnique then
v = CopyAbility(v)
end
list[#list+1] = v
list[v.id] = list[v.id] or NewKeyTable()
list[v.id].abil = v
hidden = list[v.abil]~=nil
UnitSetAbilityData(u,v,"hidden",hidden)
list[v.abil] = list[v.abil] or v
local count = v.abil.."count"
list[count] = list[count] and list[count] + 1 or 1
UnitAddAbility(u,v.abil)
UnitMakeAbilityPermanent(u,true,v.abil)
if not hidden then
SetPlayerAbilityAvailable(p, v.abil, true)
UpdateSpellDescription(Player2Data(p),v,u)
end
if v.gainAction then
v.gainAction(u,v)
end
end
function HideUnitAbilityAll(u,hide)
local abilList = AbilityList.attachedUnit[GetUnitUserData(u)]
if not abilList then return end
local pdata = Player2Data(GetOwningPlayer(u))
for i = 1, #abilList do
local abl = abilList[i]
UnitSetAbilityData(u,abl,"hidden", hide)
if pdata.unitSelectionMain == u then
SetPlayerAbilityAvailable(pdata.p, abl.abil, not hide)
end
if hide then
abilList[abl.abil] = nil
else
abilList[abl.abil] = abl
UpdateSpellDescription(pdata,abl,u)
end
end
end
function HideUnitAbility(u,hide,...)
local abilList = AbilityList.attachedUnit[GetUnitUserData(u)]
if not abilList then return end
local arg = varagToTable(...)
local nextAbil
local abilHidden
local pdata = Player2Data(GetOwningPlayer(u))
for i = 1, #arg do
local v = GetUnitAbility(u,arg[i])
if v then
if not hide then
if pdata.unitSelectionMain == u then
SetPlayerAbilityAvailable(pdata.p, v.abil, true)
end
UnitSetAbilityData(u,v,"hidden", false)
abilList[v.abil] = v
UpdateSpellDescription(pdata,v,u)
else
UnitSetAbilityData(u,v,"hidden",true)
abilList[v.abil] = nil
if pdata.unitSelectionMain == u then
SetPlayerAbilityAvailable(pdata.p, v.abil,false)
end
end
end
end
ReleaseTable(arg)
end
function RemoveUnitAbility(u,v,keepId)
local abilList = AbilityList.attachedUnit[GetUnitUserData(u)]
if abilList == nil then return end
v = GetUnitAbility(u,v)
if not v then return end
table.removeElement(abilList,v)
abilList[v.abil] = nil
if v.unitUnique then
ReleaseTable(v.cost)
ReleaseKeyTable(v)
end
ReleaseKeyTable(abilList[v.id])
abilList[v.id] = nil
local pdata = Player2Data(GetOwningPlayer(u))
local count = v.abil.."count"
abilList[count] = abilList[count] - 1
if abilList[count] == 0 and not keepId then
UnitRemoveAbility(u,v.abil)
elseif pdata.unitSelectionMain == u then
SetPlayerAbilityAvailable(pdata.p, v.abil,false)
end
end
function AddUnitTypeAbilities(u)
local abilList = AbilityList.attachedType[GetUnitTypeId(u)]
if abilList ~= nil then
for i = 1, #abilList do
AddUnitAbility(u,abilList[i])
end
local pid = GetPlayerId(GetOwningPlayer(u))
abilList = abilList[pid]
if abilList ~= nil then
for i = 1, #abilList do
AddUnitAbility(u,abilList[i])
end
end
end
end
function AbilityCleanIndex(id)
local list = AbilityList.attachedUnit[id]
if list then
for i = 1, #list do
local abil = list[i]
if abil.unitUnique then
ReleaseTable(abil.cost)
ReleaseKeyTable(abil)
end
ReleaseKeyTable(list[abil.id])
end
ReleaseKeyTable(list)
AbilityList.attachedUnit[id] = nil
end
end
function DynamicAbilCheck(p,u)
local pData = Player2Data(p)
StopBuildingObject(pData)
pData.unitSelectionMain = u
CheckAttachedAbilities(pData,u)
end
end
do
playersBuilding = 0
BUILD_TIME_DEFAULT = 16
BUILD_PERIOD = 0.03125
BUILD_ID = FourCC("A001")
BUILD_NAME_COLOR = "|cffc8ffc8"
ABILITY_HUMAN_FIRE = FourCC("Afih")
function NewBuildGrid(...)
local tbl = {...}
local xSum, ySum = 0, 0
for _, val in ipairs(tbl) do
xSum = xSum + val.x
ySum = ySum + val.y
end
tbl.centerX = xSum / #tbl * 128
tbl.centerY = ySum / #tbl * 128
return tbl
end
BUILD_GRID_DEFAULT = NewBuildGrid({x = 0, y = 0})
BUILD_GRID_2X2 = NewBuildGrid({x = 1,y = 0},{x = 1,y = 1},{x = 0, y = 1},{x = 0,y = 0})
function Build_lerpSFX(pdata)
pdata.build_lerpTime = math.min(pdata.build_lerpTime + .01,.2)
if pdata.build_lerpTime == .2 or pdata.buildSFX == nil then
pdata.build_lerping = nil
RemoveActionInTimerQueue()
return
end
if PlayerDataLocal == pdata then
local time = easeOutQuad(pdata.build_lerpTime / .2)
BlzSetSpecialEffectPosition(pdata.buildSFX,lerp(time,pdata.build_lerpX,pdata.buildX),lerp(time,pdata.build_lerpY,pdata.buildY),0)
end
end
function Build_moveSFX(pdata)
if pdata.building then
local x,y = ClampCords(pdata.mouseX,pdata.mouseY)
x,y = Cord2Cell(x,y)
local x2,y2 = Cell2Cord(x,y)
local bg = pdata.buildObject.buildGrid
local x3,y3 = x2 + bg.centerX, y2 + bg.centerY
local hasPrevPos = pdata.buildX ~= nil
local isLocal = LocalPlayer == pdata.p
if IsVisibleToPlayer(x3,y3,pdata.p) and pdata.mouseX + pdata.mouseY ~= 0 then
if pdata.buildX == x3 and pdata.buildY == y3 then
return
end
elseif not hasPrevPos then
x,y = Cord2Cell(GetUnitX(pdata.builder),GetUnitY(pdata.builder))
x2,y2 = Cell2Cord(x,y)
x3,y3 = x2 + bg.centerX, y2 + bg.centerY
else
return
end
pdata.buildX = x3
pdata.buildY = y3
pdata.buildX2 = x2
pdata.buildY2 = y2
local sfx
local cells = GetCellsInRange(x2,y2,pdata.buildObject.range or 0) or {}
for _, vec in ipairs(bg) do
table.insert(cells,Vector(x2 + vec.x*128,y2 + vec.y*128))
end
for i, vec in ipairs(cells) do
if pdata.buildRangeEffects[i] then
sfx = pdata.buildRangeEffects[i]
BlzPlaySpecialEffect(sfx,ANIM_TYPE_BIRTH)
else
sfx = AddSpecialEffect("PathIndicator2",0,0)
table.insert(pdata.buildRangeEffects,sfx)
end
if isLocal then
BlzSetSpecialEffectPosition(sfx,vec.x,vec.y,0)
else
BlzSetSpecialEffectPosition(sfx,0,0,-99999)
end
end
local sizeStart = #cells
local sizeEnd = #pdata.buildRangeEffects
if sizeEnd > sizeStart then
for i = sizeEnd, sizeStart, -1 do
DestroyEffect(pdata.buildRangeEffects[i])
pdata.buildRangeEffects[i] = nil
end
end
ReleaseVectorTable(cells)
if hasPrevPos then
pdata.build_lerpX = BlzGetLocalSpecialEffectX(pdata.buildSFX)
pdata.build_lerpY = BlzGetLocalSpecialEffectY(pdata.buildSFX)
pdata.build_lerpTime = 0
pdata.build_pdata = pdata
if not pdata.build_lerping then
pdata.build_lerping = true
AddActionInTimerQueue(0.01,Build_lerpSFX,pdata)
end
elseif isLocal then
BlzSetSpecialEffectPosition(pdata.buildSFX,x3,y3,0)
end
if isLocal then
for _, vec in ipairs(bg) do
local newX = x + vec.x
local newY = y + vec.y
local cell = isCellValid(newX,newY)
if not cell or IsCellFilled(newX,newY) or cell.building or cell.cross then
BlzSetSpecialEffectColor(pdata.buildSFX,255,0,0)
break
else
BlzSetSpecialEffectColor(pdata.buildSFX,0,255,0)
end
end
end
end
end
function buildObjectTimed(tbl)
tbl.time = tbl.time + BUILD_PERIOD
if not UnitAlive(tbl.u) then
tbl.buildTime = 0
end
if tbl.step == 0 then
local input = easeOutQuad(tbl.time)
tbl.x,tbl.y,tbl.z = lerp(input,tbl.x1,tbl.x2),lerp(input,tbl.y1,tbl.y2),lerp(input,0,256)
tbl.yaw, tbl.pitch = AngleBetween3D(tbl.x2,tbl.y2,0,tbl.x,tbl.y,tbl.z)
BlzSetSpecialEffectPosition(tbl.sfx,tbl.x,tbl.y,tbl.z)
BlzSetSpecialEffectYaw(tbl.sfx,tbl.yaw)
BlzSetSpecialEffectPitch(tbl.sfx,tbl.pitch+1.5708)
if tbl.time >= 1 then
tbl.time = 3
tbl.step = 1
tbl.x4, tbl.y4 = tbl.x2, tbl.y2
end
elseif tbl.step == 1 then
tbl.buildTime = math.max(tbl.buildTime - BUILD_PERIOD,0)
local percentDone = 1-tbl.buildTime/tbl.totalTime
if tbl.time >= 3 then
if tbl.light then
DestroyEffect(AddSpecialEffect("Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile",tbl.x4,tbl.y4))
else
tbl.light = AddLightning("RETL",false,0,0,0,0)
end
tbl.time = 0
tbl.x1, tbl.y1 = BlzGetLocalSpecialEffectX(tbl.sfx), BlzGetLocalSpecialEffectY(tbl.sfx)
tbl.x3, tbl.y3 = tbl.x2 + math.random(-256,256), tbl.y2 + math.random(-256,256)
tbl.x5, tbl.y5 = tbl.x4, tbl.y4
tbl.x4, tbl.y4 = tbl.x2 + math.random(-64,64), tbl.y2 + math.random(-64,64)
end
local input = easeOutQuad(tbl.time/3)
BlzSetSpecialEffectPosition(tbl.sfx,lerp(input,tbl.x1,tbl.x3), lerp(input,tbl.y1,tbl.y3), 256)
tbl.x, tbl.y, tbl.z = lerp(input,tbl.x1,tbl.x3), lerp(input,tbl.y1,tbl.y3), 256
BlzSetSpecialEffectYaw(tbl.sfx,tbl.yaw)
BlzSetSpecialEffectPitch(tbl.sfx,tbl.pitch+1.5708)
tbl.x6, tbl.y6 = lerp(input,tbl.x5,tbl.x4), lerp(input,tbl.y5,tbl.y4)
tbl.yaw, tbl.pitch = AngleBetween3D(tbl.x6,tbl.y6,0,tbl.x,tbl.y,tbl.z)
MoveLightningEx(tbl.light,false,tbl.x,tbl.y,tbl.z,tbl.x6,tbl.y6,0)
input = easeInSine(percentDone)
SetUnitVertexColor(tbl.u,math.floor(lerp(input,tbl.r1,tbl.r)),math.floor(lerp(input,tbl.g1,tbl.g)),math.floor(lerp(input,tbl.b1,tbl.b)),math.floor(lerp(input,tbl.a1,tbl.a)))
SetWidgetLife(tbl.u,math.max(GetWidgetLife(tbl.u)+tbl.lifePer,1))
if tbl.buildTime <= 0 then
tbl.time = 0
if UnitAlive(tbl.u) then
AttachFilterToUnit(tbl.uid,FILTER_BUILT)
if tbl.buildData.buildAction then
tbl.buildData.buildAction(tbl.u,tbl.udata, tbl.buildData,tbl.pdata)
end
if tbl.pdata.AlbativeCoating then
local max = BlzGetUnitMaxHP(tbl.u)+500
BlzSetUnitMaxHP(tbl.u,max)
SetWidgetLife(tbl.u,max)
tbl.udata.fragileHP = 500
end
CheckNearbyObjects(tbl.u)
UpdateAllSpellDescriptions(tbl.pdata,tbl.u)
if tbl.abilModList then
HideUnitAbility(tbl.u,false,table.unpack(tbl.abilModList))
ReleaseTable(tbl.abilModList)
end
soundStructureComplete(tbl.pdata == PlayerDataLocal)
BlzSetUnitWeaponBooleanField(tbl.u,UNIT_WEAPON_BF_ATTACKS_ENABLED,0,tbl.atk1)
BlzSetUnitWeaponBooleanField(tbl.u,UNIT_WEAPON_BF_ATTACKS_ENABLED,1,tbl.atk2)
SetUnitTimeScale(tbl.u,1)
end
tbl.x4, tbl.y4 = BlzGetLocalSpecialEffectX(tbl.sfx), BlzGetLocalSpecialEffectY(tbl.sfx)
DestroyLightning(tbl.light)
tbl.step = 2
end
else
local input = easeOutQuad(tbl.time)
tbl.x, tbl.y, tbl.z = lerp(input,tbl.x4,GetUnitX(tbl.builder)), lerp(input,tbl.y4,GetUnitY(tbl.builder)), lerp(input,256,0)
BlzSetSpecialEffectPosition(tbl.sfx,tbl.x,tbl.y,tbl.z)
tbl.yaw, tbl.pitch = AngleBetween3D(tbl.x,tbl.y,0,tbl.x4,tbl.y4,tbl.z)
BlzSetSpecialEffectYaw(tbl.sfx,tbl.yaw)
BlzSetSpecialEffectPitch(tbl.sfx,tbl.pitch+1.5708)
if tbl.time >= 1 then
BlzSetSpecialEffectZ(tbl.sfx,-99999)
DestroyEffect(tbl.sfx)
RemoveActionInTimerQueue()
ReleaseKeyTable(tbl)
end
end
end
function NewBuildProcess(builder,u,buildData)
SetUnitScale(u,buildData.scale,buildData.scale,buildData.scale)
local tbl = NewKeyTable()
local uid = GetUnitUserData(u)
local udata= UnitData[uid]
tbl.builder = builder
tbl.u = u
tbl.buildData = buildData
tbl.x1, tbl.y1 = GetUnitX(builder), GetUnitY(builder)
tbl.x2, tbl.y2 = GetUnitX(u), GetUnitY(u)
tbl.r, tbl.g, tbl.b, tbl.a = BlzGetUnitIntegerField(u,UNIT_IF_TINTING_COLOR_RED),BlzGetUnitIntegerField(u,UNIT_IF_TINTING_COLOR_GREEN),BlzGetUnitIntegerField(u,UNIT_IF_TINTING_COLOR_BLUE),255
tbl.r1, tbl.g1, tbl.b1, tbl.a1 = 0, 255, 255, 50
tbl.time = 0
tbl.sfx = AddSpecialEffect("Widgets\\RoamingDrone2",tbl.x1, tbl.y1)
BlzSetSpecialEffectScale(tbl.sfx,.5)
tbl.step = 0
tbl.buildTime = buildData.buildTime or BUILD_TIME_DEFAULT
tbl.totalTime = tbl.buildTime
tbl.lifePer = BlzGetUnitMaxHP(u) / tbl.totalTime * BUILD_PERIOD
tbl.uid = uid
tbl.udata = udata
tbl.atk1, tbl.atk2 = BlzGetUnitWeaponBooleanField(u,UNIT_WEAPON_BF_ATTACKS_ENABLED,0), BlzGetUnitWeaponBooleanField(u,UNIT_WEAPON_BF_ATTACKS_ENABLED,1)
tbl.pdata = Player2Data(GetOwningPlayer(u))
BlzSetUnitWeaponBooleanField(u,UNIT_WEAPON_BF_ATTACKS_ENABLED,0,false)
BlzSetUnitWeaponBooleanField(u,UNIT_WEAPON_BF_ATTACKS_ENABLED,1,false)
SetUnitTimeScale(u,0)
local abilList = AbilityList.attachedUnit[tbl.uid]
if abilList then
tbl.abilModList = NewTable()
for _, abl in ipairs(abilList) do
if not UnitGetAbilityData(u,abl,"hidden") and abl.id ~= "recycle" then
HideUnitAbility(u,true,abl)
table.insert(tbl.abilModList,abl)
end
end
end
tbl.udata.buildData = buildData
tbl.udata.range = buildData.range
if buildData.prebuildAction then
buildData.prebuildAction(u,udata,buildData)
end
BlzSetUnitName(u,BUILD_NAME_COLOR..tbl.buildData.name.."|r")
UnitRemoveAbility(u,ABILITY_HUMAN_FIRE)
SetWidgetLife(u,1)
SetUnitVertexColor(u, tbl.r1,tbl.g1,tbl.b1,tbl.a1)
AddActionInTimerQueue(BUILD_PERIOD,buildObjectTimed,tbl)
return u
end
function finishBuild(pdata,abl)
if pdata.building ~= nil and pdata.building then
if pdata.inCommandCard ~= nil and pdata.inCommandCard then
return
end
local x, y = Cord2Cell(pdata.buildX2,pdata.buildY2)
local x2, y2
local bg = pdata.buildObject.buildGrid
for _, vec in ipairs(bg) do
local newX = x + vec.x
local newY = y + vec.y
local cell = isCellValid(newX,newY)
x2, y2 = Cell2Cord(newX,newY)
if not cell or IsCellFilled(newX,newY) or cell.building or not IsVisibleToPlayer(x2, y2,pdata.p) or not IsTerrainBlocked(x2,y2) or cell.cross then
soundCantBuild(pdata == PlayerDataLocal)
ErrorMessage("You can't build there!",pdata.p)
return
end
end
if CanAffordCost(pdata,pdata.buildObject.cost) and pdata.mouseX + pdata.mouseY ~= 0 then
DeductCost(pdata,pdata.buildObject.cost)
local u = NewBuildProcess(pdata.builder,CreateUnit(pdata.p,pdata.buildObject.unitid,pdata.buildX,pdata.buildY,pdata.buildAngle*bj_RADTODEG),pdata.buildObject)
BlzSetUnitFacingEx(u,pdata.buildAngle*bj_RADTODEG)
local newCost = NewTable()
for i, val in ipairs(pdata.buildObject.cost) do
newCost[i] = 0
end
UnitData[GetUnitUserData(u)].cost = newCost
AddUnitValue(u,pdata.buildObject.cost)
if not pdata.oskeyShiftPress then
StopBuildingObject(pdata,true)
else
BlzSetSpecialEffectColor(pdata.buildSFX,255,0,0)
end
end
end
end
function DuplicateBuildingFunction(oldId, newId)
AddUnitTypePathingFunc(newId,GetUnitTypePathingFunc(oldId))
local list = AbilityList.attachedType[oldId]
if list then
AttachAbilityToUnitType(newId,table.unpack(list))
end
end
function buildAction(u,abil)
StartBuildingObject(Player2Data(GetTriggerPlayer()),abil,u)
end
function NewBuilding(o)
if o.abil then
o.action = buildAction
o.updateAction = updateActionCost
o.playerUnique = true
o.buildGrid = o.buildGrid or BUILD_GRID_DEFAULT
NewAbility(o)
end
if o.canRecycle then
AttachAbilityToUnitType(o.unitid,AbilityList.recycle)
end
return o
end
function StartBuildingObject(pdata,object,u)
StopBuildingObject(pdata,false)
if PlayerDataLocal == pdata then
EnableDragSelect(false,false)
EnablePreSelect(false,false)
BlzEnableSelections(false,false)
SelectUnit(u,true)
end
pdata.buildAngle = pdata.buildAngle or 4.71239
pdata.buildSFX = AddSpecialEffect(object.model,0,0)
pdata.buildRangeEffects = pdata.buildRangeEffects or {}
pdata.buildObject = object
pdata.builder = u
BlzSetSpecialEffectScale(pdata.buildSFX,object.scale)
BlzSetSpecialEffectColorByPlayer(pdata.buildSFX,pdata.p)
BlzSetSpecialEffectAlpha(pdata.buildSFX,150)
BlzPlaySpecialEffect(pdata.buildSFX,ANIM_TYPE_STAND)
BlzSetSpecialEffectTimeScale(pdata.buildSFX,0)
BlzSetSpecialEffectZ(pdata.buildSFX,-99999)
BlzSetSpecialEffectYaw(pdata.buildSFX,pdata.buildAngle)
pdata.building = true
Build_moveSFX(pdata)
end
function StopBuildingObject(pdata,removeabil)
if pdata.unitSelectionMain ~= nil and GetUnitAbilityLevel(pdata.unitSelectionMain,BUILD_ID) > 0 and removeabil then
UnitRemoveAbility(pdata.unitSelectionMain,BUILD_ID)
UnitAddAbility(pdata.unitSelectionMain,BUILD_ID)
end
if pdata.building ~= nil and pdata.building then
if PlayerDataLocal == pdata then
EnableDragSelect(true,true)
EnablePreSelect(true,true)
BlzEnableSelections(true,true)
end
pdata.buildX = nil
pdata.buildY = nil
if pdata.buildRangeEffects then
for i, sfx in ipairs(pdata.buildRangeEffects) do
DestroyEffect(sfx)
pdata.buildRangeEffects[i] = nil
end
end
BlzSetSpecialEffectPosition(pdata.buildSFX,-999999,-999999,-999999)
DestroyEffect(pdata.buildSFX)
pdata.buildSFX = nil
pdata.building = false
end
end
function InitBuilding()
soundCantBuild = SOUND:new({
path = "Sound\\Interface\\Warning\\Human\\PeasantCannotBuildThere1",
duration = 2.184
})
soundStructureComplete = SOUND:new({
path = "Sounds\\StructureComplete",
duration = 1.409
})
ActionAdd(DeathActions,true,function(victim,_,index)
local udata = UnitData[index]
if udata.buildData and (not udata.filterSelf or FILTER_BUILT & udata.filterSelf ~= FILTER_BUILT) then
SetUnitTimeScale(victim,1)
end
end)
ActionAdd(MouseActions.move,true,Build_moveSFX)
ActionAdd(Button2Table(MouseActions.unclick,MOUSE_BUTTON_TYPE_LEFT),true,finishBuild)
ActionAdd(Button2Table(MouseActions.click,MOUSE_BUTTON_TYPE_RIGHT),true,StopBuildingObject)
end
end
do --//ABILITY UTILITY
ABILITIES_INITIALIZED = false
STRING_UNLOCKS_FURTHER = "|n|n|r|cff00ff00Higher levels unlock further upgrades."
function FinishUpgrade(u,abl,udata,sfx)
local x,y = GetUnitX(u),GetUnitY(u)
local pdata = Player2Data(GetOwningPlayer(u))
soundUpgradeComplete(pdata == PlayerDataLocal)
DestroyEffect(AddSpecialEffect(sfx,x,y))
return pdata, incrementUpgradeAndDeductCostUnit(pdata,u,abl), x, y
end
function updateActionHide(pdata,abl,_,_)
SetPlayerAbilityAvailable(pdata.p,abl.abil,false)
end
function SetTowerDamage(u,dmg,pdata,udata)
local newBonus = math.floor(dmg*pdata.TowerDamageBonus)
BlzSetUnitBaseDamage(u,newBonus,0)
udata.TowerDamageBonus = newBonus - dmg
end
function GetTowerDamageBonus(udata)
return udata.TowerDamageBonus or 0
end
function updateActionCost(_,abl,_,isLocal)
local s = Cost2String(abl.cost)..BlzGetAbilityExtendedTooltip(abl.abil,0)
if isLocal then
BlzSetAbilityExtendedTooltip(abl.abil,s,0)
end
end
function updateUpgrade(pdata,abl,u,isLocal)
local s = SPELL_NAME_COLOR..abl.name.." "..ToRomanNumerals(PlayerGetAbilityData(pdata.p,abl,"lvl") or 1).."|r"
local s1 = Cost2String(abl.cost)..BlzGetAbilityExtendedTooltip(abl.abil,0)
if isLocal then
BlzSetAbilityTooltip(abl.abil,s,0)
BlzSetAbilityExtendedTooltip(abl.abil,s1,0)
end
end
function updateUnitUpgrade(_,abl,u,isLocal)
local s = SPELL_NAME_COLOR..abl.name.." "..ToRomanNumerals(UnitGetAbilityData(u,abl,"lvl") or 1).."|r"
local s1 = Cost2String(abl.cost)..BlzGetAbilityExtendedTooltip(abl.abil,0)
if isLocal then
BlzSetAbilityTooltip(abl.abil,s,0)
BlzSetAbilityExtendedTooltip(abl.abil,s1,0)
end
end
function updateMineSpeed(u,lvls)
BlzSetUnitAttackCooldown(u,BlzGetUnitAttackCooldown(u,0)*(MINING_INCREASE_VALUE^lvls),0)
end
function updateMineRange(u,lvls)
SetUnitAttackRange(u,BlzGetUnitWeaponRealField(u, UNIT_WEAPON_RF_ATTACK_RANGE, 0) + lvls*MINE_RANGE_PER_LEVEL,0)
end
function incrementUpgradeAndDeductCost(pdata,u,abl)
local lvl = PlayerGetAbilityData(pdata.p,abl,"lvl") or 1
lvl = lvl + 1
PlayerSetAbilityData(pdata.p,abl,"lvl",lvl)
DeductCost(pdata,abl.cost)
return lvl
end
function incrementUpgradeAndDeductCostUnit(pdata,u,abl)
local lvl = UnitGetAbilityData(u,abl,"lvl") or 1
lvl = lvl + 1
UnitSetAbilityData(u,abl,"lvl",lvl)
DeductCost(pdata,abl.cost)
AddUnitValue(u,abl.cost)
UpdateSpellDescription(pdata,AbilityList.recycle,u)
return lvl
end
end
do--//RECYCLE
RECYCLE_VALUE = .75
function RecycleUnit(pdata,u,udata)
for i, val in ipairs(udata.cost) do
AddMinerals(pdata,i,math.floor(val*RECYCLE_VALUE))
end
if GetWidgetLife(u)/BlzGetUnitMaxHP(u) <= .75 then
WAVE_SCALE.killed = WAVE_SCALE.killed + 1
end
DestroyEffect(AddSpecialEffect("RecycleEffect",GetUnitX(u),GetUnitY(u)))
RemoveUnit(u)
end
AddAbilityInit(function()
soundRecycle = SOUND:new({
path = "Sounds\\SalvageOperation",
duration = 1.680
})
NewAbility({
id = "recycle",
icon = "ReplaceableTextures\\CommandButtons\\BTNRecycle",
name = "Recycle",
description = "Allows you to recycle this object to regain "..tostring(math.floor(RECYCLE_VALUE*100)).."%% of the cost used to build it.",
abil = DummyList.active[3][2],
action = function(u)
local pdata = Player2Data(GetOwningPlayer(u))
RecycleUnit(pdata,u,UnitData[GetUnitUserData(u)])
soundRecycle(pdata == PlayerDataLocal)
end,
updateAction = function (pdata,abl,u,isLocal)
local unitData = UnitData[GetUnitUserData(u)]
if not unitData.cost then return end
local s = "|cffffc832Value:|n|r"
local hasNoValue = true
for i, c in ipairs(unitData.cost) do
if c > 0 then
hasNoValue = false
local rtype = ResourceTypes[i]
s = s .. " " .. rtype.color.s .. rtype.name .. ": " .. tostring(math.floor(c*RECYCLE_VALUE)) .. "|r|n"
end
end
if hasNoValue then
s = s.." |cff808080None|r|n"
end
if isLocal then
BlzSetAbilityExtendedTooltip(abl.abil,s..BlzGetAbilityExtendedTooltip(abl.abil,0),0)
end
end
})
end)
end
do--//RESOURCE CENTER
MINING_INCREASE_VALUE = .9
BASE_CRITICAL_SUCCESS = .05
CRITICAL_SUCCESS_INCREASE = 1.02
MINE_RANGE_PER_LEVEL = 96
BUILDING_ID_RESOURCE_CENTER = FourCC("h006")
AddAbilityInit(function()
local pageLevel = {}
AddUnitTypePathingFunc(BUILDING_ID_RESOURCE_CENTER,PathingTableBuildings)
soundUpgradeComplete = SOUND:new({
path = "Sounds\\UpgradeComplete",
duration = 1.392
})
soundResearchComplete = SOUND:new({
path = "Sounds\\ResearchComplete",
duration = 1.416
})
NewBuilding({
id = "resourceCenter",
name = "Resource Center",
unitid = BUILDING_ID_RESOURCE_CENTER,
model = "Widgets\\Headquarters",
scale = .8,
cost = {5,0,0,5},
buildGrid = BUILD_GRID_2X2,
buildTime = 30,
canRecycle = true,
abil = DummyList.bookActive[0][0],
icon = "ReplaceableTextures\\CommandButtons\\BTNResourceCenter",
description = "Contains vital technologies for upgrading your ability to mine and gather minerals."
})
pageLevel[0] = function(u,show)
for i, minData in ipairs(ResourceTypes) do
HideUnitAbility(u,show,AbilityList["upgrade"..minData.name.."Quantity"])
end
HideUnitAbility(u,show,
AbilityList.upgradeMainFrame,
AbilityList.upgradeMineSpeed,
AbilityList.upgradeCriticalMineSuccess,
AbilityList.chainMine,
AbilityList.upgradeMineRange
)
end
pageLevel[1] = function(u,show)
HideUnitAbility(u,show,
AbilityList.upgradeImprovedThrusters,
AbilityList.upgradeBarrierManagement,
AbilityList.upgradeNourishingBeam,
AbilityList.upgradeTurretHealth,
AbilityList.upgradeTurretDamage,
AbilityList.upgradeTransferChip,
AbilityList.upgradeAdvancedMineralProcessing,
AbilityList.upgradeMegaCrit,
AbilityList.upgradeShinyGems,
AbilityList.upgradeDeuteriumBlend
)
end
pageLevel[2] = function(u,show)
end
NewAbility({
id = "nextResource",
icon = "ReplaceableTextures\\CommandButtons\\BTNNextPage",
name = "Next",
description = "Goes to the next Resource Center upgrades page.",
abil = DummyList.active[2][2],
action = function(u,abl,udata)
pageLevel[udata.menuLevel](u,true)
udata.menuLevel = udata.menuLevel + 1
if udata.menuLevel > udata.menuLevelMax then
udata.menuLevel = 0
end
pageLevel[udata.menuLevel](u,false)
end
})
local posY = 1
local posX = 1
for i = 1, #ResourceTypes do
local minData = ResourceTypes[i]
if posX > 3 then
posX = 0
posY = posY + 1
end
local newCost = {}
for j = 1, i-1 do
newCost[j] = 0
end
newCost[i] = 8
AttachAbilityToUnitType(BUILDING_ID_RESOURCE_CENTER,
NewAbility({
id = "upgrade"..minData.name.."Quantity",
icon = "ReplaceableTextures/CommandButtons/BTNMiningQuantity"..tostring(i),
name = minData.name.." Mining Quantity",
abil = DummyList.active[posX][posY],
cost = newCost,
action = function(u,abl)
local pdata = Player2Data(GetTriggerPlayer())
pdata.quantityUpLvl = pdata.quantityUpLvl and pdata.quantityUpLvl + 1 or 2
local lvl = incrementUpgradeAndDeductCost(pdata,u,abl)
for x = 1, #ResourceTypes do
local cur = GetUnitAbility(u,AbilityList["upgrade"..ResourceTypes[x].name.."Quantity"])
cur.cost[x] = math.floor((pdata.quantityUpLvl^2)*2+16)
UpdateSpellDescription(pdata,cur,u)
end
pdata.mineRate[i] = pdata.mineRate[i] + 1
soundResearchComplete(pdata == PlayerDataLocal)
if pdata.DrillMaster and lvl == 3 then
RemoveUnitAbility(u,abl)
end
end,
updateAction = function(pdata,abl,u,isLocal)
local lvl = PlayerGetAbilityData(pdata.p,abl,"lvl") or 1
local s = StringToSpellDescription(string.format(
"Increases the quantity of %%s that you can gather while mining by 1 per level. Your current rate is: %%d",
minData.name,
lvl))
if isLocal then
BlzSetAbilityExtendedTooltip(abl.abil,s,0)
end
updateUpgrade(pdata,abl,u,isLocal)
end,
playerUnique = true
})
)
posX = posX + 1
end
AttachAbilityToUnitType(BUILDING_ID_RESOURCE_CENTER,
NewAbility({
id = "upgradeMainFrame",
icon = "ReplaceableTextures\\CommandButtons\\BTNradeMainFrame",
name = "Upgrade Mainframe",
description = "Unlocks further upgrades that can be found using the, \"next\" button.",
abil = DummyList.active[0][0],
cost = {50,50,50,50,50},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
for i = 1, #abl.cost do
if abl.cost[i] ~= 0 then
abl.cost[i] = math.ceil(lvl^3 * 50)
end
end
if lvl == 2 then
AddUnitAbility(u,AbilityList.nextResource)
udata.menuLevel = 0
udata.menuLevelMax = 1
AddUnitAnimationProperties(u,"upgrade first",true)
elseif lvl == 3 then
udata.menuLevelMax = 2
AddUnitAnimationProperties(u,"upgrade first",false)
AddUnitAnimationProperties(u,"upgrade second",true)
end
UpdateSpellDescription(pdata,abl,u)
SetUnitMaxHP(u,BlzGetUnitMaxHP(u)+lvl*100)
BlzSetUnitName(u,BUILD_NAME_COLOR..udata.buildData.name.." "..ToRomanNumerals(lvl-1).."|r")
end,
updateAction = updateUnitUpgrade,
unitUnique = true
}),
NewAbility({
id = "upgradeMineSpeed",
icon = "ReplaceableTextures\\CommandButtons\\BTNMiningSpeed",
name = "Mining Speed",
abil = DummyList.active[0][1],
cost = {5,5,5,5,5},
action = function(u,abl)
local pdata = Player2Data(GetTriggerPlayer())
local lvl = incrementUpgradeAndDeductCost(pdata,u,abl)
for i = 1, #abl.cost do
if abl.cost[i] ~= 0 then
abl.cost[i] = lvl * lvl * 5 + 5
end
end
UpdateSpellDescription(pdata,abl,u)
for i = 1, #pdata.AllUnits do
local enum = pdata.AllUnits[i]
if UnitHasAbility(enum,AbilityList.harvest) then
updateMineSpeed(enum,1)
end
end
soundResearchComplete(pdata == PlayerDataLocal)
end,
updateAction = function(pdata,abl,u,isLocal)
local lvl = PlayerGetAbilityData(pdata.p,abl,"lvl") or 1
local s = StringToSpellDescription(string.format(
"Increases the rate at which you gather minerals by %%.2f%%%% per level multiplicatively.|nCurrent speed speed is: %%.2f%%%%.",
100*(1-MINING_INCREASE_VALUE),
100/(MINING_INCREASE_VALUE^(lvl-1))))
if isLocal then
BlzSetAbilityExtendedTooltip(abl.abil,s,0)
end
updateUpgrade(pdata,abl,u,isLocal)
end,
playerUnique = true
}),
NewAbility({
id = "upgradeCriticalMineSuccess",
icon = "ReplaceableTextures\\CommandButtons\\BTNCriticalSuccess",
name = "Critical Success",
abil = DummyList.active[1][0],
cost = {0,0,4},
action = function(u,abl)
local pdata = Player2Data(GetTriggerPlayer())
local lvl = incrementUpgradeAndDeductCost(pdata,u,abl)
for i = 1, #abl.cost do
if abl.cost[i] ~= 0 then
abl.cost[i] = lvl * lvl * 4
end
end
UpdateSpellDescription(pdata,abl,u)
pdata.criticalSuccess = math.min(BASE_CRITICAL_SUCCESS * (CRITICAL_SUCCESS_INCREASE ^ (lvl-1)),1)
pdata.criticalSucessLevel = lvl-1
soundResearchComplete(pdata == PlayerDataLocal)
end,
updateAction = function(pdata,abl,u,isLocal)
local lvl = PlayerGetAbilityData(pdata.p,abl,"lvl") or 1
local s = StringToSpellDescription(string.format(
"Provides a %%.2f%%%% chance to mine an additional %%d mineral(s) while mining any mineral.",
math.min(100 * BASE_CRITICAL_SUCCESS * (CRITICAL_SUCCESS_INCREASE ^ (lvl-1)),100),
lvl))
if isLocal then
BlzSetAbilityExtendedTooltip(abl.abil,s,0)
end
updateUpgrade(pdata,abl,u,isLocal)
end,
playerUnique = true
}),
NewAbility({
id = "chainMine",
icon = "ReplaceableTextures\\CommandButtons\\BTNChainMining",
name = "Chain Mining",
abil = DummyList.active[2][0],
cost = {0,0,0,0,25},
action = function(u,abl,udata)
local pdata = Player2Data(GetTriggerPlayer())
local lvl = incrementUpgradeAndDeductCost(pdata,u,abl)
for i = 1, #abl.cost do
if abl.cost[i] ~= 0 then
abl.cost[i] = abl.cost[i] * 3
end
end
UpdateSpellDescription(pdata,abl,u)
pdata.chainMineLevel = pdata.chainMineLevel or 0
pdata.chainMineLevel = pdata.chainMineLevel + 1
soundResearchComplete(pdata == PlayerDataLocal)
end,
updateAction = function(pdata,abl,u,isLocal)
local lvl = PlayerGetAbilityData(pdata.p,abl,"lvl") or 1
local s = StringToSpellDescription(string.format(
"Enhanced A.I. increases the precision of the mining laser, refracting through crystals to chain to %%d additional nodes.",
lvl))
if isLocal then
BlzSetAbilityExtendedTooltip(abl.abil,s,0)
end
updateUpgrade(pdata,abl,u, isLocal)
end,
playerUnique = true
}),
NewAbility({
id = "upgradeMineRange",
icon = "ReplaceableTextures\\CommandButtons\\BTNMiningRange",
name = "Bonus Mining Range",
abil = DummyList.active[3][0],
cost = {0,0,0,35},
action = function(u,abl,udata)
local pdata = Player2Data(GetTriggerPlayer())
local lvl = incrementUpgradeAndDeductCost(pdata,u,abl)
for i = 1, #abl.cost do
if abl.cost[i] ~= 0 then
abl.cost[i] = abl.cost[i] * 2
end
end
UpdateSpellDescription(pdata,abl,u)
for i = 1, #pdata.AllUnits do
local enum = pdata.AllUnits[i]
if UnitHasAbility(enum,AbilityList.harvest) then
updateMineRange(enum,1)
end
end
soundResearchComplete(pdata == PlayerDataLocal)
end,
updateAction = function(pdata,abl,u,isLocal)
local lvl = PlayerGetAbilityData(pdata.p,abl,"lvl") or 1
local s = StringToSpellDescription(string.format(
"Increases your mining range by %%d per level.|nTotal Bonus: %%d",
MINE_RANGE_PER_LEVEL,
(lvl-1)*MINE_RANGE_PER_LEVEL))
if isLocal then
BlzSetAbilityExtendedTooltip(abl.abil,s,0)
end
updateUpgrade(pdata,abl,u, isLocal)
end,
playerUnique = true
}),
NewAbility({
id = "upgradeImprovedThrusters",
icon = "ReplaceableTextures\\CommandButtons\\BTNImprovedBoosters",
name = "Improved Thrusters",
description = "Increases your sythensizer's movement speed significantly.",
abil = DummyList.active[0][0],
cost = {25,25,25,25,25},
action = function(u,abl)
local pdata = Player2Data(GetTriggerPlayer())
DeductCost(pdata,abl.cost)
soundResearchComplete(pdata == PlayerDataLocal)
AttachFunctionPlayerToUnitType(pdata,UNIT_ID_SYNTHESIZER,function(u)
SetUnitMoveSpeed(u,522)
end)
RemoveUnitAbility(u,abl)
end,
updateAction = updateActionCost,
playerUnique = true
}),
NewAbility({
id = "upgradeBarrierManagement",
icon = "ReplaceableTextures\\CommandButtons\\BTNBarrierManagement",
name = "Barrier Management",
description = "Decreases the cost of upgrading barriers by 50%%.",
abil = DummyList.active[1][0],
cost = {125,125,125,125,125},
action = function(u,abl)
local pdata = Player2Data(GetTriggerPlayer())
DeductCost(pdata,abl.cost)
soundResearchComplete(pdata == PlayerDataLocal)
AttachFunctionPlayerToUnitType(pdata,BUILDING_ID_WALL,function(u2)
local cost = GetUnitAbility(u2,AbilityList.upwallHP).cost
for i = 1, #cost do
cost[i] = math.floor(cost[i] * .5)
end
end)
RemoveUnitAbility(u,abl)
pdata.barrierManagement = true
end,
updateAction = updateActionCost,
playerUnique = true
}),
NewAbility({
id = "upgradeShinyGems",
icon = "ReplaceableTextures\\CommandButtons\\BTNQualityMining",
name = "Shiny Gems",
description = "The quality of the mineral increases the quantity of minerals mined. This does not apply to power drills.",
abil = DummyList.active[2][0],
cost = {50,50,50,50,50},
action = function(u,abl)
local pdata = Player2Data(GetTriggerPlayer())
DeductCost(pdata,abl.cost)
soundResearchComplete(pdata == PlayerDataLocal)
RemoveUnitAbility(u,abl)
pdata.ShinyGems = true
end,
updateAction = updateActionCost,
playerUnique = true
}),
NewAbility({
id = "upgradeAdvancedMineralProcessing",
icon = "ReplaceableTextures\\CommandButtons\\BTNAdvancedMineralProcessing",
name = "Advanced Mineral Processing",
abil = DummyList.active[3][0],
cost = {100,100,100,100,100},
action = function(u,abl)
local pdata = Player2Data(GetTriggerPlayer())
local lvl = incrementUpgradeAndDeductCost(pdata,u,abl)
for i = 1, #abl.cost do
if abl.cost[i] ~= 0 then
abl.cost[i] = abl.cost[i] * 3
end
end
UpdateSpellDescription(pdata,abl,u)
soundResearchComplete(pdata == PlayerDataLocal)
pdata.MineralProcessing = pdata.MineralProcessing and pdata.MineralProcessing + 1 or 1
end,
updateAction = function(pdata,abl,u,isLocal)
local lvl = PlayerGetAbilityData(pdata.p,abl,"lvl") or 1
local s = StringToSpellDescription(string.format(
"Any time you mine a mineral, gain additional minerals. This does not cause additional damage to mineral nodes.|nCurrent bonus: %%d.",
lvl-1))
if isLocal then
BlzSetAbilityExtendedTooltip(abl.abil,s,0)
end
updateUpgrade(pdata,abl,u,isLocal)
end,
playerUnique = true
}),
NewAbility({
id = "upgradeTurretDamage",
icon = "ReplaceableTextures\\CommandButtons\\BTNTowerDamage",
name = "Turret Weapons Overhaul",
description = "All of your turrets gain a permanent 15%% damage boost.",
abil = DummyList.active[0][1],
cost = {450,450,450,450,450},
action = function(u,abl)
local pdata = Player2Data(GetTriggerPlayer())
local lvl = incrementUpgradeAndDeductCost(pdata,u,abl)
for i = 1, #abl.cost do
if abl.cost[i] ~= 0 then
abl.cost[i] = abl.cost[i] * 3
end
end
UpdateSpellDescription(pdata,abl,u)
soundResearchComplete(pdata == PlayerDataLocal)
pdata.TowerDamageBonus = pdata.TowerDamageBonus + .15
for i = 1, #pdata.AllUnits do
local enum = pdata.AllUnits[i]
local enumData = UnitData[GetUnitUserData(enum)]
if enumData.TowerDamageBonus then
SetTowerDamage(enum,math.ceil(BlzGetUnitBaseDamage(enum,0))-GetTowerDamageBonus(enumData),pdata,enumData)
end
end
end,
updateAction = updateActionCost,
playerUnique = true
}),
NewAbility({
id = "upgradeTurretHealth",
icon = "ReplaceableTextures\\CommandButtons\\BTNTowerHealth",
name = "Turret Structure Overhaul",
description = "All of your turrets gain a permanent 100%% health boost.",
abil = DummyList.active[1][1],
cost = {350,350,350,350,350},
action = function(u,abl)
local pdata = Player2Data(GetTriggerPlayer())
local lvl = incrementUpgradeAndDeductCost(pdata,u,abl)
for i = 1, #abl.cost do
if abl.cost[i] ~= 0 then
abl.cost[i] = abl.cost[i] * 3
end
end
UpdateSpellDescription(pdata,abl,u)
soundResearchComplete(pdata == PlayerDataLocal)
pdata.TowerHealthBonus = pdata.TowerHealthBonus + 1
for i = 1, #pdata.AllUnits do
local enum = pdata.AllUnits[i]
local enumData = UnitData[GetUnitUserData(enum)]
if enumData.TowerDamageBonus then
local max = enumData.fragileHP and BlzGetUnitMaxHP(enum) - enumData.fragileHP or BlzGetUnitMaxHP(enum)
SetUnitMaxHP(enum,max/(pdata.TowerHealthBonus-1)*pdata.TowerHealthBonus)
end
end
end,
updateAction = updateActionCost,
playerUnique = true
}),
NewAbility({
id = "upgradeTransferChip",
icon = "ReplaceableTextures\\CommandButtons\\BTNTransferChip",
name = "Transfer Chip",
description = "All of your power generators gain double the transfer rate. (The ability to send power to nearby structures)",
abil = DummyList.active[2][1],
cost = {550,550,550,550,550},
action = function(u,abl)
local pdata = Player2Data(GetTriggerPlayer())
DeductCost(pdata,abl.cost)
soundResearchComplete(pdata == PlayerDataLocal)
RemoveUnitAbility(u,abl)
pdata.UpgradeTransferChip = true
for i = 1, #pdata.AllUnits do
local enum = pdata.AllUnits[i]
local enumData = UnitData[GetUnitUserData(enum)]
if enumData.buildData and enumData.buildData.id == "pgen" and enumData.transferRate then
enumData.transferRate = enumData.transferRate * 2
end
end
end,
updateAction = updateActionCost,
playerUnique = true
}),
NewAbility({
id = "upgradeMegaCrit",
icon = "ReplaceableTextures\\CommandButtons\\BTNMegaCritical",
name = "MEGA Criticals",
description = "Critical Success now applies to chain mining.",
abil = DummyList.active[3][1],
cost = {0,0,600},
action = function(u,abl)
local pdata = Player2Data(GetTriggerPlayer())
DeductCost(pdata,abl.cost)
soundResearchComplete(pdata == PlayerDataLocal)
RemoveUnitAbility(u,abl)
pdata.MegaCritical = true
end,
updateAction = updateActionCost,
playerUnique = true
}),
NewAbility({
id = "upgradeNourishingBeam",
icon = "ReplaceableTextures\\CommandButtons\\BTNradeBeam",
name = "Nourishing Beam",
description = "Causes your power drill to automatically upgrade minerals when they get depleted; at a cost.",
abil = DummyList.active[0][2],
cost = {200,200,200,200,200},
action = function(u,abl)
local pdata = Player2Data(GetTriggerPlayer())
DeductCost(pdata,abl.cost)
soundResearchComplete(pdata == PlayerDataLocal)
RemoveUnitAbility(u,abl)
pdata.UpgradeNourishingBeam = true
end,
updateAction = updateActionCost,
playerUnique = true
}),
NewAbility({
id = "upgradeDeuteriumBlend",
icon = "ReplaceableTextures\\CommandButtons\\BTNDeuteriumBlend",
name = "Deuterium Blend",
description = "Your towers use 50%% less energy when atttacking.",
abil = DummyList.active[1][2],
cost = {0,0,0,0,300},
action = function(u,abl)
local pdata = Player2Data(GetTriggerPlayer())
DeductCost(pdata,abl.cost)
soundResearchComplete(pdata == PlayerDataLocal)
RemoveUnitAbility(u,abl)
pdata.DeuteriumBlend = true
end,
updateAction = updateActionCost,
playerUnique = true
})
)
end)
end
do--//WALL
BUILDING_ID_WALL = FourCC("h007")
BUILDING_ID_BIGWALL = FourCC("h00J")
ABILITY_ID_HARDEN_SKIN = FourCC('A01Q')
ABILITY_ID_THORNS = FourCC('A01R')
WALL_REGEN_PER_LEVEL = 10
BARRIER_BUILT = false
AddAbilityInit(function()
AddUnitTypePathingFunc(BUILDING_ID_WALL,PathTableAmphibious)
NewBuilding({
id = "wall",
name = "Force Barrier",
unitid = BUILDING_ID_WALL,
model = "widgets\\forcefield5",
scale = .5,
cost = {15},
canRecycle = true,
abil = DummyList.bookActive[1][0],
icon = "ReplaceableTextures\\CommandButtons\\BTNbarrier",
description = "A vital structure used to absorb damage and block a path for hostiles. Allows for friendly units to pass through it.",
buildAction = function(u,udata,bdata)
BARRIER_BUILT = true
end
})
NewAbility({
id = "naniteCluster",
icon = "ReplaceableTextures\\CommandButtons\\BTNNaniteCluster",
name = "Nanite Cluster",
abil = DummyList.active[1][0],
cost = {50,10},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Spells\\Other\\AcidBomb\\BottleMissile")
abl.cost[1] = lvl * lvl * 10 + 30
abl.cost[2] = math.floor(abl.cost[1]/5)
UpdateSpellDescription(pdata,abl,u)
BlzSetUnitRealField(u,UNIT_RF_HIT_POINTS_REGENERATION_RATE,BlzGetUnitRealField(u,UNIT_RF_HIT_POINTS_REGENERATION_RATE) + 10)
end,
updateAction = function(pdata,abl,u,isLocal)
if isLocal then
local lvl = UnitGetAbilityData(u,abl,"lvl") or 1
BlzSetAbilityExtendedTooltip(abl.abil,StringToSpellDescription(string.format(
"Supplies the wall with a small batch of nanites, allowing for passive material regeneration of %%d per second.|nTotal Bonus: %%d",
WALL_REGEN_PER_LEVEL,
WALL_REGEN_PER_LEVEL*(lvl-1)
)),0)
updateUnitUpgrade(pdata,abl,u,isLocal)
end
end,
unitUnique = true
})
NewAbility({
id = "causticBarrier",
icon = "ReplaceableTextures\\CommandButtons\\BTNForceShieldAbility2",
name = "Caustic Barrier",
abil = DummyList.active[2][0],
cost = {20,0,0,20},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Spells\\Orc\\FeralSpirit\\feralspiritdone")
for i = 1, #abl.cost do
if abl.cost[i] ~= 0 then
abl.cost[i] = lvl * lvl * 4 + 20
end
end
UnitAddAbility(u,ABILITY_ID_THORNS)
SetUnitRGBA(u,255,150,150,255)
BlzSetAbilityRealLevelField(BlzGetUnitAbility(u,ABILITY_ID_THORNS),ABILITY_RLF_DAMAGE_DEALT_TO_ATTACKERS,0,lvl*lvl+6)
UpdateSpellDescription(pdata,abl,u)
end,
updateAction = function(pdata,abl,u,isLocal)
local lvl = (UnitGetAbilityData(u,abl,"lvl") or 1) + 1
local s = StringToSpellDescription(string.format(
"Provides the barrier with a caustic layer that causes melee attackers to take %%d damage per attack.",
lvl*lvl+6))
if isLocal then
BlzSetAbilityExtendedTooltip(abl.abil,s,0)
end
updateUnitUpgrade(pdata,abl,u,isLocal)
end,
unitUnique = true
})
AttachAbilityToUnitType(BUILDING_ID_WALL,
NewAbility({
id = "upwallHP",
icon = "ReplaceableTextures\\CommandButtons\\BTNCrystalLattice",
name = "Upgrade Barrier",
description = "Improves the crystal lattice in the structure, making it more damage resilient.|n|n|r|cff00ff00Higher levels unlock further upgrades.",
abil = DummyList.active[0][0],
cost = {30},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Spells\\Human\\DispelMagic\\DispelMagicTarget")
for i = 1, #abl.cost do
if abl.cost[i] ~= 0 then
abl.cost[i] = math.floor(lvl^3.5 * 5 + 40)
if pdata.barrierManagement then
abl.cost[i] = math.floor(abl.cost[i] * .5)
end
if pdata.BiggerWalls then
abl.cost[i] = math.floor(abl.cost[i] * 1.5)
end
end
end
if lvl == 3 then
AddUnitAbility(u,AbilityList.naniteCluster)
elseif lvl == 5 then
AddUnitAbility(u,AbilityList.causticBarrier)
end
UpdateSpellDescription(pdata,abl,u)
UpdateSpellDescription(pdata,AbilityList.wallSkin,u)
local hp = 100*lvl*lvl+100
if pdata.BiggerWalls then
hp = hp * 2.5
end
SetUnitMaxHP(u,math.floor(hp))
BlzSetAbilityRealLevelField(BlzGetUnitAbility(u,AbilityList.wallSkin.abil),ABILITY_RLF_IGNORED_DAMAGE,0,lvl)
BlzSetUnitName(u,BUILD_NAME_COLOR..udata.buildData.name.." "..ToRomanNumerals(lvl-1).."|r")
end,
updateAction = updateUnitUpgrade,
unitUnique = true
}),
NewAbility({
id = "wallSkin",
icon = "ReplaceableTextures\\PassiveButtons\\PASForceShieldAbility",
name = "Deflection",
abil = ABILITY_ID_HARDEN_SKIN,
gainAction = function(u,abl)
BlzSetAbilityRealLevelField(BlzGetUnitAbility(u,abl.abil),ABILITY_RLF_IGNORED_DAMAGE,0,1)
end,
updateAction = function(pdata,abl,u,isLocal)
local lvl = UnitGetAbilityData(u,AbilityList.upwallHP,"lvl") or 1
local s = StringToSpellDescription(string.format(
"This energy barrier reduces all incoming damage by %%d.",
lvl))
if isLocal then
BlzSetAbilityPosX(abl.abil,0)
BlzSetAbilityPosY(abl.abil,1)
BlzSetAbilityExtendedTooltip(abl.abil,s,0)
end
end
})
)
DuplicateBuildingFunction(BUILDING_ID_WALL,BUILDING_ID_BIGWALL)
end)
end
do--//REMATERIALIZER
BUILDING_ID_REMATERIALIZER = FourCC("h009")
AddAbilityInit(function()
AddUnitTypePathingFunc(BUILDING_ID_REMATERIALIZER,PathingTableBuildings)
function HealTarget(source,target, sdata, tdata)
local healthTarget = math.round(GetWidgetLife(target))
local healthTargetMax = BlzGetUnitMaxHP(target)
if healthTargetMax > healthTarget then
local manaSource = math.floor(GetUnitState(source,UNIT_STATE_MANA))
if manaSource > 0 then
local manaUsed = math.min(manaSource,healthTargetMax-healthTarget,sdata.transferRate)
SetUnitState(source,UNIT_STATE_MANA,manaSource - manaUsed)
if sdata.mineralHealer and FILTER_MINERAL & tdata.filterSelf == FILTER_MINERAL then
SetWidgetLife(target,healthTarget + manaUsed * sdata.transferEfficiency * .80)
else
SetWidgetLife(target,healthTarget + manaUsed * sdata.transferEfficiency)
end
AddLightningToDestroyQueue(AddLightningEx(sdata.transferLightning,true,GetUnitX(source),GetUnitY(source),128,GetUnitX(target),GetUnitY(target),64),1)
else
return true
end
elseif sdata.specialBlend then
manaSource = math.floor(GetUnitState(source,UNIT_STATE_MANA))
if manaSource > 0 then
if tdata.fragileHP then
healthTarget = math.max(math.round(sdata.transferRate * 5), tdata.fragileHP)
if tdata. fragileHP >= healthTarget then
return false
end
else
healthTarget = sdata.transferRate * 5
tdata.fragileHP = 0
end
local prev = tdata.fragileHP
manaUsed = math.min(manaSource,math.min(healthTarget-tdata.fragileHP,sdata.transferRate)/sdata.transferEfficiency)
SetUnitState(source,UNIT_STATE_MANA,manaSource - manaUsed)
tdata.fragileHP = math.round(tdata.fragileHP + manaUsed * sdata.transferEfficiency)
healthTargetMax = healthTargetMax+tdata.fragileHP-prev
BlzSetUnitMaxHP(target,healthTargetMax)
SetWidgetLife(target,healthTargetMax)
local light = AddLightningEx("SPPA",true,GetUnitX(source),GetUnitY(source),128,GetUnitX(target),GetUnitY(target),64)
SetLightningColor(light,1,1,0,1)
AddLightningToDestroyQueue(light,1)
else
return true
end
end
return false
end
NewBuilding({
id = "material",
name = "Rematerializer",
unitid = BUILDING_ID_REMATERIALIZER,
model = "Widgets\\Materializer.mdx",
scale = .5,
cost = {0,10},
canRecycle = true,
abil = DummyList.bookActive[2][0],
icon = "ReplaceableTextures\\CommandButtons\\BTNRematerializer",
description = "Provides repairs to nearby structures while powered.",
range = 256,
buildAction = function(u,udata,bdata)
udata.transferRate = 1
BlzSetUnitMaxMana(u,udata.transferRate*5)
udata.transferEfficiency = 2
udata.transferLightning = "GRBM"
AttachFilterToUnit(GetUnitUserData(u), FILTER_USES_POWER)
NewGroupObject(u, bdata.range,FILTER_CAN_BE_HEALED+FILTER_BUILT,HealTarget,true,FILTER_MINERAL)
end
})
NewAbility({
id = "upgradeMineralHealer",
icon = "ReplaceableTextures\\CommandButtons\\BTNradeMineralHealer",
name = "Mineral Transfusion",
description = "Allows this rematerializer to heal mineral nodes at 80%% of their output.|r|n|n|cffff0000This is exclusionary towards Special Healing Blend",
abil = DummyList.active[0][1],
cost = {0,50},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
local sfx = AddSpecialEffect("Abilities\\Weapons\\PoisonSting\\Rings_Green",x,y)
BlzSetSpecialEffectScale(sfx,2)
BlzSetSpecialEffectTimeScale(sfx,3)
BlzSetSpecialEffectHeight(sfx,64)
AttachUnitEffect(u,sfx)
RemoveUnitAbility(u,AbilityList.upgradeMineralHealer)
RemoveUnitAbility(u,AbilityList.upgradeSpecialHealingBlend)
udata.mineralHealer = true
RefreshGroupObject(u, udata.range,FILTER_CAN_BE_HEALED+FILTER_BUILT,HealTarget)
UpdateSpellDescription(pdata,AbilityList.materializerStatus,u)
end,
updateAction = updateActionCost
})
NewAbility({
id = "upgradeSpecialHealingBlend",
icon = "ReplaceableTextures\\CommandButtons\\BTNradeSpecialBlend",
name = "Special Healing Blend",
description = "Allows the rematerializer to over-heal structures, increasing the maximum hitpoints over time to a total of 5x the transfer rate.|r|n|n|cffff0000This is exclusionary towards Mineral Transfusion",
abil = DummyList.active[0][2],
cost = {0,50},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
local sfx = AddSpecialEffect("Abilities\\Spells\\NightElf\\Tranquility\\TranquilityTarget",x,y)
BlzSetSpecialEffectScale(sfx,1.5)
AttachUnitEffect(u,sfx)
RemoveUnitAbility(u,AbilityList.upgradeMineralHealer)
RemoveUnitAbility(u,AbilityList.upgradeSpecialHealingBlend)
udata.specialBlend = true
UpdateSpellDescription(pdata,AbilityList.materializerStatus,u)
end,
updateAction = updateActionCost
})
AttachAbilityToUnitType(BUILDING_ID_REMATERIALIZER,
NewAbility({
id = "upgradeMaterial",
icon = "ReplaceableTextures\\CommandButtons\\BTNMaterializerUpgrade",
name = "Improved Nanites",
description = "Increases healing efficiency, range and internal energy storage."..STRING_UNLOCKS_FURTHER,
abil = DummyList.active[0][0],
cost = {0,20},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
for i = 1, #abl.cost do
if abl.cost[i] ~= 0 then
abl.cost[i] = math.floor(lvl^3 * 4 + 20)
end
end
if lvl == 3 then
AddUnitAbility(u,AbilityList.upgradeMineralHealer)
AddUnitAbility(u,AbilityList.upgradeSpecialHealingBlend)
abl.description = string.gsub(abl.description,STRING_UNLOCKS_FURTHER,"")
end
udata.range = math.min(udata.range+32,OBJECT_MAX_RANGE)
udata.transferEfficiency = udata.transferEfficiency + .25
udata.transferRate = udata.transferRate + lvl*lvl*.25
SetUnitMaxHP(u,5*lvl^1.5+50)
BlzSetUnitName(u,BUILD_NAME_COLOR..udata.buildData.name.." "..ToRomanNumerals(lvl-1).."|r")
BlzSetUnitMaxMana(u,math.floor(udata.transferRate*5))
if not udata.mineralHealer then
RefreshGroupObject(u, udata.range,FILTER_CAN_BE_HEALED+FILTER_BUILT,HealTarget,FILTER_MINERAL)
else
RefreshGroupObject(u, udata.range,FILTER_CAN_BE_HEALED+FILTER_BUILT,HealTarget)
end
UpdateSpellDescription(pdata,abl,u)
UpdateSpellDescription(pdata,AbilityList.materializerStatus,u)
RefreshHoverUnit()
end,
updateAction = updateUnitUpgrade,
unitUnique = true
}),
NewAbility({
id = "materializerStatus",
icon = "ReplaceableTextures\\PassiveButtons\\PASMaterializerStatus",
name = "Materializer Status",
abil = DummyList.passive[1][0],
updateAction = function(pdata,abl,u,isLocal)
local udata=UnitData[GetUnitUserData(u)]
if not udata.transferRate then return end
local s = string.format("Repair Rate: %%.1f/sec|n|nRepair Efficiency: %%.0f%%%%|n|nRange: %%.0f",
udata.transferRate,
100*udata.transferEfficiency,
udata.range)
if udata.specialBlend then
s = s..string.format("|n|nOverheal Amount: %%.0f", udata.transferRate * 5)
elseif udata.mineralHealer then
s = s..string.format("|n|nMineral Heal Amount: %%.1f/sec", udata.transferRate * .8)
end
s = StringToSpellDescription(s)
if isLocal then
BlzSetAbilityExtendedTooltip(abl.abil,s,0)
end
end
})
)
end)
end
do--//POWER GENERATOR
BUILDING_ID_POWER_GENERATOR = FourCC("h008")
AddAbilityInit(function()
function Power_SetSolar(u,udata,isDay)
if isDay then
BlzSetUnitRealField(u,UNIT_RF_MANA_REGENERATION,udata.regenEnergyRate*5)
BlzPlaySpecialEffectWithTimeScale(udata.SolarSFX,ANIM_TYPE_STAND,1)
BlzSetSpecialEffectColor(udata.SolarSFX,255,255,255)
else
BlzSetUnitRealField(u,UNIT_RF_MANA_REGENERATION,0)
BlzPlaySpecialEffectWithTimeScale(udata.SolarSFX,ANIM_TYPE_STAND,0)
BlzSetSpecialEffectColor(udata.SolarSFX,255,0,0)
end
UpdateSpellDescription(Player2Data(GetOwningPlayer(u)),AbilityList.generatorStatus,u)
end
ActionAdd(DayNightActions,true,function(isDay)
for _, u in ipairs(SolarUnits) do
Power_SetSolar(u,UnitData[GetUnitUserData(u)],isDay)
end
end)
AddUnitTypePathingFunc(BUILDING_ID_POWER_GENERATOR,PathingTableBuildings)
function PowerTarget(source,target, sdata, tdata)
local manaTarget = GetUnitState(target,UNIT_STATE_MANA)
local manaTargetMax = GetUnitState(target, UNIT_STATE_MAX_MANA)
if manaTargetMax > manaTarget then
local manaSource = math.floor(GetUnitState(source,UNIT_STATE_MANA))
if manaSource > 0 then
local manaUsed = math.min(manaSource,math.min(manaTargetMax-manaTarget,sdata.transferRate)/sdata.transferEfficiency)
SetUnitState(source,UNIT_STATE_MANA,manaSource - manaUsed)
manaTarget = math.ceil(manaTarget + manaUsed * sdata.transferEfficiency)
SetUnitState(target,UNIT_STATE_MANA,manaTarget)
AddLightningToDestroyQueue(AddLightningEx(sdata.transferLightning,true,GetUnitX(source),GetUnitY(source),64,GetUnitX(target),GetUnitY(target),64),1)
if tdata.heldAttack and tdata.powerPerShot <= manaTarget then
UnitRemoveAbility(target,ABILITY_DISABLE_ATTACK)
tdata.heldAttack = false
end
else
return true
end
end
return false
end
NewBuilding({
id = "pgen",
name = "Power Generator",
unitid = BUILDING_ID_POWER_GENERATOR,
model = "Widgets\\PowerGenerator2",
scale = .9,
cost = {0,0,0,0,10},
canRecycle = true,
abil = DummyList.bookActive[3][0],
icon = "ReplaceableTextures\\CommandButtons\\BTNGenerator",
description = "Provides power to nearby structures, enabling certain functions.",
range = 512,
buildAction = function(u,udata,bdata,pdata)
udata.transferRate = 1
udata.transferEfficiency = .5
udata.transferLightning = "BLPA"
NewGroupObject(u, bdata.range,FILTER_USES_POWER+FILTER_BUILT,PowerTarget,true)
BlzSetUnitRealField(u,UNIT_RF_MANA_REGENERATION,2)
if pdata.UpgradeTransferChip then
udata.transferRate = udata.transferRate * 2
end
end
})
NewAbility({
id = "upgradeSolarCells",
icon = "ReplaceableTextures\\CommandButtons\\BTNSolarUpgrade",
name = "Solar Cells",
description = "Causes this power generator to only generate power during the day, but generates far more than a default generator.|r|n|n|cffff0000This is exclusionary towards Battery",
abil = DummyList.active[0][1],
cost = {0,0,0,0,75},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
local sfx = AddSpecialEffect("SolarAttachment",x,y)
BlzSetSpecialEffectScale(sfx,.5)
BlzSetSpecialEffectHeight(sfx,64)
BlzSetSpecialEffectAlpha(sfx,200)
udata.SolarSFX = sfx
AttachUnitEffect(u,sfx)
RemoveUnitAbility(u,AbilityList.upgradeSolarCells)
RemoveUnitAbility(u,AbilityList.upgradeToBattery)
AddUnitToSynthGroup(u,SolarUnits)
udata.SolarCell = true
Power_SetSolar(u,udata,IS_DAY_TIME)
udata.transferRate = udata.transferRate * 2
if pdata.UpgradeTransferChip then
udata.transferRate = udata.transferRate * 2
end
UpdateSpellDescription(pdata,AbilityList.generatorStatus,u)
end,
updateAction = updateActionCost
})
NewAbility({
id = "upgradeToBattery",
icon = "ReplaceableTextures\\CommandButtons\\BTNBatteryUpgrade",
name = "Battery",
description = "This power generator turns into a battery, which can take in energy and disperse it more effeciently.|r|n|n|cffff0000This is exclusionary towards Solar Cells",
abil = DummyList.active[0][2],
cost = {0,0,0,0,50},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
AttachUnitEffect(u,AddSpecialEffect("MagicShieldOrangeBig",x,y))
RemoveUnitAbility(u,AbilityList.upgradeSolarCells)
RemoveUnitAbility(u,AbilityList.upgradeToBattery)
AttachFilterToUnit(GetUnitUserData(u), FILTER_BATTERY+FILTER_USES_POWER)
udata.transferEfficiency = udata.transferEfficiency + 1
RefreshGroupObject(u, udata.range,FILTER_USES_POWER+FILTER_BUILT,PowerTarget,FILTER_BATTERY)
udata.Battery = true
BlzSetUnitMaxMana(u,BlzGetUnitMaxMana(u) * 5)
BlzSetUnitRealField(u,UNIT_RF_MANA_REGENERATION,0)
udata.regenEnergyRate = 0
local upgGen = GetUnitAbility(u,AbilityList.upgradeGenerator)
if string.match(upgGen.description,STRING_UNLOCKS_FURTHER) then
upgGen.description = "Increases range and effeciency."..STRING_UNLOCKS_FURTHER
else
upgGen.description = "Increases range and effeciency."
end
end,
updateAction = updateActionCost
})
AttachAbilityToUnitType(BUILDING_ID_POWER_GENERATOR,
NewAbility({
id = "upgradeGenerator",
icon = "ReplaceableTextures\\CommandButtons\\BTNImprovedFusion",
name = "Improved Fusion Core",
description = "Increases energy production, range and efficiency."..STRING_UNLOCKS_FURTHER,
abil = DummyList.active[0][0],
cost = {0,0,0,0,20},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
for i = 1, #abl.cost do
if abl.cost[i] ~= 0 then
abl.cost[i] = math.floor(lvl^3* 4 + 20)
end
end
if lvl == 3 then
AddUnitAbility(u,AbilityList.upgradeSolarCells)
AddUnitAbility(u,AbilityList.upgradeToBattery)
abl.description = string.gsub(abl.description,STRING_UNLOCKS_FURTHER,"")
end
udata.range = math.min(udata.range+64,OBJECT_MAX_RANGE)
udata.transferEfficiency = udata.transferEfficiency + .035
udata.transferRate = lvl^2*.25 + 1
if not udata.Battery then
udata.regenEnergyRate = lvl^2.5*.25
RefreshGroupObject(u, udata.range,FILTER_USES_POWER+FILTER_BUILT,PowerTarget)
else
RefreshGroupObject(u, udata.range,FILTER_USES_POWER+FILTER_BUILT,PowerTarget,FILTER_BATTERY)
end
RefreshHoverUnit()
SetUnitMaxHP(u,5*lvl^1.5+50)
local mana = math.floor(lvl^2.5*5) + 100
BlzSetUnitName(u,BUILD_NAME_COLOR..udata.buildData.name.." "..ToRomanNumerals(lvl-1).."|r")
BlzSetUnitRealField(u,UNIT_RF_MANA_REGENERATION,udata.regenEnergyRate)
if udata.Battery then
mana = mana * 5
end
if pdata.UpgradeTransferChip then
udata.transferRate = udata.transferRate * 2
end
if udata.SolarCell then
udata.transferRate = udata.transferRate * 2
Power_SetSolar(u,udata,IS_DAY_TIME)
else
UpdateSpellDescription(pdata,AbilityList.generatorStatus,u)
end
BlzSetUnitMaxMana(u,mana)
UpdateSpellDescription(pdata,abl,u)
end,
updateAction = updateUnitUpgrade,
unitUnique = true
}),
NewAbility({
id = "generatorStatus",
icon = "ReplaceableTextures\\PassiveButtons\\PASGeneratorStatus",
name = "Generator Status",
abil = DummyList.passive[1][0],
updateAction = function(pdata,abl,u,isLocal)
local udata=UnitData[GetUnitUserData(u)]
if not udata.transferRate then return end
local s = string.format("Energy Transfer Rate: %%.1f/sec|n|nTransfer Efficiency: %%.0f%%%%|n|nEnergy Generation Rate: %%.1f|n|nRange: %%.0f",
udata.transferRate,
100*udata.transferEfficiency,
BlzGetUnitRealField(u,UNIT_RF_MANA_REGENERATION),
udata.range)
s = StringToSpellDescription(s)
if isLocal then
BlzSetAbilityExtendedTooltip(abl.abil,s,0)
end
end
})
)
end)
end
do--//POWER Drill
BUILDING_ID_MINING_DRILL = FourCC("h00B")
AddAbilityInit(function()
AddUnitTypePathingFunc(BUILDING_ID_MINING_DRILL,PathingTableBuildings)
function Drill_MineTarget(source,target, sdata, tdata)
local manaSource = math.floor(GetUnitState(source,UNIT_STATE_MANA))
local life = GetWidgetLife(target)
local amount = math.min(math.min(sdata.transferRate,manaSource)*sdata.transferEfficiency,life-1)
local pData = Player2Data(GetOwningPlayer(source))
if pData.UpgradeNourishingBeam and sdata.transferRate*sdata.transferEfficiency > life then
AttemptMineralUpgrade(pData,target,tdata)
end
if manaSource > 0 and amount > 0 then
SetUnitState(source,UNIT_STATE_MANA,manaSource-amount/sdata.transferEfficiency)
local x1,y1,x2,y2 = GetUnitX(source), GetUnitY(source), GetUnitX(target), GetUnitY(target)
AddLightningToDestroyQueue(AddLightningEx(sdata.transferLightning,true,x1,y1,sdata.launchZ,x2,y2,64),1)
if pData.PrecisionLasers then
SetWidgetLife(target,life - amount*.25)
else
SetWidgetLife(target,life - amount)
end
amount = pData.MineralProcessing and pData.MineralProcessing + amount or amount
AddMinerals(pData,tdata.mineralType,amount)
MiningTextTag(pData,amount, ResourceTypes[tdata.mineralType].color, miningStartColor, x2, y2, x1,y1, 2)
else
return true
end
return false
end
NewBuilding({
id = "pdrill",
name = "Power Drill",
unitid = BUILDING_ID_MINING_DRILL,
model = "buildings\\other\\BridgeObelisk\\BridgeObelisk",
scale = 1,
cost = {0,0,50},
canRecycle = true,
abil = DummyList.bookActive[0][1],
icon = "ReplaceableTextures\\CommandButtons\\BTNMiningDrill",
description = "Automatically mines nearby minerals at the cost of power.",
range = 196,
buildAction = function(u,udata,bdata,pdata)
udata.transferRate = .5
udata.transferEfficiency = .5
if pdata.DrillMaster then
udata.transferEfficiency = udata.transferEfficiency + .5
end
udata.transferLightning = "SPSB"
udata.launchZ = 230
AttachFilterToUnit(GetUnitUserData(u), FILTER_USES_POWER)
NewGroupObject(u, bdata.range,FILTER_MINERAL,Drill_MineTarget,true)
end
})
AttachAbilityToUnitType(BUILDING_ID_MINING_DRILL,
NewAbility({
id = "upgradePowerDrill",
icon = "ReplaceableTextures\\CommandButtons\\BTNupgradePowerDrill",
name = "Improved Mining Laser",
description = "Increases the mining laser's range, efficiency and mining rate.",
abil = DummyList.active[0][0],
cost = {0,0,150},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
for i = 1, #abl.cost do
if abl.cost[i] ~= 0 then
abl.cost[i] = math.floor(lvl^3 * 4 + 30)*5
end
end
udata.range = math.min(udata.range+32,OBJECT_MAX_RANGE)
udata.transferEfficiency = udata.transferEfficiency + .025
udata.transferRate = lvl*(.5*1.07^lvl)
SetUnitMaxHP(u,5*lvl^1.5+50)
UpdateSpellDescription(pdata,abl,u)
RefreshGroupObject(u, udata.range,FILTER_MINERAL,Drill_MineTarget)
RefreshHoverUnit()
BlzSetUnitName(u,BUILD_NAME_COLOR..udata.buildData.name.." "..ToRomanNumerals(lvl-1).."|r")
BlzSetUnitMaxMana(u,math.floor(udata.transferRate*20))
UpdateSpellDescription(pdata,AbilityList.powerDrillStatus,u)
end,
updateAction = updateUnitUpgrade,
unitUnique = true
}),
NewAbility({
id = "powerDrillStatus",
icon = "ReplaceableTextures\\PassiveButtons\\PASpowerDrillStatus",
name = "Drill Status",
abil = DummyList.passive[1][0],
updateAction = function(pdata,abl,u,isLocal)
local udata=UnitData[GetUnitUserData(u)]
if not udata.transferRate then return end
local s = string.format("Mining Rate: %%.1f/sec|n|nMining Efficiency: %%.0f%%%%|n|nRange: %%.0f",
udata.transferRate,
100*udata.transferEfficiency,
udata.range)
s = StringToSpellDescription(s)
if isLocal then
BlzSetAbilityExtendedTooltip(abl.abil,s,0)
end
end
})
)
end)
end
do--//Teleporter
local RegionData = {}
local recycledRects = {}
local recycledRegions = {}
ABILITY_ID_GHOST = FourCC('Aeth')
local TeleportCellSpeed = 0.25
function NewRect(x,y,size)
size = size * .5
local rec = recycledRects[#recycledRects]
if rec then
SetRect(rec,x-size,y-size,x+size,y+size)
recycledRects[#recycledRects] = nil
return rec
else
return Rect(x-size,y-size,x+size,y+size)
end
end
function NewRegion(data,rec)
local reg = recycledRegions[#recycledRegions]
local new = not reg
if new then
reg = CreateRegion()
else
recycledRegions[#recycledRegions] = nil
end
RegionData[reg] = data
if rec then
RegionAddRect(reg,rec)
end
return reg, new
end
function ReleaseRect(rec)
table.insert(recycledRects,rec)
end
function ReleaseRegion(reg,rec)
if rec then
RegionClearRect(reg,rec)
ReleaseRect(rec)
end
RegionData[reg] = nil
table.insert(recycledRegions,reg)
end
function CleanTeleporter(u)
local udata = UnitData[GetUnitUserData(u)]
if udata.teleportTarget then
ReleaseRegion(udata.teleportRegion,udata.teleportRect)
udata.teleportTarget = nil
end
if udata.teleportList then
ReleaseTable(udata.teleportList)
udata.teleportList = nil
local tbl = GetTimerData(udata.teleportTimer)
ReleaseTimer(udata.teleportTimer)
ReleaseKeyTable(tbl)
end
if udata.teleportPath then
udata.teleportPath:Release()
udata.teleportPath = nil
end
end
local function teleportTime(tbl)
tbl.time = math.min(tbl.time+.03125,TeleportCellSpeed)
local time = tbl.time/TeleportCellSpeed
SetUnitX(tbl.u,lerp(time,tbl.x,tbl.tarX))
SetUnitY(tbl.u,lerp(time,tbl.y,tbl.tarY))
if time == 1 then
tbl.pos = tbl.pos + 1
if tbl.pos > #tbl.path then
tbl.udata.teleportImmune = nil
UnitRemoveAbility(tbl.u,ABILITY_ID_GHOST)
BlzPauseUnitEx(tbl.u,false)
DestroyEffect(AddSpecialEffect("Abilities\\Spells\\NightElf\\Blink\\BlinkCaster",GetUnitX(tbl.u),GetUnitY(tbl.u)))
ApplyUnitRGBA(tbl.u)
ReleaseTable(tbl.path)
ReleaseKeyTable(tbl)
RemoveActionInTimerQueue()
return
end
tbl.time = 0
tbl.x, tbl.y = Cell2Cord(tbl.path[tbl.pos-1].x, tbl.path[tbl.pos-1].y)
tbl.tarX, tbl.tarY = Cell2Cord(tbl.path[tbl.pos].x, tbl.path[tbl.pos].y)
end
end
function FinishTeleport(u,curMana,target,path,tdata)
SetUnitAnimation(u,"attack")
SetUnitState(u,UNIT_STATE_MANA,curMana)
tdata.teleportFatigue = tdata.teleportFatigue or 0
if Asymptotic(tdata.teleportFatigue,1) < math.random() or Player2Data(GetOwningPlayer(target)).user then
tdata.teleportFatigue = tdata.teleportFatigue + 1
tdata.teleportImmune = true
SoftSetUnitRGBA(target,100,175,255,100)
UnitAddAbility(target,ABILITY_ID_GHOST)
BlzPauseUnitEx(target,true)
IssueImmediateOrder(target,"stop")
DestroyEffect(AddSpecialEffect("Abilities\\Spells\\NightElf\\Blink\\BlinkCaster",GetUnitX(target),GetUnitY(target)))
tbl = NewKeyTable()
tbl.path = DuplicateVectorTable(path)
tbl.u = target
tbl.udata = tdata
tbl.pos = 2
tbl.time = 0
tbl.tarX, tbl.tarY = Cell2Cord(tbl.path[2].x, tbl.path[2].y)
tbl.x, tbl.y = Cell2Cord(tbl.path[1].x, tbl.path[1].y)
AddActionInTimerQueue(.03125,teleportTime,tbl)
return true
else
DestroyEffect(AddSpecialEffect("Abilities\\Weapons\\Bolt\\BoltImpact",GetUnitX(u),GetUnitY(u)))
return false
end
end
function attemptTeleportTime()
local tbl = GetTimerData()
local max = #tbl.udata.teleportList
for i = 1, max do
local target = tbl.udata.teleportList[i]
local tdata = UnitData[GetUnitUserData(target)]
if target and UnitAlive(target) and IsUnitInRegion(tbl.udata.teleportRegion,target) then
local curMana = GetUnitState(tbl.u,UNIT_STATE_MANA) - tbl.udata.transferRate
if curMana >= 0 and not tdata.teleportImmune and FinishTeleport(tbl.u,curMana,target,tbl.udata.teleportPath.Path,tdata) and #tbl.udata.teleportList == 0 then
ReleaseTable(tbl.udata.teleportList)
tbl.udata.teleportList = nil
tbl.udata.teleportTimer = nil
ReleaseTimer()
ReleaseKeyTable(tbl)
end
else
table.remove(tbl.udata.teleportList,i)
if #tbl.udata.teleportList == 0 then
ReleaseTable(tbl.udata.teleportList)
tbl.udata.teleportList = nil
tbl.udata.teleportTimer = nil
ReleaseTimer()
ReleaseKeyTable(tbl)
end
end
end
end
function AttemptTeleport(u,target)
local udata = UnitData[GetUnitUserData(u)]
if not UnitAlive(udata.teleportTarget) then return end
local curMana = GetUnitState(u,UNIT_STATE_MANA) - udata.transferRate
local tdata = UnitData[GetUnitUserData(target)]
if curMana >= 0 and not tdata.teleportImmune and FinishTeleport(u,curMana,target,udata.teleportPath.Path,tdata) then
else
if not udata.teleportList then
udata.teleportList = NewTable()
local tbl = NewKeyTable()
local t = NewTimer(tbl)
udata.teleportTimer = t
tbl.udata = udata
tbl.u = u
TimerStart(t,1,true,attemptTeleportTime)
end
table.insert(udata.teleportList,target)
end
end
BUILDING_ID_TELEPORTER = FourCC("h00G")
AddAbilityInit(function()
soundMustTargetTeleporter = SOUND:new({
path = "Sounds\\MustTargetTeleporter",
duration = 2.354
})
soundUnableToMaterialize = SOUND:new({
path = "Sounds\\UnableTeleportThroughBlocks",
duration = 3.843
})
local t = CreateTrigger()
TriggerAddAction(t,function()
AttemptTeleport(RegionData[GetTriggeringRegion()],GetTriggerUnit())
end)
AddUnitTypePathingFunc(BUILDING_ID_TELEPORTER,PathingTableTeleporter)
local function IsCellValidEmpty(cell)
return not cell.alive or cell.building
end
NewBuilding({
id = "teleporterPad",
name = "Teleporter",
unitid = BUILDING_ID_TELEPORTER,
model = "Widgets\\TeleportSystem2",
scale = .5,
cost = {20,0,0,0,20},
range = 1024,
canRecycle = true,
abil = DummyList.bookActive[1][1],
icon = "ReplaceableTextures\\CommandButtons\\BTNTeleporter",
description = "A structure used to teleport anything that stands on it to another teleporter. The more times an enemy is teleported, the lower the chances for success.",
buildAction = function(u,udata,bdata)
udata.transferRate = 10
AttachFilterToUnit(GetUnitUserData(u), FILTER_USES_POWER)
table.removeElement(PlayerUnits,u)
BlzSetAbilityRealLevelField(BlzGetUnitAbility(u,AbilityList.linkTeleporter.abil),ABILITY_RLF_CAST_RANGE,0,bdata.range)
end
})
AttachAbilityToUnitType(BUILDING_ID_TELEPORTER,
NewAbility({
id = "linkTeleporter",
icon = "ReplaceableTextures\\CommandButtons\\BTNLinkTeleporters",
name = "Link Teleporter",
description = "Link this teleporter with another teleporter to enable the effect. If you used on itself, it will disable the teleporter.",
abil = DummyList.target[0][0],
action = function(u,abl,udata,target)
if udata.teleportTarget then
ReleaseRegion(udata.teleportRegion,udata.teleportRect)
udata.teleportTarget = nil
end
if u ~= target then
local p = GetOwningPlayer(u)
if udata.buildData == UnitData[GetUnitUserData(target)].buildData then
local x,y = GetUnitX(u),GetUnitY(u)
if udata.teleportPath then
udata.teleportPath:Release()
end
udata.teleportPath = Astar( Cord2Cell(x,y,true), Cord2Cell(GetUnitX(target),GetUnitY(target),true), IsCellValidEmpty)
if udata.teleportPath then
udata.teleportPath:CleanPath()
local new
udata.teleportRect = NewRect(x,y,64)
udata.teleportRegion, new = NewRegion(u,udata.teleportRect)
udata.teleportTarget = target
if new then
TriggerRegisterEnterRegion(t,udata.teleportRegion,nil)
end
local g = NewGroup()
MoveRectTo(ENUM_Rect,x,y)
GroupEnumUnitsInRect(g,ENUM_Rect)
GroupRemoveUnit(g,u)
local target
local size = BlzGroupGetSize(g)-1
for i = 0, size, 1 do
target = BlzGroupUnitAt(g,i)
if UnitAlive(target) and IsInSquare(GetUnitX(target),GetUnitY(target),64,x,y)then
AttemptTeleport(u,target)
end
end
ReleaseGroup(g)
else
ErrorMessage("Unable to materialize objects through temple blocks.",p)
soundUnableToMaterialize(p == LocalPlayer)
end
else
ErrorMessage("You must target another teleporter.",p)
soundMustTargetTeleporter(p == LocalPlayer)
end
else
if udata.teleportPath then
udata.teleportPath:Release()
udata.teleportPath = nil
end
ReleaseTable(udata.teleportList)
udata.teleportList = nil
local tim = udata.teleportTimer
if tim then
ReleaseKeyTable(GetTimerData(tim))
ReleaseTimer(tim)
end
end
end
})
)
end)
end
do--//THUNDER TURRET
BUILDING_ID_THUNDER_TURRET = FourCC('h00A')
AddAbilityInit(function()
AddUnitTypePathingFunc(BUILDING_ID_THUNDER_TURRET,PathingTableBuildings)
function Thunder_ChainClosest(u,udata,dmg,count)
local objs = udata.GroupObjects
if objs then
for i = 1, #objs do
local target = objs[i]
local targetData = UnitData[GetUnitUserData(target)]
local mana = GetUnitState(target, UNIT_STATE_MANA)
if (targetData.lastAttacked == nil or targetData.lastAttacked <= GAME_TIME) and targetData.powerPerShot <= mana then
SetUnitState(target,UNIT_STATE_MANA,mana - targetData.powerPerShot)
AddLightningToDestroyQueue(
AddLightningEx(targetData.transferLightning,
true,
GetUnitX(target)+math.random(-16,16),
GetUnitY(target)+math.random(-16,16),
targetData.launchZ,
GetUnitX(u)+math.random(-16,16),
GetUnitY(u)+math.random(-16,16),
udata.launchZ
),
.25
)
SetUnitAnimation(target,"attack")
QueueUnitAnimation(target,"stand")
targetData.lastAttacked = GAME_TIME + BlzGetUnitAttackCooldown(target,0) - GAME_TICK
return Thunder_ChainClosest(target,targetData,dmg+BlzGetUnitBaseDamage(target,0)+1,count+1)
end
end
return dmg, count
else
return dmg, count
end
end
function Thunder_attack(data)
local dmg, count= Thunder_ChainClosest(data.source,data.sourceData,BlzGetUnitBaseDamage(data.source,0)+1,1)
dmg = dmg * (count *.05 + 1)
AddLightningToDestroyQueue(
AddLightningEx(
data.sourceData.transferLightning,
true,
GetUnitX(data.source)+math.random(-16,16),
GetUnitY(data.source)+math.random(-16,16),
data.sourceData.launchZ,
GetUnitX(data.victim),
GetUnitY(data.victim),
BlzGetUnitZ(data.victim)+16
),
.5
)
BlzSetEventDamage(dmg)
if data.sourceData.AOE then
local g = NewGroup()
GroupEnumUnitsInRange(g,GetUnitX(data.victim),GetUnitY(data.victim),data.sourceData.AOE,nil)
GroupRemoveUnit(g,data.victim)
local target
local size = BlzGroupGetSize(g)-1
local p = GetOwningPlayer(data.source)
for i = 0, size, 1 do
target = BlzGroupUnitAt(g,i)
if UnitAlive(target) and IsUnitEnemy(target,p) then
UnitDamageTarget(data.source,target,dmg,false,true,data.attackType,data.damageType,data.weaponType)
end
end
ReleaseGroup(g)
end
if data.sourceData.StunCoil and math.GetPlayerRandom(data.sourceOwner,.25) then
StunUnit(data.victim,1)
end
end
NewBuilding({
id = "thunderTurret",
name = "Thunder Turret",
unitid = BUILDING_ID_THUNDER_TURRET,
model = "Widgets\\ThunderTower.mdl",
scale = .8,
cost = {0,0,0,10},
canRecycle = true,
abil = DummyList.bookActive[0][0],
icon = "ReplaceableTextures\\CommandButtons\\BTNManaFlare",
description = "Capable of chaining together with other thunder turrets to deliver devastating damage. Longer turret chains produce a damage multiplier.",
range = 512,
buildAction = function(u,udata,bdata,pdata)
udata.powerPerShot = 1
udata.onAttackFunc = Thunder_attack
udata.transferLightning = "BLNL"
udata.launchZ = 164
AttachFilterToUnit(GetUnitUserData(u), FILTER_USES_POWER+FILTER_THUNDER)
NewGroupObject(u, bdata.range,FILTER_THUNDER+FILTER_BUILT,nil,false)
UnitAddAbility(u,ABILITY_DISABLE_ATTACK)
udata.heldAttack = true
SetUnitMaxHP(u,BlzGetUnitMaxHP(u)*pdata.TowerHealthBonus)
SetTowerDamage(u,BlzGetUnitBaseDamage(u,0),pdata,udata)
end
})
NewAbility({
id = "upgradeStunCoils",
icon = "ReplaceableTextures\\CommandButtons\\BTNStunCoils",
name = "Stun Coils",
description = "Provides this tower a 25%% chance to stun the target for 1 second.|r|n|n|cffff0000This is exclusionary towards Super Static Coils.",
abil = DummyList.active[0][1],
cost = {0,0,0,50},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
local sfx = AddSpecialEffect("Abilities\\Spells\\Human\\ManaFlare\\ManaFlareMissile",x,y)
BlzSetSpecialEffectScale(sfx,1.5)
BlzSetSpecialEffectZ(sfx,164)
BlzSetSpecialEffectPitch(sfx,4.71239)
AttachUnitEffect(u,sfx)
udata.StunCoil = true
RemoveUnitAbility(u,AbilityList.upgradeStunCoils)
RemoveUnitAbility(u,AbilityList.upgradeSuperStaticCoils)
end,
updateAction = updateActionCost
})
NewAbility({
id = "upgradeSuperStaticCoils",
icon = "ReplaceableTextures\\CommandButtons\\BTNSuperStaticCoils",
name = "Super Static Coils",
description = "Causes this turret to deal damage in an area.|r|n|n|cffff0000This is exclusionary towards Stun Coils.",
abil = DummyList.active[0][2],
cost = {0,10,0,40},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
udata.transferLightning = "RENL"
BlzSetUnitWeaponStringField(u,UNIT_WEAPON_SF_ATTACK_PROJECTILE_ART,0,"Abilities\\Weapons\\AncestralGuardianMissile\\AncestralGuardianMissile")
udata.AOE = 128
RemoveUnitAbility(u,AbilityList.upgradeStunCoils)
RemoveUnitAbility(u,AbilityList.upgradeSuperStaticCoils)
local x,y = GetUnitX(u),GetUnitY(u)
local sfx = AddSpecialEffect("Abilities\\Spells\\Human\\ManaFlare\\ManaFlareBase",x,y)
BlzSetSpecialEffectColor(sfx,255,0,0)
BlzSetSpecialEffectScale(sfx,1.5)
BlzSetSpecialEffectZ(sfx,164)
AttachUnitEffect(u,sfx)
end,
updateAction = updateActionCost
})
AttachAbilityToUnitType(BUILDING_ID_THUNDER_TURRET,
NewAbility({
id = "upgradeThunderTurret",
icon = "ReplaceableTextures\\CommandButtons\\BTNThunderTurretUpgrade",
name = "Improved Coils",
description = "Increases range, damage and health of the tower, but increases the energy used."..STRING_UNLOCKS_FURTHER,
abil = DummyList.active[0][0],
cost = {0,0,0,20},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
for i = 1, #abl.cost do
if abl.cost[i] ~= 0 then
abl.cost[i] = lvl * lvl * 4 + 20
end
end
if lvl == 3 then
AddUnitAbility(u,AbilityList.upgradeStunCoils)
AddUnitAbility(u,AbilityList.upgradeSuperStaticCoils)
abl.description = string.gsub(abl.description,STRING_UNLOCKS_FURTHER,"")
end
udata.powerPerShot = lvl
udata.range = math.min(udata.range+64,OBJECT_MAX_RANGE)
SetTowerDamage(u,math.ceil(BlzGetUnitBaseDamage(u,0)+lvl*1.5)-GetTowerDamageBonus(udata),pdata,udata)
SetUnitAttackRange(u,udata.range,0)
UpdateSpellDescription(pdata,abl,u)
RefreshGroupObject(u, udata.range,FILTER_THUNDER+FILTER_BUILT)
RefreshHoverUnit()
SetUnitMaxHP(u,(5*lvl^1.5+50)*pdata.TowerHealthBonus)
BlzSetUnitName(u,BUILD_NAME_COLOR..udata.buildData.name.." "..ToRomanNumerals(lvl-1).."|r")
BlzSetUnitMaxMana(u,lvl)
end,
updateAction = updateUnitUpgrade,
unitUnique = true
})
)
end)
end
do--//SERPENT TURRET
BUILDING_ID_SERPENT_TURRET = FourCC("h005")
AddAbilityInit(function()
AddUnitTypePathingFunc(BUILDING_ID_SERPENT_TURRET,PathingTableBuildings)
function Serpent_moveProjectile(tbl)
tbl.time = math.min(tbl.time + 0.03125,tbl.duration)
local per = easeInOutBack(math.min(tbl.time / tbl.duration,1))
local x,y = lerp(per,tbl.sourceX,tbl.tarX),lerp(per,tbl.sourceY,tbl.tarY)
BlzSetSpecialEffectPosition(tbl.sfx,x,y,lerp(per,tbl.sourceZ,0))
local g = NewGroup()
GroupEnumUnitsInRange(g,x,y,tbl.aoe,nil)
local target
local size = BlzGroupGetSize(g)-1
for i = 0, size do
target = BlzGroupUnitAt(g,i)
if UnitAlive(target) and (IsUnitEnemy(target,tbl.p) or target == tbl.victim) and not table.hasElement(tbl.hitGroup,target) then
table.insert(tbl.hitGroup,target)
if tbl.source == nil then
per = 1
break
end
if tbl.dense then
tbl.dense = math.min(tbl.dense + .5,5)
local col = math.ceil((1-tbl.dense / 5) * 255)
BlzSetSpecialEffectColor(tbl.sfx,255,col,col)
UnitDamageTarget(tbl.source,target,tbl.dmg*tbl.dense,false,true,tbl.attackType,DAMAGE_TYPE_FORCE,WEAPON_TYPE_WHOKNOWS)
else
UnitDamageTarget(tbl.source,target,tbl.dmg,false,true,tbl.attackType,DAMAGE_TYPE_FORCE,WEAPON_TYPE_WHOKNOWS)
end
DestroyEffect(AddSpecialEffect(tbl.sfxPath,GetUnitX(target),GetUnitY(target)))
end
end
ReleaseGroup(g)
if per == 1 then
ReleaseTable(tbl.hitGroup)
DestroyEffect(tbl.sfx)
ReleaseKeyTable(tbl)
RemoveActionInTimerQueue()
end
end
function Serpent_createProjectile(data,sourceX,sourceY,sourceZ,angle,pitch,duration)
local tbl = NewKeyTable()
tbl.duration = duration
tbl.time = 0
tbl.source = data.source
tbl.victim = data.victim
tbl.hitGroup = NewTable()
tbl.tarX = sourceX + (data.sourceData.range-128) * math.cos(angle)
tbl.tarY = sourceY + (data.sourceData.range-128) * math.sin(angle)
tbl.sourceX = sourceX
tbl.sourceY = sourceY
tbl.sourceZ = sourceZ
if data.sourceData.UltraDense then
tbl.dense = 1
end
tbl.sfxPath = BlzGetUnitWeaponStringField(data.source,UNIT_WEAPON_SF_ATTACK_PROJECTILE_ART,0)
tbl.sfx = AddSpecialEffect(tbl.sfxPath,sourceX,sourceY)
tbl.p = GetOwningPlayer(tbl.source)
tbl.dmg = BlzGetUnitBaseDamage(tbl.source,0)
tbl.attackType = data.attackType
tbl.aoe = data.sourceData.scalar * 64
BlzSetSpecialEffectYaw(tbl.sfx,angle)
BlzSetSpecialEffectPitch(tbl.sfx, pitch+1.5708)
BlzSetSpecialEffectZ(tbl.sfx,sourceZ)
BlzSetSpecialEffectScale(tbl.sfx,data.sourceData.scalar)
AddActionInTimerQueue(0.03125,Serpent_moveProjectile,tbl)
end
function Serpent_attack(data)
local sourceX, sourceY, sourceZ = GetUnitX(data.source), GetUnitY(data.source), data.sourceData.launchZ
local angle, pitch = AngleBetween3D(GetUnitX(data.victim),GetUnitY(data.victim),0,sourceX,sourceY,sourceZ)
sourceX = sourceX + 128 * math.cos(angle)
sourceY = sourceY + 128 * math.sin(angle)
if not data.sourceData.Trifurcation then
Serpent_createProjectile(data,sourceX,sourceY,sourceZ,angle,pitch,(data.sourceData.range-128) / BlzGetUnitWeaponRealField(data.source,UNIT_WEAPON_RF_ATTACK_PROJECTILE_SPEED,0))
else
for i = -1, 1 do
Serpent_createProjectile(data,sourceX,sourceY,sourceZ,angle+(0.261799*i),pitch,(data.sourceData.range-128) / BlzGetUnitWeaponRealField(data.source,UNIT_WEAPON_RF_ATTACK_PROJECTILE_SPEED,0))
end
end
end
NewBuilding({
id = "serpentTurret",
name = "Serpent Turret",
unitid = BUILDING_ID_SERPENT_TURRET,
model = "Widgets\\IronSerpent",
scale = 1,
cost = {0,10,0,10},
canRecycle = true,
abil = DummyList.bookActive[1][0],
icon = "ReplaceableTextures\\CommandButtons\\BTNSerpentTurret",
description = "Deals significant damage in the form of a shockwave, but is narrow and travels in a line.",
range = 512,
buildAction = function(u,udata,bdata,pdata)
udata.powerPerShot = 4
udata.onAttackFunc = Serpent_attack
udata.launchZ = 96
udata.scalar = 1
AttachFilterToUnit(GetUnitUserData(u), FILTER_USES_POWER)
UnitAddAbility(u,ABILITY_DISABLE_ATTACK)
udata.heldAttack = true
SetUnitMaxHP(u,math.floor(BlzGetUnitMaxHP(u)*pdata.TowerHealthBonus))
SetTowerDamage(u,BlzGetUnitBaseDamage(u,0),pdata,udata)
end
})
NewAbility({
id = "upgradeUltraDenseRound",
icon = "ReplaceableTextures\\CommandButtons\\BTNUltraDensityRound",
name = "Ultra Dense Rounds",
description = "Causes this turret to fire microscopic black holes that deal more damage with the more matter they come into contact with.|r|n|n|cffff0000This is exclusionary towards Trifurcation.",
abil = DummyList.active[0][1],
cost = {0,60,0,60},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
udata.UltraDense = true
AttachUnitEffect(u,AddSpecialEffect("MagicShieldPurple.mdx",x,y))
RemoveUnitAbility(u,AbilityList.upgradeUltraDenseRound)
RemoveUnitAbility(u,AbilityList.upgradeTrifurcation)
BlzSetUnitWeaponRealField(u,UNIT_WEAPON_RF_ATTACK_PROJECTILE_SPEED,0,350)
BlzSetUnitWeaponStringField(u,UNIT_WEAPON_SF_ATTACK_PROJECTILE_ART,0,"VoidCrescentTailed")
end,
updateAction = updateActionCost
})
NewAbility({
id = "upgradeTrifurcation",
icon = "ReplaceableTextures\\CommandButtons\\BTNTrifurcation",
name = "Trifurcation",
description = "Causes this turret to release three pulses instead of 1 in a cone pattern.|r|n|n|cffff0000This is exclusionary towards Ultra Dense Rounds.",
abil = DummyList.active[0][2],
cost = {0,60,0,60},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
udata.Trifurcation = true
AttachUnitEffect(u,AddSpecialEffect("MagicShieldCyan",GetUnitX(u),GetUnitY(u)))
RemoveUnitAbility(u,AbilityList.upgradeUltraDenseRound)
RemoveUnitAbility(u,AbilityList.upgradeTrifurcation)
end,
updateAction = updateActionCost
})
AttachAbilityToUnitType(BUILDING_ID_SERPENT_TURRET,
NewAbility({
id = "upgradeSerpentTurret",
icon = "ReplaceableTextures\\CommandButtons\\BTNImprovedCapacitor",
name = "Improved Capacitors",
description = "Increases range, damage, shockwave size and health of the tower, but increases the energy used."..STRING_UNLOCKS_FURTHER,
abil = DummyList.active[0][0],
cost = {0,20,0,20},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
for i = 1, #abl.cost do
if abl.cost[i] ~= 0 then
abl.cost[i] = lvl * lvl * 4 + 20
end
end
if lvl == 3 then
AddUnitAbility(u,AbilityList.upgradeTrifurcation)
AddUnitAbility(u,AbilityList.upgradeUltraDenseRound)
abl.description = string.gsub(abl.description,STRING_UNLOCKS_FURTHER,"")
end
UpdateSpellDescription(pdata,abl,u)
udata.scalar = udata.scalar + .05
udata.powerPerShot = lvl*3
udata.range = math.min(udata.range+32,OBJECT_MAX_RANGE)
SetTowerDamage(u,math.ceil(BlzGetUnitBaseDamage(u,0)+lvl*3)-GetTowerDamageBonus(udata),pdata,udata)
SetUnitAttackRange(u,udata.range,0)
RefreshHoverUnit()
SetUnitMaxHP(u,(5*lvl^1.5+50)*pdata.TowerHealthBonus)
BlzSetUnitName(u,BUILD_NAME_COLOR..udata.buildData.name.." "..ToRomanNumerals(lvl-1).."|r")
BlzSetUnitMaxMana(u,lvl*3)
end,
updateAction = updateUnitUpgrade,
unitUnique = true
})
)
end)
end
do--//FLAMER TURRET
BUILDING_ID_FLAMER_TURRET = FourCC("h00D")
AddAbilityInit(function()
AddUnitTypePathingFunc(BUILDING_ID_FLAMER_TURRET,PathingTableBuildings)
Flamer_flameGroup = {}
function Flamer_burn()
for i = #Flamer_flameGroup, 1, -1 do
local target = Flamer_flameGroup[i]
if target then
local tdata = UnitData[GetUnitUserData(target)]
if not UnitAlive(tdata.fireSource) or tdata.fireDamage < 0.01 then
tdata.fireDamage = nil
RemoveUnitFromSynthGroup(target,Flamer_flameGroup)
else
UnitDamageTarget(tdata.fireSource,target,tdata.fireDamage,false,true,tdata.fireAttackType,DAMAGE_TYPE_FIRE,WEAPON_TYPE_WHOKNOWS)
tdata.fireDamage = tdata.fireDamage * .75
end
end
end
end
SynthGroupAddRemovalAction(Flamer_flameGroup,function(u)
local udata = UnitData[GetUnitUserData(u)]
DestroyEffect(udata.fireSFX)
if #Flamer_flameGroup == 0 then
RemoveActionInTimerQueue(1,Flamer_burn)
end
end)
ActionAdd(DeathActions, true, function(u,_,index)
if UnitData[index].fireDamage then
local source = UnitData[index].fireSource
local udata = UnitData[GetUnitUserData(source)]
if udata.Firebomb then
local g = NewGroup()
local dmg = BlzGetUnitBaseDamage(source,0)*5
local x,y = GetUnitX(u),GetUnitY(u)
GroupEnumUnitsInRange(g,x,y,udata.attackAOE,nil)
DestroyEffect(AddSpecialEffect("Objects\\Spawnmodels\\Human\\FragmentationShards\\FragBoomSpawn",x,y))
local size = BlzGroupGetSize(g)-1
local p = GetOwningPlayer(source)
local atkType = UnitData[index].fireAttackType
for i = 0, size, 1 do
local target = BlzGroupUnitAt(g,i)
if UnitAlive(target) and IsUnitEnemy(target,p) then
UnitDamageTarget(u,target,dmg,false,true,atkType,DAMAGE_TYPE_FIRE,WEAPON_TYPE_WHOKNOWS)
end
end
ReleaseGroup(g)
end
end
end)
function Flamer_attack(data)
local u = data.source
local victim = data.victim
local udata= data.sourceData
local atkType = ConvertAttackType(BlzGetUnitWeaponIntegerField(u,UNIT_WEAPON_IF_ATTACK_ATTACK_TYPE,0))
local g = NewGroup()
local dmg = BlzGetUnitBaseDamage(u,0)
GroupEnumUnitsInRange(g,GetUnitX(victim),GetUnitY(victim),udata.attackAOE,nil)
local target, tdata
local p = GetOwningPlayer(u)
for i = 0, BlzGroupGetSize(g)-1 do
target = BlzGroupUnitAt(g,i)
if UnitAlive(target) and (IsUnitEnemy(target,p) or target == victim) then
UnitDamageTarget(u,target,dmg,false,true,atkType,DAMAGE_TYPE_FIRE,WEAPON_TYPE_WHOKNOWS)
tdata = UnitData[GetUnitUserData(target)]
if tdata.fireDamage then
tdata.fireDamage = tdata.fireDamage + dmg * udata.flameMult
else
if #Flamer_flameGroup == 0 then
AddActionInTimerQueue(1,Flamer_burn)
end
AddUnitToSynthGroup(target,Flamer_flameGroup)
tdata.fireSFX = AddSpecialEffectTarget(udata.attackSFX,target,"origin")
tdata.fireDamage = dmg * udata.flameMult
tdata.fireSource = u
tdata.fireAttackType = atkType
end
end
end
ReleaseGroup(g)
end
NewBuilding({
id = "flamerTurret",
name = "Flamer Turret",
unitid = BUILDING_ID_FLAMER_TURRET,
model = "Widgets\\FlamethrowerTurret",
scale = 1.5,
cost = {0,0,5,10,5},
canRecycle = true,
abil = DummyList.bookActive[2][0],
icon = "ReplaceableTextures\\CommandButtons\\BTNFlamerTurret",
description = "A sturdy front-line turret. It deals small amounts of damage quickly, slowly burning targets alive. Burning damage increases the longer the target is exposed.",
range = 256,
buildAction = function(u,udata,bdata,pdata)
udata.powerPerShot = .25
udata.flameMult = 0.1
udata.attackAOE = 196
udata.onAttackFunc = Flamer_attack
udata.attackSFX = "Environment\\LargeBuildingFire\\LargeBuildingFire1"
AttachFilterToUnit(GetUnitUserData(u), FILTER_USES_POWER)
UnitAddAbility(u,ABILITY_DISABLE_ATTACK)
udata.heldAttack = true
SetUnitMaxHP(u,BlzGetUnitMaxHP(u)*pdata.TowerHealthBonus)
SetTowerDamage(u,BlzGetUnitBaseDamage(u,0),pdata,udata)
end
})
NewAbility({
id = "upgradeFirebomb",
icon = "ReplaceableTextures\\CommandButtons\\BTNFirebomb",
name = "Fire Bomb",
description = "Causes units set ablaze by this turret to explode when killed, dealing damage in an area.|r|n|n|cffff0000This is exclusionary towards Special Blend.",
abil = DummyList.active[0][1],
cost = {0,0,0,65,65},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
udata.Firebomb = true
AttachUnitEffect(u,AddSpecialEffect("Abilities\\Spells\\Undead\\Cripple\\CrippleTarget",x,y))
RemoveUnitAbility(u,AbilityList.upgradeFirebomb)
RemoveUnitAbility(u,AbilityList.upgradeSpecialBlend)
end,
updateAction = updateActionCost
})
NewAbility({
id = "upgradeSpecialBlend",
icon = "ReplaceableTextures\\CommandButtons\\BTNFireCell",
name = "Special Blend",
description = "Significantly increases attack range and burn damage of the turret.|r|n|n|cffff0000This is exclusionary towards Fire Bomb.",
abil = DummyList.active[0][2],
cost = {0,0,65,0,65},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
udata.flameMult = 0.3
udata.range = 512
SetUnitAttackRange(u,512,0)
RefreshHoverUnit()
RemoveUnitAbility(u,AbilityList.upgradeFirebomb)
RemoveUnitAbility(u,AbilityList.upgradeSpecialBlend)
local sfx = AddSpecialEffect("Abilities\\Spells\\Undead\\Curse\\CurseTarget",x,y)
BlzSetSpecialEffectZ(sfx,96)
AttachUnitEffect(u,sfx)
end,
updateAction = updateActionCost
})
AttachAbilityToUnitType(BUILDING_ID_FLAMER_TURRET,
NewAbility({
id = "upgradeFlamerTurret",
icon = "ReplaceableTextures\\CommandButtons\\BTNImprovedInjectors",
name = "Improved Injectors",
description = "Increases initial damage, burn damage and health, but increases the energy used."..STRING_UNLOCKS_FURTHER,
abil = DummyList.active[0][0],
cost = {0,0,10,20,10},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
abl.cost[4] = lvl * lvl * 4 + 20
abl.cost[3] = math.floor(abl.cost[4]*.5)
abl.cost[5] = abl.cost[3]
if lvl == 3 then
AddUnitAbility(u,AbilityList.upgradeFirebomb)
AddUnitAbility(u,AbilityList.upgradeSpecialBlend)
abl.description = string.gsub(abl.description,STRING_UNLOCKS_FURTHER,"")
end
udata.powerPerShot = lvl * .25
SetTowerDamage(u,math.ceil(BlzGetUnitBaseDamage(u,0)+(lvl*lvl)*.05 + 1)-GetTowerDamageBonus(udata),pdata,udata)
UpdateSpellDescription(pdata,abl,u)
RefreshHoverUnit()
SetUnitMaxHP(u,(20*lvl^1.5+100)*pdata.TowerHealthBonus)
BlzSetUnitName(u,BUILD_NAME_COLOR..udata.buildData.name.." "..ToRomanNumerals(lvl-1).."|r")
BlzSetUnitMaxMana(u,lvl*2)
end,
updateAction = updateUnitUpgrade,
unitUnique = true
})
)
end)
end
do--//FUSION TURRET
BUILDING_ID_FUSION_TURRET = FourCC("h00E")
AddAbilityInit(function()
function fusionPowerAttack(data)
if IsUnitEnemy(data.victim,data.sourceOwner.p) then
DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Undead\\ReplenishMana\\SpiritTouchTarget",GetUnitX(data.source),GetUnitY(data.source)))
SetUnitState(data.source,UNIT_STATE_MANA,GetUnitState(data.source,UNIT_STATE_MANA)+data.damage*.5)
end
end
AddUnitTypePathingFunc(BUILDING_ID_FUSION_TURRET,PathingTableBuildings)
NewBuilding({
id = "fusionTurret",
name = "Fusion Turret",
unitid = BUILDING_ID_FUSION_TURRET,
model = "Widgets\\FusionCannon",
scale = .8,
cost = {0,0,0,10,10},
canRecycle = true,
abil = DummyList.bookActive[3][0],
icon = "ReplaceableTextures\\CommandButtons\\BTNFusionTurret",
description = "A long range turret that powers itself, dealing high single target damage.",
range = 640,
buildAction = function(u,udata, buildData,pdata)
SetUnitMaxHP(u,BlzGetUnitMaxHP(u)*pdata.TowerHealthBonus)
SetTowerDamage(u,BlzGetUnitBaseDamage(u,0),pdata,udata)
end,
gainAction = function(u,abl)
if Player2Data(GetOwningPlayer(u)).EldritchHorror then
RemoveUnitAbility(u,abl)
AddUnitAbility(u,AbilityList.abominationTurret)
end
end
})
NewAbility({
id = "upgradeDemoleculizer",
icon = "ReplaceableTextures\\CommandButtons\\BTNDemoleculizer",
name = "Demoleculizer",
description = "Turns this turret into a power generator that gains power from it's attacks, which then can be distributed.|r|n|n|cffff0000This is exclusionary towards Accelerator.",
abil = DummyList.active[0][1],
cost = {0,0,0,0,60},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
local sfx = AddSpecialEffect("Abilities\\Spells\\NightElf\\Barkskin\\BarkSkinTarget",x,y)
BlzSetSpecialEffectZ(sfx,130)
BlzSetSpecialEffectScale(sfx,2)
AttachUnitEffect(u,sfx)
RemoveUnitAbility(u,AbilityList.upgradeDemoleculizer)
RemoveUnitAbility(u,AbilityList.upgradeFusionAccelerator)
udata.transferRate = 99999
udata.transferEfficiency = 1
udata.transferLightning = "BLPA"
udata.onAttackFunc = fusionPowerAttack
BlzSetUnitMaxMana(u,99999)
NewGroupObject(u, udata.range,FILTER_USES_POWER+FILTER_BUILT,PowerTarget,true)
end,
updateAction = updateActionCost
})
NewAbility({
id = "upgradeFusionAccelerator",
icon = "ReplaceableTextures\\CommandButtons\\BTNAccelerator",
name = "Accelerator",
description = "Increases the attack rate of this turret by 50%%.|r|n|n|cffff0000This is exclusionary towards Demoleculizer.",
abil = DummyList.active[0][2],
cost = {0,0,0,60},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
local sfx = AddSpecialEffect("Abilities\\Spells\\Orc\\Bloodlust\\BloodlustTarget",x,y)
BlzSetSpecialEffectZ(sfx,130)
BlzSetSpecialEffectScale(sfx,5)
AttachUnitEffect(u,sfx)
RemoveUnitAbility(u,AbilityList.upgradeDemoleculizer)
RemoveUnitAbility(u,AbilityList.upgradeFusionAccelerator)
BlzSetUnitAttackCooldown(u,BlzGetUnitAttackCooldown(u,0)*.5,0)
end,
updateAction = updateActionCost
})
AttachAbilityToUnitType(BUILDING_ID_FUSION_TURRET,
NewAbility({
id = "upgradeFusionTurret",
icon = "ReplaceableTextures\\CommandButtons\\BTNIsotopicReconstruction",
name = "Improved Isotopic Reconstruction",
description = "Increases the damage, health and range."..STRING_UNLOCKS_FURTHER,
abil = DummyList.active[0][0],
cost = {0,0,0,20,20},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
for i, val in ipairs(abl.cost) do
if val ~= 0 then
abl.cost[i] = lvl * lvl * 4 + 20
end
end
if lvl == 3 then
AddUnitAbility(u,AbilityList.upgradeDemoleculizer)
AddUnitAbility(u,AbilityList.upgradeFusionAccelerator)
abl.description = string.gsub(abl.description,STRING_UNLOCKS_FURTHER,"")
end
udata.range = math.min(udata.range+64,OBJECT_MAX_RANGE)
SetUnitAttackRange(u,udata.range,0)
SetTowerDamage(u,math.ceil(BlzGetUnitBaseDamage(u,0)+lvl*4)-GetTowerDamageBonus(udata),pdata,udata)
UpdateSpellDescription(pdata,abl,u)
RefreshHoverUnit()
SetUnitMaxHP(u,(5*lvl^1.5+50)*pdata.TowerHealthBonus)
BlzSetUnitName(u,BUILD_NAME_COLOR..udata.buildData.name.." "..ToRomanNumerals(lvl-1).."|r")
end,
updateAction = updateUnitUpgrade,
unitUnique = true
})
)
end)
end
do--//ABOMINATION TURRET
local isInit = false
AbilityList.abominationTurret = false
function InitEldritch()
if isInit then return end
isInit = true
BUILDING_ID_ABOMINATION_TURRET = FourCC("h00K")
ActionAdd(DeathActions,true,function(victim,killer,udata)
local kdata = UnitData[GetUnitUserData(killer)]
if kdata and kdata.abominationCur and UnitData[udata].waveType then
kdata.abominationCur = math.min(kdata.abominationCur+1,kdata.abominationCap)
local abil = GetUnitAbility(killer,AbilityList.upgradeAbominationTurret)
if kdata.abominationCap > kdata.abominationCur then
local pdata = Player2Data(GetOwningPlayer(killer))
SetTowerDamage(killer,math.ceil(BlzGetUnitBaseDamage(killer,0)+1)-GetTowerDamageBonus(kdata),pdata,kdata)
UpdateSpellDescription(pdata,abil,killer)
else
abil.cost[4] = 0
UpdateSpellDescription(pdata,abil,killer)
end
end
end)
AddUnitTypePathingFunc(BUILDING_ID_ABOMINATION_TURRET,PathingTableBuildings)
NewBuilding({
id = "abominationTurret",
name = "Abominable Entity",
unitid = BUILDING_ID_ABOMINATION_TURRET,
model = "Widgets\\NzothTentacle",
scale = 1.2,
cost = {0,0,0,25},
canRecycle = true,
abil = DummyList.bookActive[3][0],
icon = "ReplaceableTextures\\CommandButtons\\BTNNzothTentacleIcon",
description = "An unholy blend of Thrul biology with technology. Deals massive area damage in a small range.",
range = 256,
buildAction = function(u,udata, buildData,pdata)
SetUnitMaxHP(u,BlzGetUnitMaxHP(u)*pdata.TowerHealthBonus)
SetTowerDamage(u,BlzGetUnitBaseDamage(u,0),pdata,udata)
udata.abominationCur = 0
udata.abominationCap = 25
end
})
AttachAbilityToUnitType(BUILDING_ID_ABOMINATION_TURRET,
NewAbility({
id = "upgradeAbominationTurret",
icon = "ReplaceableTextures\\CommandButtons\\BTNCannibalize",
name = "Gluttony Incentive",
description = "This. . . thing upgrades through killing and eating Thrul, gaining a permanent boost to damage as it eats. Feeding it minerals increases the damage boost cap and health.",
abil = DummyList.active[0][0],
cost = {0,0,0,50},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Objects\\Spawnmodels\\Orc\\OrcSmallDeathExplode\\OrcSmallDeathExplode")
local last = udata.abominationCap
SetTowerDamage(u,math.ceil(BlzGetUnitBaseDamage(u,0)+udata.abominationCap-udata.abominationCur)-GetTowerDamageBonus(udata),pdata,udata)
udata.abominationCur = udata.abominationCap
udata.abominationCap = lvl^2*12
abl.cost[4] = math.floor(lvl^3 * 15 + 40)
UpdateSpellDescription(pdata,abl,u)
SetUnitMaxHP(u,(20*lvl^1.5+100)*pdata.TowerHealthBonus)
BlzSetUnitName(u,BUILD_NAME_COLOR..udata.buildData.name.." "..ToRomanNumerals(lvl-1).."|r")
end,
updateAction = function(pdata,abl,u,isLocal)
local udata = UnitData[GetUnitUserData(u)]
if not udata.abominationCap then return end
local s = StringToSpellDescription(string.format(
abl.description.."|n|nThrul Consumed: %%d/%%d",
udata.abominationCur,
udata.abominationCap))
if isLocal then
BlzSetAbilityExtendedTooltip(abl.abil,s,0)
end
updateUnitUpgrade(pdata,abl,u,isLocal)
end,
unitUnique = true
})
)
end
end
do--//DISRUPTER TURRET
BUILDING_ID_DISRUPTOR_TURRET = FourCC("h00F")
ABILITY_ID_SLOW_ATTACK = FourCC('A01T')
ABILITY_ID_DISRUPTOR_ABILITY = FourCC('A01N')
AddAbilityInit(function()
AddUnitTypePathingFunc(BUILDING_ID_DISRUPTOR_TURRET,PathingTableBuildings)
function Disrupter_attack(data)
DestroyEffect(AddSpecialEffect("Widgets\\EnergyBurst",GetUnitX(data.victim),GetUnitY(data.victim)))
local sfx = AddSpecialEffect("Widgets\\ArcaneNova",GetUnitX(data.source),GetUnitY(data.source))
BlzSetSpecialEffectZ(sfx,BlzGetUnitZ(data.source)+128)
DestroyEffect(sfx)
local lvl = data.victimData.disruptedLvl or 0
local nextLvl = UnitGetAbilityData(data.source,AbilityList.upgradeDisrupterTurret,"lvl") or 1
if lvl < nextLvl or data.sourceData.AntiMatter then
if data.sourceData.AntiMatter then
nextLvl = math.max(lvl+1,nextLvl)
local abil = BlzGetUnitAbility(data.source,ABILITY_ID_DISRUPTOR_ABILITY)
BlzSetAbilityRealLevelField(abil,ABILITY_RLF_DAMAGE_INCREASE_PERCENT_INF1,0,-nextLvl/(nextLvl+10))
BlzSetAbilityIntegerLevelField(abil,ABILITY_ILF_DEFENSE_INCREASE_INF2,0,-nextLvl)
end
IssueTargetOrder(data.source,"innerfire",data.victim)
data.victimData.disruptedLvl = nextLvl
end
QueueUnitAnimation(data.source,"attack")
end
NewBuilding({
id = "disruptorTurret",
name = "Disruptor Turret",
unitid = BUILDING_ID_DISRUPTOR_TURRET,
model = "Widgets\\DisruptorTower1",
scale = .6,
cost = {10,10,10,10,10},
canRecycle = true,
abil = DummyList.bookActive[0][1],
icon = "ReplaceableTextures\\CommandButtons\\BTNDisruptorTower",
description = "A long range turret that permanently lowers the armor and damage of the target it attacks.",
range = 768,
buildAction = function(u,udata, buildData,pdata)
udata.powerPerShot = 10
udata.onAttackFunc = Disrupter_attack
AttachFilterToUnit(GetUnitUserData(u), FILTER_USES_POWER)
UnitAddAbility(u,ABILITY_DISABLE_ATTACK)
udata.heldAttack = true
local uabil = BlzGetUnitAbility(u,ABILITY_ID_DISRUPTOR_ABILITY)
BlzSetAbilityRealLevelField(uabil,ABILITY_RLF_DAMAGE_INCREASE_PERCENT_INF1,0,-.1)
BlzSetAbilityIntegerLevelField(uabil,ABILITY_ILF_DEFENSE_INCREASE_INF2,0,-1)
SetUnitMaxHP(u,BlzGetUnitMaxHP(u)*pdata.TowerHealthBonus)
SetTowerDamage(u,BlzGetUnitBaseDamage(u,0),pdata,udata)
end
})
NewAbility({
id = "upgradeDecelerator",
icon = "ReplaceableTextures\\CommandButtons\\BTNBreathOfFrost",
name = "Decelerator",
description = "Makes this turret's attacks slow enemy movement speed and attack speed by 25%%.|r|n|n|cffff0000This is exclusionary towards Antimatter.",
abil = DummyList.active[0][1],
cost = {60,60,60,60,60},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
local sfx = AddSpecialEffect("Abilities\\Spells\\Undead\\FrostArmor\\FrostArmorTarget",x,y)
BlzSetSpecialEffectZ(sfx,130)
BlzSetSpecialEffectScale(sfx,1.25)
AttachUnitEffect(u,sfx)
UnitAddAbility(u,ABILITY_ID_SLOW_ATTACK)
RemoveUnitAbility(u,AbilityList.upgradeDecelerator)
RemoveUnitAbility(u,AbilityList.upgradeAntimatterReaction)
end,
updateAction = updateActionCost
})
NewAbility({
id = "upgradeAntimatterReaction",
icon = "ReplaceableTextures\\CommandButtons\\BTNAnitmaterReaction",
name = "Antimatter Reaction",
description = "Increase this turret's attack speed by 50%% and causes the degredation to stack.|r|n|n|cffff0000This is exclusionary towards Decelerator.",
abil = DummyList.active[0][2],
cost = {60,60,60,60,60},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
local sfx = AddSpecialEffect("Abilities\\Spells\\Orc\\LightningShield\\LightningShieldTarget",x,y)
BlzSetSpecialEffectZ(sfx,130)
BlzSetSpecialEffectScale(sfx,1.25)
BlzSetSpecialEffectColor(sfx,255,0,0)
BlzSetUnitAttackCooldown(u,BlzGetUnitAttackCooldown(u,0)*.5,0)
udata.AntiMatter = true
AttachUnitEffect(u,sfx)
RemoveUnitAbility(u,AbilityList.upgradeDecelerator)
RemoveUnitAbility(u,AbilityList.upgradeAntimatterReaction)
end,
updateAction = updateActionCost
})
AttachAbilityToUnitType(BUILDING_ID_DISRUPTOR_TURRET,
NewAbility({
id = "upgradeDisrupterTurret",
icon = "ReplaceableTextures\\CommandButtons\\BTNDisruptorTowerUpgrade",
name = "Improved Radiation Jammer",
description = "Increases damage and armor reduction, but increases the energy used."..STRING_UNLOCKS_FURTHER,
abil = DummyList.active[0][0],
cost = {20,20,20,20,20},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
for i, val in ipairs(abl.cost) do
if val ~= 0 then
abl.cost[i] = lvl * lvl * 4 + 20
end
end
if lvl == 3 then
AddUnitAbility(u,AbilityList.upgradeDecelerator)
AddUnitAbility(u,AbilityList.upgradeAntimatterReaction)
abl.description = string.gsub(abl.description,STRING_UNLOCKS_FURTHER,"")
end
udata.powerPerShot = lvl * 5 + 5
SetTowerDamage(u,math.ceil(BlzGetUnitBaseDamage(u,0)+lvl*5)-GetTowerDamageBonus(udata),pdata,udata)
UpdateSpellDescription(pdata,abl,u)
RefreshHoverUnit()
SetUnitMaxHP(u,(10*lvl^1.5+100)*pdata.TowerHealthBonus)
BlzSetUnitName(u,BUILD_NAME_COLOR..udata.buildData.name.." "..ToRomanNumerals(lvl-1).."|r")
BlzSetUnitMaxMana(u,lvl * 5 + 5)
local uabil = BlzGetUnitAbility(u,ABILITY_ID_DISRUPTOR_ABILITY)
BlzSetAbilityRealLevelField(uabil,ABILITY_RLF_DAMAGE_INCREASE_PERCENT_INF1,0,-lvl/(lvl+10))
BlzSetAbilityIntegerLevelField(uabil,ABILITY_ILF_DEFENSE_INCREASE_INF2,0,-lvl)
end,
updateAction = updateUnitUpgrade,
unitUnique = true
})
)
end)
end
do--//Gatling TURRET
BUILDING_ID_GATLING_TURRET = FourCC("h00H")
ABILITY_ID_SLOW_ATTACK = FourCC('A01T')
ABILITY_ID_GATLING_ABILITY = FourCC('A01O')
AddAbilityInit(function()
AddUnitTypePathingFunc(BUILDING_ID_GATLING_TURRET,PathingTableBuildings)
function updateGatlingAbility(u,udata)
if GetUnitAbilityLevel(u,ABILITY_ID_GATLING_ABILITY) > 0 then
local abl = BlzGetUnitAbility(u,ABILITY_ID_GATLING_ABILITY)
local dmg = (BlzGetUnitBaseDamage(u,0) + 1) * .5
BlzSetAbilityRealLevelField(abl,ABILITY_RLF_CHANCE_TO_STOMP_PERCENT,0,100)
if udata.ExplosiveMunition then
BlzSetAbilityRealLevelField(abl,ABILITY_RLF_DAMAGE_DEALT_WAR2,0,BlzGetUnitBaseDamage(u,0) + 1*.75)
else
BlzSetAbilityRealLevelField(abl,ABILITY_RLF_DAMAGE_DEALT_WAR2,0,(BlzGetUnitBaseDamage(u,0) + 1) * .5)
end
end
end
function Gatling_attack(data)
if data.sourceData.gatLast then
if data.sourceData.gatLast > GAME_TIME then
local as
if data.sourceData.SlowMunition then
as = math.max(BlzGetUnitAttackCooldown(data.source,0)-.015,.1)
else
as = math.max(BlzGetUnitAttackCooldown(data.source,0)-.03,.1)
end
BlzSetUnitAttackCooldown(data.source,as,0)
if as == .05 and UnitAddAbility(data.source,ABILITY_ID_GATLING_ABILITY) then
data.sourceData.gatSFX = AttachUnitEffect(data.source,AddSpecialEffectTarget("Abilities\\Spells\\Orc\\Bloodlust\\BloodlustTarget",data.source,"weapon"))
updateGatlingAbility(data.source,data.sourceData)
end
else
BlzSetUnitAttackCooldown(data.source,1,0)
if data.sourceData.gatSFX then
UnitRemoveAbility(data.source,ABILITY_ID_GATLING_ABILITY)
RemoveUnitEffect(data.source,data.sourceData.gatSFX)
data.sourceData.gatSFX = nil
end
end
end
data.sourceData.gatLast = GAME_TIME + 5
end
NewBuilding({
id = "gatlingTurret",
name = "Gatling Turret",
unitid = BUILDING_ID_GATLING_TURRET,
model = "Widgets\\GatlingTurretSingle",
scale = .8,
cost = {0,0,0,10},
canRecycle = true,
abil = DummyList.bookActive[1][1],
icon = "ReplaceableTextures\\CommandButtons\\BTNGatlingTurret",
description = "A short range turret that gains attack speed the longer it attacks. At max attack speed the turret deal AOE damage.",
range = 512,
buildAction = function(u,udata, buildData,pdata)
udata.powerPerShot = 1
udata.onAttackFunc = Gatling_attack
AttachFilterToUnit(GetUnitUserData(u), FILTER_USES_POWER)
UnitAddAbility(u,ABILITY_DISABLE_ATTACK)
udata.heldAttack = true
SetUnitMaxHP(u,BlzGetUnitMaxHP(u)*pdata.TowerHealthBonus)
SetTowerDamage(u,BlzGetUnitBaseDamage(u,0),pdata,udata)
end
})
NewAbility({
id = "upgradeExplosiveMunitions",
icon = "ReplaceableTextures\\CommandButtons\\BTNHumanMissileUpThree",
name = "Explosive Munitions",
description = "The AOE damage effect of when the attack speed buildup is finished deals 50%% more damage.|r|n|n|cffff0000This is exclusionary towards Slow Burn Munitions.",
abil = DummyList.active[0][1],
cost = {0,0,0,60},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
AttachUnitEffect(u,AddSpecialEffectTarget("Abilities\\Weapons\\Mortar\\MortarMissile",u,"weapon"))
RemoveUnitAbility(u,AbilityList.upgradeSlowMunition)
RemoveUnitAbility(u,AbilityList.upgradeExplosiveMunitions)
BlzSetUnitWeaponStringField(u,UNIT_WEAPON_SF_ATTACK_PROJECTILE_ART,0,"Abilities\\Weapons\\FireBallMissile\\FireBallMissile")
udata.ExplosiveMunition = true
updateGatlingAbility(u,udata)
end,
updateAction = updateActionCost
})
NewAbility({
id = "upgradeSlowMunition",
icon = "ReplaceableTextures\\CommandButtons\\BTNHumanMissileUpTwo",
name = "Slow Burn Munitions",
description = "Reduces the attack speed buildup of the turret by 50%% but the turret deals 100%% more damage.|r|n|n|cffff0000This is exclusionary towards Explosive Munitions.",
abil = DummyList.active[0][2],
cost = {0,0,0,60},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
udata.SlowMunition = true
AttachUnitEffect(u,AddSpecialEffect("Abilities\\Spells\\Human\\slow\\slowtarget",x,y))
RemoveUnitAbility(u,AbilityList.upgradeSlowMunition)
RemoveUnitAbility(u,AbilityList.upgradeExplosiveMunitions)
BlzSetUnitBaseDamage(u,BlzGetUnitBaseDamage(u,0)*2,0)
updateGatlingAbility(u,udata)
end,
updateAction = updateActionCost
})
AttachAbilityToUnitType(BUILDING_ID_GATLING_TURRET,
NewAbility({
id = "upgradeGatlingTurret",
icon = "ReplaceableTextures\\CommandButtons\\BTNImprovedMunitions",
name = "Improved Munitions",
description = "Increases damage and health but increases the energy used."..STRING_UNLOCKS_FURTHER,
abil = DummyList.active[0][0],
cost = {0,0,0,20},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
for i, val in ipairs(abl.cost) do
if val ~= 0 then
abl.cost[i] = lvl * lvl * 4 + 20
end
end
if lvl == 3 then
abl.description = string.gsub(abl.description,STRING_UNLOCKS_FURTHER,"")
AddUnitAbility(u,AbilityList.upgradeSlowMunition)
AddUnitAbility(u,AbilityList.upgradeExplosiveMunitions)
end
udata.powerPerShot = lvl
SetTowerDamage(u,math.ceil(lvl*lvl*.5)-GetTowerDamageBonus(udata),pdata,udata)
if udata.SlowMunition then
BlzSetUnitBaseDamage(u,BlzGetUnitBaseDamage(u,0)*2,0)
end
UpdateSpellDescription(pdata,abl,u)
RefreshHoverUnit()
SetUnitMaxHP(u,(10*lvl^1.5+100)*pdata.TowerHealthBonus)
BlzSetUnitName(u,BUILD_NAME_COLOR..udata.buildData.name.." "..ToRomanNumerals(lvl-1).."|r")
BlzSetUnitMaxMana(u,lvl * 20)
updateGatlingAbility(u,udata)
end,
updateAction = updateUnitUpgrade,
unitUnique = true
})
)
end)
end
do--//Sentry TURRET
BUILDING_ID_SENTRY_TURRET = FourCC("h00I")
AddAbilityInit(function()
AddUnitTypePathingFunc(BUILDING_ID_SENTRY_TURRET,PathingTableBuildings)
function Sentry_attack(data)
DestroyEffect(AddSpecialEffect("Abilities\\Weapons\\GyroCopter\\GyroCopterImpact",GetUnitX(data.victim),GetUnitY(data.victim)))
end
NewBuilding({
id = "sentryTurret",
name = "Sentry Turret",
unitid = BUILDING_ID_SENTRY_TURRET,
model = "Widgets\\SentryTurret.mdx",
scale = 1.5,
cost = {50,50,50,50,50},
canRecycle = true,
abil = DummyList.bookActive[2][1],
icon = "ReplaceableTextures\\CommandButtons\\BTNSentryTurret",
description = "A long range turret that deals devastating damage.",
range = 1024,
buildGrid = BUILD_GRID_2X2,
buildAction = function(u,udata, buildData,pdata)
udata.powerPerShot = 50
udata.onAttackFunc = Sentry_attack
AttachFilterToUnit(GetUnitUserData(u), FILTER_USES_POWER)
UnitAddAbility(u,ABILITY_DISABLE_ATTACK)
udata.heldAttack = true
SetUnitMaxHP(u,BlzGetUnitMaxHP(u)*pdata.TowerHealthBonus)
SetTowerDamage(u,BlzGetUnitBaseDamage(u,0),pdata,udata)
AddUnitAnimationProperties(u,"upgrade",true)
end
})
AttachAbilityToUnitType(BUILDING_ID_SENTRY_TURRET,
NewAbility({
id = "upgradeBiggerBombs",
icon = "ReplaceableTextures\\CommandButtons\\BTNSentryExplosion",
name = "Bigger Bombs",
description = "Increases damage and health but increases the energy used."..STRING_UNLOCKS_FURTHER,
abil = DummyList.active[0][0],
cost = {250,250,250,250,250},
action = function(u,abl,udata)
local pdata, lvl, x, y = FinishUpgrade(u,abl,udata,"Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile")
for i, val in ipairs(abl.cost) do
if val ~= 0 then
abl.cost[i] = val * 5
end
end
if lvl == 3 then
abl.description = string.gsub(abl.description,STRING_UNLOCKS_FURTHER,"")
end
udata.powerPerShot = lvl * 50
SetTowerDamage(u,math.ceil(100*3^(lvl-1)-1)-GetTowerDamageBonus(udata),pdata,udata)
UpdateSpellDescription(pdata,abl,u)
RefreshHoverUnit()
SetUnitMaxHP(u,(50*lvl^1.5+250)*pdata.TowerHealthBonus)
BlzSetUnitName(u,BUILD_NAME_COLOR..udata.buildData.name.." "..ToRomanNumerals(lvl-1).."|r")
BlzSetUnitMaxMana(u,udata.powerPerShot)
end,
updateAction = updateUnitUpgrade,
unitUnique = true
})
)
end)
end
do--//TRADING POST
BUILDING_ID_TRADING_POST = FourCC("h00L")
AddAbilityInit(function()
AddUnitTypePathingFunc(BUILDING_ID_TRADING_POST,PathingTableTradingPost)
NewBuilding({
id = "tradingPost",
name = "Trading Post",
unitid = BUILDING_ID_TRADING_POST,
model = "Widgets\\Projector",
scale = 1,
cost = {5,5,5,5,5},
canRecycle = true,
range = 1024,
abil = DummyList.bookActive[2][1],
icon = "ReplaceableTextures\\CommandButtons\\BTNTradingPost",
description = "A structure used to trade minerals with other synthesizers.",
buildAction = function(u,udata,bdata)
BlzSetAbilityRealLevelField(BlzGetUnitAbility(u,AbilityList.setTradeTarget.abil),ABILITY_RLF_CAST_RANGE,0,bdata.range)
end
})
NewAbility({
id = "showTradeMineral",
icon = "ReplaceableTextures\\CommandButtons\\BTNMiningQuantity1",
name = "Trade Mineral",
description = "This is the mineral you are going to trade.",
abil = DummyList.passive[1][1],
unitUnique = true
})
NewAbility({
id = "changeMineralUp",
icon = "ReplaceableTextures\\CommandButtons\\BTNReplay-SpeedUp",
name = "Change Mineral Type",
description = "Changes the type of mineral you wish to trade.",
abil = DummyList.active[1][0],
action = function(u,abl,udata)
local lastMin = udata.tradeMineral
udata.tradeMineral = udata.tradeMineral + 1
if udata.tradeMineral > #ResourceTypes then
udata.tradeMineral = 1
end
abl = GetUnitAbility(u,AbilityList.showTradeMineral)
abl.icon = "ReplaceableTextures\\CommandButtons\\BTNMiningQuantity"..udata.tradeMineral
abl = GetUnitAbility(u,AbilityList.finishTrade)
abl.cost[udata.tradeMineral], abl.cost[lastMin] = abl.cost[lastMin], abl.cost[udata.tradeMineral]
UpdateAllSpellDescriptions(Player2Data(GetOwningPlayer(u)),u)
end
})
NewAbility({
id = "changeMineralDown",
icon = "ReplaceableTextures\\CommandButtons\\BTNReplay-SpeedDown",
name = "Change Mineral Type",
description = "Changes the type of mineral you wish to trade.",
abil = DummyList.active[1][2],
action = function(u,abl,udata)
local lastMin = udata.tradeMineral
udata.tradeMineral = udata.tradeMineral - 1
if udata.tradeMineral < 1 then
udata.tradeMineral = #ResourceTypes
end
abl = GetUnitAbility(u,AbilityList.showTradeMineral)
abl.icon = "ReplaceableTextures\\CommandButtons\\BTNMiningQuantity"..udata.tradeMineral
abl = GetUnitAbility(u,AbilityList.finishTrade)
abl.cost[udata.tradeMineral], abl.cost[lastMin] = abl.cost[lastMin], abl.cost[udata.tradeMineral]
UpdateAllSpellDescriptions(Player2Data(GetOwningPlayer(u)),u)
end
})
NewAbility({
id = "finishTrade",
icon = "ReplaceableTextures\\CommandButtons\\BTNFinishTrade",
name = "Send Minerals",
cost = {0,0,0,0,0},
description = "Sends minerals to the target player via their trading post.",
abil = DummyList.active[2][1],
action = function(u,abl,udata)
if UnitAlive(udata.tradeTarget) then
local pdata = Player2Data(GetOwningPlayer(u))
local pdata2 = Player2Data(GetOwningPlayer(udata.tradeTarget))
for i = 1, #abl.cost do
if abl.cost[i] > 0 then
pdata.resource[i] = pdata.resource[i] - abl.cost[i]
pdata2.resource[i] = pdata2.resource[i] + abl.cost[i]
MiningTextTag(pdata2,abl.cost[i], ResourceTypes[i].color, miningStartColor, GetUnitX(u),GetUnitY(u), GetUnitX(udata.tradeTarget), GetUnitY(udata.tradeTarget), 2)
UpdateResourceFrame(i)
end
end
end
end,
updateAction = updateActionCost,
unitUnique = true
})
NewAbility({
id = "changeTradeCostUp",
icon = "ReplaceableTextures\\CommandButtons\\BTNReplay-SpeedUp",
name = "Increase Amount",
abil = DummyList.active[2][0],
action = function(u,abl,udata)
udata.tradeLevel = math.min(udata.tradeLevel + 1,15)
local pdata = Player2Data(GetOwningPlayer(u))
abl = GetUnitAbility(u,AbilityList.finishTrade)
abl.cost[udata.tradeMineral] = math.floor(3^udata.tradeLevel)
UpdateAllSpellDescriptions(pdata,u)
end,
updateAction = function(pdata,abl,u,isLocal)
local udata = UnitData[GetUnitUserData(u)]
local s = StringToSpellDescription(string.format(
"Updates the amount sent for a total of %%d.",
math.floor(3^(udata.tradeLevel+1))))
if isLocal then
BlzSetAbilityExtendedTooltip(abl.abil,s,0)
end
end
})
NewAbility({
id = "changeTradeCostDown",
icon = "ReplaceableTextures\\CommandButtons\\BTNReplay-SpeedDown",
name = "Increase Amount",
abil = DummyList.active[2][2],
action = function(u,abl,udata)
udata.tradeLevel = math.max(udata.tradeLevel - 1,0)
local pdata = Player2Data(GetOwningPlayer(u))
abl = GetUnitAbility(u,AbilityList.finishTrade)
if udata.tradeLevel == 0 then
abl.cost[udata.tradeMineral] = 0
else
abl.cost[udata.tradeMineral] = math.floor(3^udata.tradeLevel)
end
UpdateAllSpellDescriptions(pdata,u)
end,
updateAction = function(pdata,abl,u,isLocal)
local udata = UnitData[GetUnitUserData(u)]
local s = StringToSpellDescription(string.format(
"Updates the amount sent for a total of %%d.",
math.floor(3^(udata.tradeLevel-1))))
if isLocal then
BlzSetAbilityExtendedTooltip(abl.abil,s,0)
end
end
})
AttachAbilityToUnitType(BUILDING_ID_TRADING_POST,
NewAbility({
id = "setTradeTarget",
icon = "ReplaceableTextures\\CommandButtons\\BTNTradingPost",
name = "Set Target",
description = "Target another Trading Post to initiate trading.",
abil = DummyList.target[0][0],
action = function(u,abl,udata,target)
if GetUnitTypeId(u) == GetUnitTypeId(target) and u ~=target and GetOwningPlayer(u) ~= GetOwningPlayer(target) then
if not udata.tradeAbilsAdded then
udata.tradeAbilsAdded = true
udata.tradeMineral = 1
udata.tradeLevel = 1
AddUnitAbility(u,AbilityList.showTradeMineral)
AddUnitAbility(u,AbilityList.changeMineralUp)
AddUnitAbility(u,AbilityList.changeMineralDown)
AddUnitAbility(u,AbilityList.finishTrade)
AddUnitAbility(u,AbilityList.changeTradeCostUp)
AddUnitAbility(u,AbilityList.changeTradeCostDown)
end
udata.tradeTarget = target
end
end
})
)
end)
end
do--//SYNTHESIZER
UNIT_ID_SYNTHESIZER = FourCC("H00C")
AddAbilityInit(function()
AttachFunctionToUnitType(UNIT_ID_SYNTHESIZER,function(u)
local p = GetOwningPlayer(u)
lvls = PlayerGetAbilityData(p,GetPlayerAbility(p,AbilityList.upgradeMineSpeed),"lvl") or 0
updateMineSpeed(u,lvls)
lvls = PlayerGetAbilityData(p,GetPlayerAbility(p,AbilityList.upgradeMineRange),"lvl") or 0
updateMineRange(u,lvls)
end)
NewAbility({
id = "examineGem",
icon = "ReplaceableTextures\\CommandButtons\\BTNExamineGem",
name = "Sample Mineral",
abil = DummyList.target[0][1],
description = "Take a sample of the target gem, allowing you to assess the quality and fortify the mineral. Mining a mineral allows you to automatically take a sample.",
action = function(u,abl,udata,target)
local tdata = UnitData[GetUnitUserData(target)]
if tdata.mineralType then
local pdata = Player2Data(GetOwningPlayer(u))
pdata.mineLast = GAME_TIME + 1
udata.sampleTarget = target
RemoveUnitAbility(u,AbilityList.examineGem)
AddUnitAbility(u,AbilityList.qualityIncrease)
end
end,
gainAction = function(u,abl)
BlzSetAbilityRealLevelField(BlzGetUnitAbility(u,abl.abil),ABILITY_RLF_CAST_RANGE,0,BlzGetUnitWeaponRealField(u,UNIT_WEAPON_RF_ATTACK_RANGE,0))
end
})
function GetMineralCost(mineral,o)
o = o or {}
local udata = UnitData[GetUnitUserData(mineral)]
for i = 1, #ResourceTypes, 1 do
if i == udata.mineralType then
o[i] = math.floor(udata.mineralQuality*udata.mineralQuality*7)
else
o[i] = 0
end
end
return o
end
function AttemptMineralUpgrade(pdata,target,tdata)
local cost = GetMineralCost(target,NewTable())
if CanAffordCost(pdata,cost,true) then
DeductCost(pdata,cost)
SetMineralQuality(target,tdata.mineralQuality+1,true)
end
ReleaseTable(cost)
end
function Synth_updateQualityIncrease(tbl)
local new = tbl.curTarget ~= tbl.udata.mineralTarget or tbl.curTarget ~= tbl.udata.sampleTarget
local last = tbl.pdata.mineLast and tbl.pdata.mineLast or 0
if tbl.u ~= tbl.pdata.unitSelectionMain then
RemoveUnitAbility(tbl.u,AbilityList.qualityIncrease)
AddUnitAbility(tbl.u,AbilityList.examineGem)
RemoveActionInTimerQueue()
ReleaseKeyTable(tbl)
elseif new or tbl.show == false then
tbl.curTarget = tbl.udata.mineralTarget or tbl.udata.sampleTarget
if last > GAME_TIME then
if tbl.curTarget then
tbl.show = true
local tdata = UnitData[GetUnitUserData(tbl.curTarget)]
local minType = tdata.mineralType
for i, val in ipairs(tbl.abl.cost) do
if i == minType then
tbl.abl.cost[i] = math.floor(tdata.mineralQuality*tdata.mineralQuality*5)
else
tbl.abl.cost[i] = 0
end
end
UpdateSpellDescriptionSimple(tbl.isLocal,tbl.abl)
updateActionCost(nil,tbl.abl,nil,tbl.isLocal)
else
RemoveUnitAbility(tbl.u,AbilityList.qualityIncrease)
AddUnitAbility(tbl.u,AbilityList.examineGem)
RemoveActionInTimerQueue()
ReleaseKeyTable(tbl)
end
end
elseif last < GAME_TIME or not tbl.curTarget then
RemoveUnitAbility(tbl.u,AbilityList.qualityIncrease)
AddUnitAbility(tbl.u,AbilityList.examineGem)
RemoveActionInTimerQueue()
ReleaseKeyTable(tbl)
end
end
NewAbility({
id = "qualityIncrease",
icon = "ReplaceableTextures\\CommandButtons\\BTNQualityIncrease",
name = "That's A Quality Gem",
description = "Fortify this mineral, allowing it sustain more intense mining.",
abil = DummyList.active[0][1],
cost = {0,0,0,0,0},
action = function(u,abl,udata)
local pdata = Player2Data(GetTriggerPlayer())
local target = udata.mineralTarget or udata.sampleTarget
if target then
DeductCost(pdata,abl.cost)
local tdata = UnitData[GetUnitUserData(target)]
local lvl = tdata.mineralQuality+1
abl.cost[tdata.mineralType] = math.floor(lvl*lvl*7)
UpdateSpellDescriptionSimple(MainSelectedUnit==u,abl)
updateActionCost(nil,abl,nil,MainSelectedUnit == u)
SetMineralQuality(target,lvl,true)
IssueTargetOrder(u,"smart",udata.mineralTarget)
soundUpgradeComplete(pdata == PlayerDataLocal)
end
end,
updateAction = function(pdata,abl,u,isLocal)
local tbl = NewKeyTable()
tbl.udata = UnitData[GetUnitUserData(u)]
tbl.pdata = pdata
tbl.abl = abl
tbl.u = u
tbl.isLocal = isLocal
tbl.show = false
AddActionInTimerQueue(0.1,Synth_updateQualityIncrease,tbl)
end,
playerUnique = true
})
local Page = {
function(u,show)
HideUnitAbility(u,show,
AbilityList.resourceCenter,
AbilityList.wall,
AbilityList.material,
AbilityList.pgen,
AbilityList.pdrill,
AbilityList.teleporterPad,
AbilityList.tradingPost
)
end,
function(u,show)
HideUnitAbility(u,show,
AbilityList.thunderTurret,
AbilityList.serpentTurret,
AbilityList.flamerTurret,
AbilityList.fusionTurret,
AbilityList.abominationTurret,
AbilityList.disruptorTurret,
AbilityList.gatlingTurret,
AbilityList.sentryTurret
)
end
}
local function UpdateBuildList(u,page,prev)
Page[prev](u,true)
Page[page](u,false)
end
AttachAbilityToUnitType(UNIT_ID_SYNTHESIZER,
AbilityList.resourceCenter,
AbilityList.wall,
AbilityList.material,
AbilityList.pgen,
AbilityList.pdrill,
AbilityList.teleporterPad,
AbilityList.thunderTurret,
AbilityList.serpentTurret,
AbilityList.flamerTurret,
AbilityList.fusionTurret,
AbilityList.disruptorTurret,
AbilityList.gatlingTurret,
AbilityList.sentryTurret,
AbilityList.examineGem,
AbilityList.tradingPost,
NewAbility({
id = "nextSynth",
icon = "ReplaceableTextures\\CommandButtons\\BTNNextPage",
name = "Next",
description = "Goes to the next building page.",
abil = DummyList.bookActive[0][2],
action = function(u,abl)
local pdata = Player2Data(GetOwningPlayer(u))
local page = UnitGetAbilityData(u,abl,"menuext") or 1
local prev = page
if page == 2 then
page = 1
else
page = page + 1
end
StopBuildingObject(pdata,false)
UnitSetAbilityData(u,abl,"menuext", page)
UpdateBuildList(u,page,prev)
end,
gainAction = function(u,abl)
UpdateBuildList(u,1,2)
end
}),
NewAbility({
id = "harvest",
icon = "ReplaceableTextures\\PassiveButtons\\PASResourceHarvest",
name = "Harvest",
description = "You can mine minerals by right clicking them. Using the attack order will allow you to destroy the mineral.",
abil = DummyList.passive[1][0]
})
)
end)
end
do--//MINERALS
function updateQualityTime(tbl)
if MainSelectedUnit ~= tbl.u then
ReleaseKeyTable(tbl)
RemoveActionInTimerQueue()
return
end
if tbl.quality ~= tbl.udata.mineralQuality then
tbl.abl.updateAction(tbl.pdata,tbl.abl,tbl.u,tbl.isLocal)
ReleaseKeyTable(tbl)
RemoveActionInTimerQueue()
return
end
tbl.qualTick = tbl.qualTick + 1
if tbl.qualTick > #tbl.name+1 then
tbl.qualTick = 1
end
BlzSetAbilityTooltip(tbl.abl.abil,"|cff00ffff"..tbl.name:sub(1,tbl.qualTick-1).."|r"..SPELL_NAME_COLOR..tbl.name:sub(tbl.qualTick).."|r",0)
end
AddAbilityInit(function()
NewAbility({
id = "mineralQuality",
icon = "ReplaceableTextures\\CommandButtons\\BTNEnchantedGemstone",
name = "Quality",
description = "This determines the amount of substance the mineral has and its ability to regenerate over time.",
abil = DummyList.passive[0][0],
updateAction = function(pdata, abl, u, isLocal)
local udata = UnitData[GetUnitUserData(u)]
local quality = udata.mineralQuality
if quality then
local tbl = NewKeyTable()
tbl.u=u
tbl.name=abl.name .." "..quality
tbl.quality=quality
tbl.udata=udata
tbl.abl=abl
tbl.pdata=pdata
tbl.isLocal=isLocal
tbl.qualTick = 0
AddActionInTimerQueue(math.max(.5/quality,0.05),updateQualityTime,tbl)
end
end
})
for _, id in ipairs(MINERAL_TYPES) do
AttachAbilityToUnitType(id,AbilityList.mineralQuality)
AttachFilterToUnitType(id, FILTER_MINERAL+FILTER_CAN_BE_HEALED+FILTER_BUILT)
AddUnitTypePathingFunc(id,PathingTableMineral)
end
end)
end
do
Default_Time_Of_Day_Length = 480
First_Day_Time = 200
Cycle_Length = 100
PLANET_NIGHT_PERCENT = .5
PLANET_MAX_NIGHTS = 0
CURRENT_NIGHT = 0
WAVE_SCALE = {damage = .2,health = .15, killed = 0, sent = 0}
DayNightActions = {}
IS_DAY_TIME = true
Adaption_CanTeleport = false
Adaption_SuperTeleport = false
Adaptation_Speed = false
Adaption_Air = false
AdaptionsCount = 0
function SetTimeOfDaySeconds(val)
SetTimeOfDayScale(Default_Time_Of_Day_Length/val)
end
function InitDayNight()
local t = CreateTrigger()
SuspendTimeOfDay(true)
SetTimeOfDaySeconds(First_Day_Time)
TriggerRegisterGameStateEvent(t, GAME_STATE_TIME_OF_DAY, EQUAL, 18)
TriggerRegisterGameStateEvent(t, GAME_STATE_TIME_OF_DAY, EQUAL, 6)
SetFloatGameState(GAME_STATE_TIME_OF_DAY, 6.01)
function Wave_SpawnFunc()
for i, waveData in ipairs(SEND_WAVES) do
if SEND_WAVES_REMAIN[i] > 0 then
SEND_WAVES_REMAIN[i] = SEND_WAVES_REMAIN[i] - 1
waveData:Spawn()
end
end
end
function Wave_UV_Damage()
local size = #WAVE_UNITS
Wave_UV_Damage_Amount = Wave_UV_Damage_Amount + 0.01
if size == 0 then
RemoveActionInTimerQueue()
return
end
for i = size, 1, -1 do
local target = WAVE_UNITS[i]
if target then
UnitDamageTarget(target,target,BlzGetUnitMaxHP(target)*Wave_UV_Damage_Amount,false,false,ATTACK_TYPE_CHAOS,DAMAGE_TYPE_DIVINE,WEAPON_TYPE_WHOKNOWS)
DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Items\\ResourceItems\\ResourceEffectTarget",GetUnitX(target),GetUnitY(target)))
end
end
end
Wave_ignoreTimeChange = false
function Wave_ResetIgnore()
Wave_ignoreTimeChange = false
ReleaseTimer()
end
TriggerAddAction(t,function()
if Wave_ignoreTimeChange then
return
end
Wave_ignoreTimeChange = true
TimerStart(NewTimer(),1,false,Wave_ResetIgnore)
IS_DAY_TIME = math.ceil(GetFloatGameState(GAME_STATE_TIME_OF_DAY)) == 6
if IS_DAY_TIME then
SetTimeOfDaySeconds(Cycle_Length * (1-PLANET_NIGHT_PERCENT)*2)
RemoveActionInTimerQueue(.5,Wave_SpawnFunc)
DestroyEffect(Wave_voidSpawner)
Wave_UV_Damage_Amount = 0
AddActionInTimerQueue(.5,Wave_UV_Damage)
BlzFrameSetText(WaveFrameBox, "")
if ModuloReal(CURRENT_NIGHT, 10) == 0 then
PerkPointGainEveryone("surviving "..CURRENT_NIGHT.." nights",math.floor(CURRENT_NIGHT/10))
end
for _, p in ipairs(PlayerUsers) do
local pdata = Player2Data(p)
if not UnitAlive(pdata.playerMiner) and pdata.Revival then
pdata.Revival = nil
local x,y = GetUnitX(pdata.playerMiner), GetUnitY(pdata.playerMiner)
ReviveHero(pdata.playerMiner,x,y, true)
if pdata == PlayerDataLocal then
ClearSelection()
SelectUnit(pdata.playerMiner,true)
SetCameraPosition(x,y)
DisplayTextToPlayer(LocalPlayer,0,0,"|cffc80000Revival has been depleted.|r")
end
end
end
local msg = false
if WAVE_SCALE.sent then
local ratio = WAVE_SCALE.sent / WAVE_SCALE.sentTotal
if ratio >= .99 then
msg = true
WAVE_SCALE.damage = WAVE_SCALE.damage * 1.5
elseif ratio <= .01 then
msg = true
WAVE_SCALE.health = WAVE_SCALE.health * 2
end
end
if not WAVE_SCALE.attacked and CURRENT_NIGHT > 0 then
if Adaption_CanTeleport then
if Adaptation_Speed then
Adaption_SuperTeleport = true
else
Adaptation_Speed = true
end
else
Adaption_CanTeleport = true
end
msg = true
end
if not Adaption_Air and WAVE_SCALE.killed > 10 then
Adaption_Air = true
msg = true
end
if msg then
AdaptionsCount = AdaptionsCount + 1
if AdaptionsCount == 20 then
Achievement.getForEveryone(Achievement["Mutation Complete"])
end
DisplayTextToPlayer(LocalPlayer,0,0,"|cffff0000The Thrul are adapting. . .|r")
end
if CURRENT_NIGHT == MAX_NIGHTS then
DisableTrigger(t)
if not BARRIER_BUILT then
Achievement.getForEveryone(Achievement["Who Needs Walls Anyway"])
end
end
else
SetTimeOfDaySeconds(Cycle_Length * PLANET_NIGHT_PERCENT *2)
for i, u in ipairs(AI_ATTACKED_TARGETS) do
UnitData[GetUnitUserData(u)].lastDamaged = GAME_TIME + 30
end
RemoveActionInTimerQueue(.5,Wave_UV_Damage)
local center_x,center_y = GetPortalCenter()
Wave_voidSpawner = AddSpecialEffect("Widgets\\VoidDisc",center_x,center_y)
CURRENT_NIGHT = CURRENT_NIGHT + 1
WAVE_SCALE.damage = WAVE_SCALE.damage + .8
WAVE_SCALE.health = WAVE_SCALE.health + .85
BlzFrameSetText(NightFrameNumber, "|cff5c7cffNight: "..CURRENT_NIGHT.."|r")
AddActionInTimerQueue(.5,Wave_SpawnFunc)
GetWaveTypes(CURRENT_NIGHT,math.floor(math.max(1, PLAYER_COUNT/2)+CURRENT_NIGHT/10))
local s = "|cffFFCC00"
local max = #SEND_WAVES
WAVE_SCALE.attacked = false
WAVE_SCALE.sent = 0
WAVE_SCALE.killed = 0
if Adaption_Air then
SEND_WAVES[1] = WAVE_TYPE_THRULARK
end
Adaption_Air = false
for i, waveData in ipairs(SEND_WAVES) do
WAVE_SCALE.sent = WAVE_SCALE.sent + waveData.spawned
s = s..i..". "..waveData.name.." ("..waveData.spawned..")"
if i ~= max then
s = s.."\n"
end
end
WAVE_SCALE.sentTotal = WAVE_SCALE.sent
BlzFrameSetText(WaveFrameBox, s.."|r")
if Adaption_CanTeleport then
GetSpawnCells(Adaption_SuperTeleport)
end
end
for _, actions in ipairs(DayNightActions) do
actions(IS_DAY_TIME)
end
end)
end
end
do
WAVE_TYPE = {
name = "???",
min_night = 0,
max_night = 0,
id = FourCC('hpea'),
weight = 1,
spawned = 1
}
wave_types_list = {}
SEND_WAVES = {}
SEND_WAVES_REMAIN = {}
MAX_WAVE_UNITS_TOTAL = 0
MAX_WAVE_UNITS_PER_PLAYER = 30
WAVE_AI_PERIOD = 0.01
WAVE_UNITS = {}
AI_ATTACKED_TARGETS = {}
function WAVE_TYPE:new(t)
t = t or {}
setmetatable(t,self)
self.__index = self
if t.min_night > 0 then
for i = t.min_night, t.max_night, 1 do
wave_types_list[i] = wave_types_list[i] or {}
table.insert(wave_types_list[i], t)
end
end
return t
end
function WAVE_TYPE:UnitCreate(player_id,x,y,facing)
local u = CreateUnit(player_id,self.id,x,y,facing)
local udata = UnitData[GetUnitUserData(u)]
local life = math.floor(BlzGetUnitMaxHP(u)*WAVE_SCALE.health)
BlzSetUnitName(u,self.name)
BlzSetUnitBaseDamage(u,math.floor(BlzGetUnitBaseDamage(u,0)*WAVE_SCALE.damage),0)
BlzSetUnitMaxHP(u,life)
SetWidgetLife(u,life)
udata.waveType = self
udata.lastAttacked = 0
udata.idleCD = 0
if self.effect_create then
self.effect_create(u,udata)
end
return u
end
function GetWaveTypes(which,typesMax)
while not wave_types_list[which] do
which = which - 1
end
ClearTable(SEND_WAVES)
ClearTable(SEND_WAVES_REMAIN)
local weight_table = NewTable()
local type_table = NewTable()
for i, waveData in ipairs(wave_types_list[which]) do
weight_table[i] = waveData.weight
type_table[i] = waveData
end
weight_table = alias_table:new(weight_table)
for i = 1, typesMax do
SEND_WAVES[i] = type_table[weight_table()]
SEND_WAVES_REMAIN[i] = SEND_WAVES[i].spawned
end
ReleaseTable(type_table)
weight_table:destroy()
end
local wave_list = {}
function AddWaveInit(func)
table.insert(wave_list,func)
end
function GetWavePlayer()
local max = MAX_WAVE_UNITS_PER_PLAYER
local ply
for i, p in ipairs(PlayerComps) do
local pdata = Player2Data(p)
if pdata.waveCount < max then
ply = p
max = pdata.waveCount
end
end
return ply
end
function GetWavePlayerIncrement()
local p = GetWavePlayer()
local pdata = Player2Data(p)
pdata.waveCount = pdata.waveCount + 1
return p
end
function WAVE_TYPE:Spawn()
local p = GetWavePlayer()
if p then --//NIL IF CAN'T SPAWN
local pdata = Player2Data(p)
local x,y,u
if #SpawnableCells > 0 then
local tbl = SpawnableCells[math.random(#SpawnableCells)]
local vec = tbl[1]
x,y = Cell2Cord(tbl[1].x, tbl[1].y)
u = self:UnitCreate(p,x,y,math.random(360))
if not IsInRange(GetUnitX(u),GetUnitY(u),x,y,64) then
x,y = GetPortalCenter()
x,y = x+math.random(-128,128), y+math.random(-128,128)
SetUnitX(u,x)
SetUnitY(u,y)
else
AssignAITarget(u,tbl[2].building)
end
else
x,y = GetPortalCenter()
x,y = x+math.random(-128,128), y+math.random(-128,128)
u = self:UnitCreate(p,x,y,math.random(360))
end
if Adaptation_Speed then
SetUnitMoveSpeed(u,522)
end
DestroyEffect(AddSpecialEffect("Widgets\\VoidSpawnEffect",GetUnitX(u),GetUnitY(u)))
AddUnitToSynthGroup(u,WAVE_UNITS)
pdata.waveCount = pdata.waveCount + 1
if math.random() < .5 then
soundTeleport1(true,nil,nil,u,math.random()*.2+.9)
else
soundTeleport2(true,nil,nil,u,math.random()*.2+.9)
end
end
end
function AssignAITarget(u,target)
local udata = UnitData[GetUnitUserData(u)]
if not target then
if not IsUnitType(u,UNIT_TYPE_FLYING) and #AI_ATTACKED_TARGETS > 0 and math.random() > .3 then
target = AI_ATTACKED_TARGETS[math.random(#AI_ATTACKED_TARGETS)]
else
target = PlayerUnits[math.random(#PlayerUnits)]
end
end
RemoveGuardPosition(u)
IssueTargetOrder(u,"attack",target)
udata.waveTarget = target
end
local AICur = 0
function WaveAI()
AICur = AICur + 1
if AICur > #WAVE_UNITS then
AICur = 1
end
local u = WAVE_UNITS[AICur]
if not u or not UnitAlive(u) then return end
local udata = UnitData[GetUnitUserData(u)]
local x,y = GetUnitX(u),GetUnitY(u)
local lastMoved = udata.isUnitMoving
udata.isUnitMoving = x ~= udata.xlast or y ~= udata.ylast
udata.moveTime = lastMoved ~= udata.isUnitMoving and GAME_TIME or udata.moveTime
udata.xlast, udata.ylast = x,y
if not udata.waveTarget or not UnitAlive(udata.waveTarget) then
AssignAITarget(u)
elseif udata.idleCD < GAME_TIME and not udata.isUnitMoving and udata.lastAttacked < GAME_TIME then
udata.idleCD = GAME_TIME + 3
if udata.waveType.effect_idle then
if udata.waveType.effect_idle(u,udata) then
AssignAITarget(u)
end
else
AssignAITarget(u)
end
end
end
function ClearAIAttackedTargets()
local count = #AI_ATTACKED_TARGETS
for i = count, 1, -1 do
local u = AI_ATTACKED_TARGETS[i]
local udata = UnitData[GetUnitUserData(u)]
if udata.lastDamaged < GAME_TIME or not UnitAlive(u) then
RemoveUnitFromSynthGroup(u,AI_ATTACKED_TARGETS)
udata.aiTarget = nil
count = count - 1
end
end
end
function InitWaves()
SynthGroupAddNewAction(WAVE_UNITS,function(u)
if #WAVE_UNITS == 1 then
AddActionInTimerQueue(WAVE_AI_PERIOD,WaveAI)
end
end)
SynthGroupAddRemovalAction(WAVE_UNITS,function(u)
if #WAVE_UNITS == 0 then
RemoveActionInTimerQueue(WAVE_AI_PERIOD,WaveAI)
end
end)
SynthGroupAddNewAction(AI_ATTACKED_TARGETS,function(u)
if #AI_ATTACKED_TARGETS == 1 then
AddActionInTimerQueue(10,ClearAIAttackedTargets)
end
end)
SynthGroupAddRemovalAction(AI_ATTACKED_TARGETS,function(u)
if #AI_ATTACKED_TARGETS == 0 then
RemoveActionInTimerQueue(10,ClearAIAttackedTargets)
end
end)
soundTeleport1 = SOUND:new({
path = "Sounds\\Teleport1",
duration = .731,
is3D = true
})
soundTeleport2 = SOUND:new({
path = "Sounds\\Teleport2",
duration = .731,
is3D = true
})
ActionAdd(AttackActions,true,function(data)
local wType = data.sourceData.waveType
if wType then
if data.victimOwner.user and not data.victimData.aiTarget then
WAVE_SCALE.attacked = true
data.victimData.aiTarget = true
AddUnitToSynthGroup(data.victim,AI_ATTACKED_TARGETS)
data.victimData.lastDamaged = GAME_TIME+60
end
data.sourceData.lastAttacked = GAME_TIME+5
if wType.effect_attack then
wType.effect_attack(data)
end
end
end)
ActionAdd(DeathActions,true,function(victim,killer,udata)
udata = UnitData[udata]
local wType = udata.waveType
local pdata = Player2Data(GetOwningPlayer(victim))
local pdata2 = Player2Data(GetOwningPlayer(killer))
if wType then
WAVE_SCALE.sent = WAVE_SCALE.sent - 1
pdata.waveCount = pdata.waveCount - 1
if wType.effect_death and victim~=killer then
wType.effect_death(victim,killer,udata)
end
if pdata2 and pdata2.WaveKills then
pdata2.WaveKills = pdata2.WaveKills + 1
if pdata2.WaveKills == 10000 then
Achievement.get(pdata2,Achievement["Monster Slayer"])
end
end
elseif pdata and pdata2 and pdata.user and not pdata2.user then
WAVE_SCALE.killed = WAVE_SCALE.killed + 1
end
end)
for i, func in ipairs(wave_list) do
func()
end
for i, p in ipairs(PlayerComps) do
Player2Data(p).waveCount = 0
MAX_WAVE_UNITS_TOTAL = MAX_WAVE_UNITS_TOTAL + MAX_WAVE_UNITS_PER_PLAYER
end
end
end
do
wave_fire_running = false
wave_fire_tbl = {}
function Wave_Fire_Damage_Period()
for i = #wave_fire_tbl, 1, -1 do
local u = wave_fire_tbl[i]
local udata = UnitData[GetUnitUserData(u)]
if not UnitAlive(udata.waveFireSource) then
udata.waveFireSource = u
end
UnitDamageTarget(udata.waveFireSource,u,udata.waveFire,false,false,ATTACK_TYPE_MAGIC,DAMAGE_TYPE_FIRE,WEAPON_TYPE_WHOKNOWS)
udata.waveFire = udata.waveFire * .65
if udata.waveFire < .01 then
RemoveUnitFromSynthGroup(u,wave_fire_tbl)
end
end
end
function Wave_Apply_Fire_Damage(data)
if not data.victimData.waveFire then
if #wave_fire_tbl==0 then
AddActionInTimerQueue(1,Wave_Fire_Damage_Period)
end
AddUnitToSynthGroup(data.victim,wave_fire_tbl)
data.victimData.waveFire = 0
if IsUnitType(data.victim,UNIT_TYPE_STRUCTURE) then
data.victimData.waveFireSFX = AddSpecialEffect("Environment\\LargeBuildingFire\\LargeBuildingFire1",GetUnitX(data.victim),GetUnitY(data.victim))
else
data.victimData.waveFireSFX = AddSpecialEffectTarget("Environment\\LargeBuildingFire\\LargeBuildingFire2",data.victim,"origin")
end
end
data.victimData.waveFireSource = data.source
data.victimData.waveFire = data.victimData.waveFire + BlzGetUnitBaseDamage(data.source,0)*.15
end
function wave_SpawnMove(tbl)
tbl.durCur = math.min(tbl.durCur + 0.03125,tbl.dur)
local t = tbl.durCur/tbl.dur
SetUnitFlyHeight(tbl.u,quadBezier(t, tbl.z1, tbl.height, tbl.z2),0)
t = easeOutQuint(t)
if tbl.durCur == tbl.dur then
UnitRemoveAbility(tbl.u,ABILITY_ID_GHOST)
BlzPauseUnitEx(tbl.u,false)
SetUnitPosition(tbl.u,tbl.x2,tbl.y2)
RemoveActionInTimerQueue()
if not IsInRange(GetUnitX(tbl.u),GetUnitY(tbl.u),tbl.x2,tbl.y2,64) then
RemoveUnit(tbl.u)
end
ReleaseKeyTable(tbl)
else
SetUnitX(tbl.u,lerp(t,tbl.x1,tbl.x2))
SetUnitY(tbl.u,lerp(t,tbl.y1,tbl.y2))
end
end
function SpawnOnDeath(victim,id,amount,range,height,scale,dur)
local x,y = GetUnitX(victim), GetUnitY(victim)
local cells = GetCellsInRange(x,y,range,IsCellEmpty)
local scaleCur = UnitData[GetUnitUserData(victim)].scale or BlzGetUnitRealField(victim,UNIT_RF_SCALING_VALUE)
local hp = math.floor(BlzGetUnitMaxHP(victim)*scale)
local dmg = math.floor(BlzGetUnitBaseDamage(victim,0)*scale)
scaleCur = scaleCur * scale
if cells[1] then
amount = math.min(amount,#cells)
for i = 1, amount, 1 do
local tbl = NewKeyTable()
local vec = cells[math.random(#cells)]
table.removeElement(cells,vec)
local angle = AngleBetween(x,y,vec.x,vec.y)
tbl.u = CreateUnit(GetWavePlayerIncrement(),id,x,y,bj_RADTODEG*angle)
tbl.x1 = x
tbl.y1 = y
tbl.z1 = LocationZ(x,y)
tbl.x2 = vec.x
tbl.y2 = vec.y
tbl.z2 = LocationZ(vec.x,vec.y)
tbl.dur = dur
tbl.durCur = 0
tbl.height = height
AddUnitToSynthGroup(tbl.u,WAVE_UNITS)
UnitData[GetUnitUserData(tbl.u)].waveType = {}
SetUnitScale(tbl.u,scaleCur,scaleCur,scaleCur)
BlzSetUnitMaxHP(tbl.u,hp)
BlzSetUnitBaseDamage(tbl.u,dmg,0)
UnitAddAbility(tbl.u,ABILITY_ID_FLIGHT)
UnitRemoveAbility(tbl.u,ABILITY_ID_FLIGHT)
SetUnitAnimation(tbl.u,"attack")
UnitAddAbility(tbl.u,ABILITY_ID_GHOST)
BlzPauseUnitEx(tbl.u,true)
SetUnitX(tbl.u,x)
SetUnitY(tbl.u,y)
ReleaseVector(vec)
AddActionInTimerQueue(0.03125,wave_SpawnMove,tbl)
end
end
ReleaseTable(cells)
end
local function wave_finishAMove(tbl)
SetUnitAttackRange(tbl[1],tbl[2],0)
RemoveActionInTimerQueue()
ReleaseTable(tbl)
end
function attack_move_forward(data)
if not data.sourceData.lastAMove or data.sourceData.lastAMove <= GAME_TIME then
data.sourceData.lastAMove = GAME_TIME + 5
local tbl = NewTable()
tbl[1] = data.source
tbl[2] = BlzGetUnitWeaponRealField(data.source, UNIT_WEAPON_RF_ATTACK_RANGE, 0)
SetUnitAttackRange(data.source,0,0)
AddActionInTimerQueue(1,wave_finishAMove,tbl)
end
end
function attack_frenzy(data)
if math.random() < .10 and GetUnitAbilityLevel(data.source,FourCC('A015')) == 0 then
local u = data.source
soundFrenzy(true,nil,nil,u)
UnitAddAbility(u,FourCC('A015'))
UnitAddAbility(u,FourCC('A017'))
local sfx = AddSpecialEffectTarget("Widgets\\Frenzy",u,"origin")
local t = NewTimer()
TimerStart(t, 10, false, function()
DestroyEffect(sfx)
UnitRemoveAbility(u,FourCC('A015'))
UnitRemoveAbility(u,FourCC('A017'))
ReleaseTimer(t)
end)
end
end
function wave_growTime(tbl)
tbl.dur = math.min(tbl.dur + 0.03125,tbl.time)
local interScale = lerp(tbl.ease(tbl.dur/tbl.time),tbl.scaleCur,tbl.scale)
SetUnitScale(tbl.u,interScale,interScale,interScale)
if tbl.dur == tbl.time then
ReleaseKeyTable(tbl)
RemoveActionInTimerQueue()
end
end
function SetScaleTimed(u,scale,time,ease)
local udata = UnitData[GetUnitUserData(u)]
local tbl = NewKeyTable()
tbl.scaleCur = udata.scale or BlzGetUnitRealField(u,UNIT_RF_SCALING_VALUE)
tbl.scale = scale
tbl.time = time
tbl.dur = 0
tbl.u = u
tbl.ease = ease
AddActionInTimerQueue(0.03125,wave_growTime,tbl)
end
function attack_grow_cannibal(data)
if data.victimData.waveType and data.victimData.waveType == data.sourceData.waveType then
local u = data.source
local scale = (data.sourceData.scale or BlzGetUnitRealField(u,UNIT_RF_SCALING_VALUE)) + .05
local dmg = BlzGetUnitBaseDamage(u,0)
data.sourceData.scale = scale
SetScaleTimed(u,scale,.25,easeOutCubic)
BlzSetUnitMaxHP(u,math.round(BlzGetUnitMaxHP(u) + dmg))
BlzSetUnitMaxHP(data.victim,math.round(BlzGetUnitMaxHP(data.victim) - dmg))
local dmgTar = math.round(BlzGetUnitBaseDamage(data.victim,0) * .5)
BlzSetUnitBaseDamage(u,dmg + dmgTar,0)
BlzSetUnitBaseDamage(data.victim,dmgTar,0)
SetWidgetLife(u,GetWidgetLife(u) + dmg)
scale = (data.victimData.scale or BlzGetUnitRealField(data.victim,UNIT_RF_SCALING_VALUE)) - .05
data.victimData.scale = scale
SetScaleTimed(u,scale,.25,easeOutCubic)
if scale < .5 then
KillUnit(data.victim)
end
end
end
local function wave_growExplodeTimed(tbl)
tbl.time = math.min(tbl.time + 0.03125,1.25)
local endGrow = false
if not UnitAlive(tbl.u) then
endGrow = true
end
if tbl.time == 1.25 then
endGrow = true
local hp = GetWidgetLife(tbl.u)
KillUnit(tbl.u)
ShowUnit(tbl.u,false)
local g = NewGroup()
local p = GetOwningPlayer(tbl.u)
GroupEnumUnitsInRange(g,tbl.x,tbl.y,tbl.range,nil)
local target
local size = BlzGroupGetSize(g)-1
for i = 0, size, 1 do
target = BlzGroupUnitAt(g,i)
if IsUnitEnemy(target,p) then
UnitDamageTarget(tbl.u,target,hp*.75,false,false,ATTACK_TYPE_CHAOS,DAMAGE_TYPE_ACID,WEAPON_TYPE_WHOKNOWS)
end
end
ReleaseGroup(g)
DestroyEffect(AddSpecialEffect("Missiles\\BloodySplat",tbl.x,tbl.y))
DestroyEffect(AddSpecialEffect("Missiles\\DarknessBomb",tbl.x,tbl.y))
end
local lerpScale = SineWave(1,.25,tbl.time*10,0)
local scale = tbl.scaleCur + lerpScale*.5
SetUnitScale(tbl.u,scale,scale,scale)
SetUnitVertexColor(tbl.u,math.floor(lerpScale * 127)+255,128,128,255)
if endGrow then
DestroyWarningEffect(tbl.warnSFX)
ReleaseKeyTable(tbl)
RemoveActionInTimerQueue()
end
end
function attack_grow(data)
local u = data.source
local scaleCur = data.sourceData.scale or BlzGetUnitRealField(u,UNIT_RF_SCALING_VALUE)
local scale = scaleCur + .1
local hp = BlzGetUnitMaxHP(u)
data.sourceData.scale = scale
BlzSetUnitMaxHP(u,math.floor(hp*1.1))
SetWidgetLife(u,GetWidgetLife(u) + hp*1.1-hp)
if scale < 2 then
SetScaleTimed(u,scale,.5,easeOutCubic)
else
local tbl = NewKeyTable()
tbl.u=u
tbl.time = 0
tbl.scaleCur=scaleCur
tbl.x, tbl.y = GetUnitX(u),GetUnitY(u)
tbl.range = 98 * scale
tbl.warnSFX = CreateWarningEffect(tbl.x,tbl.y,tbl.range)
AddActionInTimerQueue(0.03125,wave_growExplodeTimed,tbl)
PauseUnit(u,true)
end
end
function cannibal_idle(u,udata)
local g = NewGroup()
local x,y = GetUnitX(u),GetUnitY(u)
local dist
GroupEnumUnitsInRange(g,x,y,256,nil)
GroupRemoveUnit(g,u)
local final_target
for i = 0, BlzGroupGetSize(g)-1, 1 do
local target = BlzGroupUnitAt(g,i)
if UnitAlive(target) and UnitData[GetUnitUserData(target)].waveType == udata.waveType then
local dist2 = FastDistance(x,y,GetUnitX(target),GetUnitY(target))
if not dist or dist2 < dist then
dist = dist2
final_target = target
end
end
end
ReleaseGroup(g)
if final_target then
IssueTargetOrder(u,"attack",final_target)
udata.waveTarget = final_target
return false
else
return true
end
end
local function endTransform(tbl)
if UnitAlive(tbl.target) then
if tbl.duration == 0 then
soundWetSplat(true,nil,nil,tbl.target)
KillUnit(tbl.target)
ShowUnit(tbl.target,false)
local pdata = Player2Data(GetOwningPlayer(tbl.target))
tbl.target = tbl.newWaveType:UnitCreate(pdata.p,GetUnitX(tbl.target),GetUnitY(tbl.target),GetUnitFacing(tbl.target))
AddUnitToSynthGroup(tbl.target,WAVE_UNITS)
pdata.waveCount = pdata.waveCount + 1
DestroyEffect(tbl.egg)
if tbl.periodFunc then
tbl.periodFunc(tbl)
end
RemoveActionInTimerQueue()
ReleaseKeyTable(tbl)
else
tbl.duration = tbl.duration - 1
if tbl.periodFunc then
tbl.periodFunc(tbl)
end
end
else
DestroyEffect(tbl.egg)
RemoveActionInTimerQueue()
ReleaseKeyTable(tbl)
end
end
function TransformUnitInEgg(target, duration, newWaveType,periodFunc)
local scale = UnitData[GetUnitUserData(target)].scale or BlzGetUnitRealField(target,UNIT_RF_SCALING_VALUE)
PauseUnit(target,true)
local egg = AddSpecialEffect("Widgets\\ZergEgg",GetUnitX(target),GetUnitY(target))
BlzSetSpecialEffectScale(egg,scale)
soundEggSpawn(true,nil,nil,target)
SetUnitVertexColor(target,255,255,255,0)
local tbl = NewKeyTable()
tbl.target = target
tbl.newWaveType = newWaveType
tbl.egg = egg
tbl.duration = duration
tbl.periodFunc = periodFunc
tbl.scale = scale
AddActionInTimerQueue(1,endTransform,tbl)
end
function EggPullsInNearbyKin(tbl)
if tbl.duration == 0 and tbl.hp then
SetUnitMaxHP(tbl.target,tbl.hp)
SetUnitScale(tbl.target,tbl.scale,tbl.scale,tbl.scale)
BlzSetUnitBaseDamage(tbl.target,tbl.dmg,0)
elseif tbl.scale < 2 then
local g = NewGroup()
local x,y = GetUnitX(tbl.target),GetUnitY(tbl.target)
local x2,y2
local wtype = UnitData[GetUnitUserData(tbl.target)].waveType
GroupEnumUnitsInRange(g,x,y,512,nil)
GroupRemoveUnit(g,tbl.target)
local size = BlzGroupGetSize(g)-1
local tdata
local target
for i = 0, size, 1 do
target = BlzGroupUnitAt(g,i)
tdata = UnitData[GetUnitUserData(target)]
if UnitAlive(target) and tdata.waveType and tdata.waveType == wtype then
x2,y2 = GetUnitX(target),GetUnitY(target)
if IsInRange(x,y,x2,y2,96) then
tbl.dmg = tbl.dmg and tbl.dmg + BlzGetUnitBaseDamage(target,0) or BlzGetUnitBaseDamage(tbl.target,0) + BlzGetUnitBaseDamage(target,0)
tbl.hp = tbl.hp and tbl.hp + GetWidgetLife(target) or GetWidgetLife(tbl.target) + GetWidgetLife(target)
DestroyEffect(AddSpecialEffect("Missiles\\BloodySplat",x2,y2))
KillUnit(target)
ShowUnit(target,false)
tbl.scale = tbl.scale + .1
SetUnitMaxHP(tbl.target,tbl.hp)
BlzSetSpecialEffectScale(tbl.egg,tbl.scale)
elseif not tdata.moveLast or tdata.moveLast < GAME_TIME then
tdata.moveLast = GAME_TIME + 5
IssueTargetOrder(target,"move",tbl.target)
end
end
end
ReleaseGroup(g)
end
end
function dire_arzih_idle(u,udata)
if udata.moveTime + 2 < GAME_TIME then
udata.idleCD = GAME_TIME + 30
TransformUnitInEgg(u,8,WAVE_TYPE_MUTANT_ARZIH,EggPullsInNearbyKin)
return false
end
return true
end
function BoostOnDeath(victim,killer,udata)
local g = NewGroup()
GroupEnumUnitsInRange(g,GetUnitX(victim),GetUnitY(victim),256,nil)
GroupRemoveUnit(g,victim)
local size = BlzGroupGetSize(g)-1
local tdata
local target
for i = 0, size, 1 do
target = BlzGroupUnitAt(g,i)
tdata = UnitData[GetUnitUserData(target)]
if UnitAlive(target) and tdata.waveType and tdata.waveType == udata.waveType then
tdata.baseHP = tdata.baseHP or BlzGetUnitMaxHP(target)
tdata.baseDMG = tdata.baseDMG or BlzGetUnitBaseDamage(target,0)
tdata.scale = tdata.scale or BlzGetUnitRealField(target,UNIT_RF_SCALING_VALUE)
tdata.scale = math.min(tdata.scale + .2,2)
if tdata.scale <= 2 then
SetUnitScale(target,tdata.scale,tdata.scale,tdata.scale)
DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Other\\AcidBomb\\BottleMissile",GetUnitX(target),GetUnitY(target)))
SetUnitMaxHP(target,math.floor(tdata.baseHP*tdata.scale))
BlzSetUnitBaseDamage(target,math.floor(tdata.baseDMG*tdata.scale),0)
end
end
end
ReleaseGroup()
end
function wave_DamageOnDeath(u,range,damage)
local g = NewGroup()
local p = GetOwningPlayer(u)
GroupEnumUnitsInRange(g,GetUnitX(u),GetUnitY(u),range,nil)
local target
local size = BlzGroupGetSize(g)-1
for i = 0, size, 1 do
target = BlzGroupUnitAt(g,i)
if IsUnitEnemy(target,p) then
UnitDamageTarget(u,target,damage,false,false,ATTACK_TYPE_CHAOS,DAMAGE_TYPE_ACID,WEAPON_TYPE_WHOKNOWS)
end
end
ReleaseGroup(g)
end
GargantuanWarningCD = 0
function GargantuanWarning()
if GargantuanWarningCD < GAME_TIME then
GargantuanWarningCD = GAME_TIME + 10
DisplayTextToPlayer(LocalPlayer,0,0,"|cffff0000WARNING: GARGANTUAN CLASS THRUL DETECTED")
soundGargantuan()
end
end
ABILITY_ID_DAMAGE_SELF = FourCC('A01M')
ABILITY_ID_PULVERIZE = FourCC('A01O')
AddWaveInit(function()
SynthGroupAddRemovalAction(wave_fire_tbl,function(u)
local udata = UnitData[GetUnitUserData(u)]
DestroyEffect(udata.waveFireSFX)
udata.waveFire = nil
udata.waveFireSFX = nil
udata.waveFireSource = nil
if #wave_fire_tbl==0 then
RemoveActionInTimerQueue(1,Wave_Fire_Damage_Period)
end
end)
WAVE_TYPE:new({
name = "Larva",
min_night = 1,
max_night = 3,
id = FourCC('u000'),
weight = 1,
spawned = 30,
effect_attack = attack_grow
})
WAVE_TYPE:new({
name = "Cannibalistic Larva",
min_night = 2,
max_night = 4,
id = FourCC('u000'),
weight = .5,
spawned = 50,
effect_idle = cannibal_idle,
effect_attack = attack_grow_cannibal,
effect_create = function(u,udata)
AttachUnitEffect(u,AddSpecialEffectTarget("Abilities\\Spells\\Orc\\Bloodlust\\BloodlustTarget",u,"head"))
end
})
soundFrenzy = SOUND:new({
path = "Sounds\\Frenzy",
duration = 2.351,
is3D = true,
volume = 50
})
soundGargantuan = SOUND:new({
path = "Sounds\\GargantuanThrul",
duration = 3
})
WAVE_TYPE:new({
name = "Arzih",
min_night = 3,
max_night = 6,
id = FourCC('u001'),
weight = 1,
spawned = 30,
effect_attack = attack_frenzy
})
WAVE_TYPE:new({
name = "Dire Arzih",
min_night = 5,
max_night = 7,
id = FourCC('u001'),
weight = .25,
spawned = 30,
effect_idle = dire_arzih_idle,
effect_create = function(u)
BlzSetUnitBaseDamage(u,math.floor(BlzGetUnitBaseDamage(u,0)*.75),0)
SetUnitMaxHP(u,math.floor(BlzGetUnitMaxHP(u)*.75))
AttachUnitEffect(u,AddSpecialEffectTarget("Abilities\\Weapons\\NecromancerMissile\\NecromancerMissile",u,"head"))
end
})
soundEggSpawn = SOUND:new({
path = "Sounds\\EggSpawn",
duration = 3.288,
is3D = true
})
soundWetSplat = SOUND:new({
path = "Sounds\\WetSplat",
duration = 1.296,
is3D = true
})
WAVE_TYPE_MUTANT_ARZIH = WAVE_TYPE:new({
name = "Mutant Arzih",
id = FourCC('u002'),
effect_create = function(u)
soundFrenzy(true,nil,nil,u)
UnitAddAbility(u,FourCC('A015'))
UnitAddAbility(u,FourCC('A017'))
AttachUnitEffect(u,AddSpecialEffectTarget("Widgets\\Frenzy",u,"origin"))
end
})
WAVE_TYPE:new({
name = "Arzih Rider",
id = FourCC('u001'),
min_night = 3,
max_night = 7,
weight = .1,
spawned = 10,
effect_create = function(u)
BlzSetUnitBaseDamage(u,math.floor(BlzGetUnitBaseDamage(u,0)*3),0)
SetUnitMaxHP(u,math.floor(BlzGetUnitMaxHP(u)*3))
BlzSetUnitRealField(u,UNIT_RF_HIT_POINTS_REGENERATION_RATE,BlzGetUnitRealField(u,UNIT_RF_HIT_POINTS_REGENERATION_RATE)+GetWidgetLife(u)*.05)
AttachUnitEffect(u,AddSpecialEffectTarget("units\\creeps\\Zombie\\Zombie",u,"chest"))
end
})
WAVE_TYPE:new({
name = "Thrulling",
id = FourCC('u003'),
min_night = 5,
max_night = 9,
weight = .5,
spawned = 30,
effect_create = function(u)
AttachUnitEffect(u,AddSpecialEffectTarget("Abilities\\Spells\\Human\\Banish\\BanishTarget",u,"chest"))
SetUnitVertexColor(u,255,255,255,100)
end,
effect_death = BoostOnDeath
})
WAVE_TYPE:new({
name = "???",
id = FourCC('u004'),
min_night = 6,
max_night = 9,
weight = .1,
spawned = 1,
effect_create = GargantuanWarning
})
WAVE_TYPE:new({
name = "Thrall",
id = FourCC('u005'),
min_night = 9,
max_night = 14,
weight = .5,
spawned = 30,
effect_create = function(u)
UnitAddAbility(u,ABILITY_ID_DAMAGE_SELF)
end,
effect_death = function(victim,killer,udata)
BoostOnDeath(victim,killer,udata)
wave_DamageOnDeath(victim,128,BlzGetUnitMaxHP(victim)*.25)
end
})
WAVE_TYPE:new({
name = "Thrul",
id = FourCC('u006'),
min_night = 9,
max_night = 14,
weight = .6,
spawned = 30,
effect_create = function(u)
UnitAddAbility(u,ABILITY_ID_PULVERIZE)
local abil = BlzGetUnitAbility(u,ABILITY_ID_PULVERIZE)
BlzSetAbilityRealLevelField(abil,ABILITY_RLF_DAMAGE_DEALT_WAR2,0,BlzGetUnitBaseDamage(u,0)*2)
SetUnitColor(u,ConvertPlayerColor(math.random(6)))
end
})
WAVE_TYPE:new({
name = "Massive Larva",
min_night = 11,
max_night = 17,
id = FourCC('u000'),
weight = 1,
spawned = 15,
effect_attack = function(data)
if BlzGetUnitMaxHP(data.source)*.5 >= GetWidgetLife(data.source) then
data.sourceData.scale = 3
attack_grow(data)
end
end,
effect_create = function(u)
local udata = UnitData[GetUnitUserData(u)]
udata.scale = 2
SetUnitScale(u,2,2,2)
BlzSetUnitBaseDamage(u,math.floor(BlzGetUnitBaseDamage(u,0)*3),0)
SetUnitMaxHP(u,math.floor(BlzGetUnitMaxHP(u)*3))
AttachUnitEffect(u,AddSpecialEffectTarget("Abilities\\Weapons\\CryptFiendMissile\\CryptFiendMissileTarget",u,"chest"))
AttachUnitEffect(u,AddSpecialEffectTarget("Abilities\\Weapons\\CryptFiendMissile\\CryptFiendMissileTarget",u,"head"))
end
})
WAVE_TYPE:new({
name = "Arzirth",
id = FourCC('u007'),
min_night = 14,
max_night = 20,
weight = .8,
spawned = 30,
effect_attack = attack_move_forward
})
WAVE_TYPE:new({
name = "Mutant Spider",
id = FourCC('u008'),
min_night = 16,
max_night = 22,
weight = .7,
spawned = 45,
effect_death = function(victim,killer,udata)
SpawnOnDeath(victim,GetUnitTypeId(victim),3,384,356,.6,1.25)
end
})
WAVE_TYPE:new({
name = "Burning Rider",
id = FourCC('u001'),
min_night = 15,
max_night = 24,
weight = .3,
spawned = 10,
effect_create = function(u)
BlzSetUnitBaseDamage(u,math.floor(BlzGetUnitBaseDamage(u,0)*3),0)
SetUnitMaxHP(u,math.floor(BlzGetUnitMaxHP(u)*3))
BlzSetUnitRealField(u,UNIT_RF_HIT_POINTS_REGENERATION_RATE,BlzGetUnitRealField(u,UNIT_RF_HIT_POINTS_REGENERATION_RATE)+GetWidgetLife(u)*.05)
AttachUnitEffect(u,AddSpecialEffectTarget("units\\creeps\\Zombie\\Zombie",u,"chest"))
AttachUnitEffect(u,AddSpecialEffectTarget("Environment\\LargeBuildingFire\\LargeBuildingFire1",u,"origin"))
end,
effect_attack = Wave_Apply_Fire_Damage
})
WAVE_TYPE:new({
name = "Burning Arzirth",
id = FourCC('u007'),
min_night = 18,
max_night = 26,
weight = .3,
spawned = 25,
effect_attack = function(data)
attack_move_forward(data)
Wave_Apply_Fire_Damage(data)
end,
effect_create = function(u)
BlzSetUnitWeaponStringField(u,UNIT_WEAPON_SF_ATTACK_PROJECTILE_ART,0,"PrismBeam_Initiate.mdx")
BlzSetUnitRealField(u,UNIT_RF_HIT_POINTS_REGENERATION_RATE,BlzGetUnitRealField(u,UNIT_RF_HIT_POINTS_REGENERATION_RATE)+GetWidgetLife(u)*.05)
AttachUnitEffect(u,AddSpecialEffectTarget("Environment\\LargeBuildingFire\\LargeBuildingFire1",u,"origin"))
end
})
WAVE_TYPE:new({
name = "Thrul Warrior",
id = FourCC('u009'),
min_night = 22,
max_night = 30,
weight = .5,
spawned = 50,
effect_idle = cannibal_idle,
effect_attack = attack_grow_cannibal,
effect_death = function(victim,killer,udata)
local scale = udata.scale or BlzGetUnitRealField(victim,UNIT_RF_SCALING_VALUE)
wave_DamageOnDeath(victim,98 * scale,BlzGetUnitMaxHP(victim)*.1)
local sfx = AddSpecialEffect("Abilities\\Spells\\Undead\\AnimateDead\\AnimateDeadTarget",GetUnitX(victim),GetUnitY(victim))
BlzSetSpecialEffectScale(sfx,scale)
DestroyEffect(sfx)
end
})
WAVE_TYPE_THRULARK = WAVE_TYPE:new({
name = "Thrulark",
id = FourCC('u00A'),
effect_create = function(u)
soundFrenzy(true,nil,nil,u)
UnitAddAbility(u,FourCC('A015'))
UnitAddAbility(u,FourCC('A017'))
AttachUnitEffect(u,AddSpecialEffectTarget("Widgets\\Frenzy",u,"origin"))
end,
spawned = 50
})
WAVE_TYPE:new({
name = "Mutant Thrul",
id = FourCC('u009'),
min_night = 22,
max_night = 30,
weight = 0.4,
spawned = 30,
effect_create = function(u)
BlzSetUnitRealField(u,UNIT_RF_HIT_POINTS_REGENERATION_RATE,BlzGetUnitRealField(u,UNIT_RF_HIT_POINTS_REGENERATION_RATE)+GetWidgetLife(u)*.075)
AttachUnitEffect(u,AddSpecialEffectTarget("Environment\\LargeBuildingFire\\LargeBuildingFire1",u,"origin"))
end,
effect_attack = function(data)
Wave_Apply_Fire_Damage(data)
end,
effect_death = function(victim,killer,udata)
SpawnOnDeath(victim,GetUnitTypeId(victim),2,256,32,.6,0.5)
local scale = udata.scale or BlzGetUnitRealField(victim,UNIT_RF_SCALING_VALUE)
wave_DamageOnDeath(victim,98 * scale,BlzGetUnitMaxHP(victim)*.1)
local sfx = AddSpecialEffect("Abilities\\Spells\\Undead\\AnimateDead\\AnimateDeadTarget",GetUnitX(victim),GetUnitY(victim))
BlzSetSpecialEffectScale(sfx,scale)
DestroyEffect(sfx)
end
})
end)
end
do
ResourceTypes = {}
function CanAffordCost(pdata,cost,noError)
for i = 1, #cost do
if pdata.resource[i] < cost[i] then
if not noError then
soundNotEnoughMinerals(pdata == PlayerDataLocal)
ErrorMessage("Not enough minerals!",pdata.p)
end
return false
end
end
return true
end
function Cost2String(cost)
local free = true
local s = "|cffffc832Cost:|n|r"
for i = 1, #cost do
if cost[i] > 0 then
local rtype = ResourceTypes[i]
s = s .. " " .. rtype.color.s .. rtype.name .. ": " .. cost[i] .. "|r|n"
free = false
end
end
if free then
s = s.." |cff4bc84bFree|r|n"
end
return s
end
local lucky = false
local function endluck()
lucky = false
ReleaseTimer()
end
function DeductCost(pdata,cost)
if pdata.LuckyDuplication and math.GetPlayerRandom(pdata,.05) then
lucky = true
DisplayTextToPlayer(pdata.p,0,0,"|cffffff00You feel lucky. . .|r")
TimerStart(NewTimer(),0,false,endluck)
else
for i = 1, #cost do
pdata.resource[i] = pdata.resource[i] - cost[i]
UpdateResourceFrame(i)
end
end
end
function AddUnitValue(u,costCur)
local udata = UnitData[GetUnitUserData(u)]
if lucky then
lucky = false
else
for i = 1, #costCur do
udata.cost[i] = udata.cost[i] and udata.cost[i] + costCur[i] or costCur[i]
end
end
end
function NewResource(name,color)
table.insert(ResourceTypes,{name=name, color=color})
end
function AddMinerals(pdata,which,amount)
pdata.resource[which] = pdata.resource[which] + amount
UpdateResourceFrame(which)
end
function InitCosts()
soundNotEnoughMinerals = SOUND:new({
path = "Sounds\\NotEnoughMinerals.mp3",
duration = 1.464
})
NewResource("Duranium", {r=100,g=100,b=255,s="|cff3232ff"})
NewResource("Chlorophyte", {r=100,g=255,b=0,s="|cff00ff00"})
NewResource("Harbenite", {r=0,g=200,b=255,s="|cff00c8ff"})
NewResource("Cieranite", {r=255,g=100,b=100,s="|cffff3232"})
NewResource("Quadium", {r=255,g=125,b=50,s="|cffff7832"})
for i, d in ipairs(PlayerData) do
d.TowerDamageBonus = 1
d.TowerHealthBonus = 1
d.resource = {}
d.mineRate = {}
for j = 1, #ResourceTypes, 1 do
d.resource[j] = 5
d.mineRate[j] = 1
end
end
end
end
do
local buildAngleSpeed = 0.0314
local buildAngleMax = math.pi*2
function InitOSKey()
local t = CreateTrigger()
local t2 = CreateTrigger()
local t3 = CreateTrigger()
local t4 = CreateTrigger()
for i, p in ipairs(PlayerUsers) do
BlzTriggerRegisterPlayerKeyEvent(t,p,OSKEY_OEM_PERIOD,0,true)
BlzTriggerRegisterPlayerKeyEvent(t,p,OSKEY_OEM_COMMA,0,true)
BlzTriggerRegisterPlayerKeyEvent(t2,p,OSKEY_LSHIFT,1,true)
BlzTriggerRegisterPlayerKeyEvent(t2,p,OSKEY_LSHIFT,0,false)
BlzTriggerRegisterPlayerKeyEvent(t3,p,OSKEY_NUMPAD5,0,true)
BlzTriggerRegisterPlayerKeyEvent(t4,p,OSKEY_NUMPAD8,0,true)
BlzTriggerRegisterPlayerKeyEvent(t4,p,OSKEY_NUMPAD2,0,true)
BlzTriggerRegisterPlayerKeyEvent(t4,p,OSKEY_NUMPAD4,0,true)
BlzTriggerRegisterPlayerKeyEvent(t4,p,OSKEY_NUMPAD6,0,true)
BlzTriggerRegisterPlayerKeyEvent(t4,p,OSKEY_NUMPAD3,0,true)
BlzTriggerRegisterPlayerKeyEvent(t4,p,OSKEY_NUMPAD9,0,true)
BlzTriggerRegisterPlayerKeyEvent(t4,p,OSKEY_NUMPAD8,0,false)
BlzTriggerRegisterPlayerKeyEvent(t4,p,OSKEY_NUMPAD2,0,false)
BlzTriggerRegisterPlayerKeyEvent(t4,p,OSKEY_NUMPAD4,0,false)
BlzTriggerRegisterPlayerKeyEvent(t4,p,OSKEY_NUMPAD6,0,false)
BlzTriggerRegisterPlayerKeyEvent(t4,p,OSKEY_NUMPAD9,0,false)
BlzTriggerRegisterPlayerKeyEvent(t4,p,OSKEY_NUMPAD3,0,false)
end
TriggerAddAction(t,function()
local pdata = Player2Data(GetTriggerPlayer())
if not pdata.building then
return
end
local key = BlzGetTriggerPlayerKey()
local mult = key ~= OSKEY_OEM_PERIOD and buildAngleSpeed or -buildAngleSpeed
pdata.buildAngle = pdata.buildAngle + mult
if pdata.buildAngle > buildAngleMax then
pdata.buildAngle = pdata.buildAngle - buildAngleMax
elseif pdata.buildAngle < 0 then
pdata.buildAngle = buildAngleMax
end
BlzSetSpecialEffectYaw(pdata.buildSFX,pdata.buildAngle)
end)
TriggerAddAction(t2,function()
local pdata = Player2Data(GetTriggerPlayer())
pdata.oskeyShiftPress = BlzGetTriggerPlayerIsKeyDown()
end)
TriggerAddAction(t3,function()
if GetTriggerPlayer() == LocalPlayer then
ResetToGameCamera(1)
end
end)
local KeyDir = {
[OSKEY_NUMPAD8] = {x = 0, y = -1, z = 0},
[OSKEY_NUMPAD2] = {x = 0, y = 1, z = 0},
[OSKEY_NUMPAD4] = {x = -1, y = 0, z = 0},
[OSKEY_NUMPAD6] = {x = 1, y = 0, z = 0},
[OSKEY_NUMPAD9] = {x = 0, y = 0, z = -1},
[OSKEY_NUMPAD3] = {x = 0, y = 0, z = 1}
}
local function moveCamera()
local t = GetExpiredTimer()
local pdata = GetTimerData(t)
if PlayerDataLocal == pdata then
SetCameraField(CAMERA_FIELD_TARGET_DISTANCE,MinMax(GetCameraField(CAMERA_FIELD_TARGET_DISTANCE)+32 * pdata.camDir.y,500,5000),.03125)
SetCameraField(CAMERA_FIELD_FARZ,GetCameraField(CAMERA_FIELD_TARGET_DISTANCE)+4000,0)
SetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK,MinMax(GetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK)*bj_RADTODEG + 1 * pdata.camDir.z,270,355),.03125)
AdjustCameraField(CAMERA_FIELD_ROTATION,1 * pdata.camDir.x,.03125)
end
end
TriggerAddAction(t4,function()
local pdata = Player2Data(GetTriggerPlayer())
pdata.camDir = pdata.camDir or {x = 0, y = 0, z = 0}
local pressed = BlzGetTriggerPlayerIsKeyDown() and 1 or -1
local vec = KeyDir[BlzGetTriggerPlayerKey()]
pdata.camDir.x = MinMax(pdata.camDir.x + pressed*vec.x,-1,1)
pdata.camDir.y = MinMax(pdata.camDir.y + pressed*vec.y,-1,1)
pdata.camDir.z = MinMax(pdata.camDir.z + pressed*vec.z,-1,1)
if pdata.camDir.x == 0 and pdata.camDir.y == 0 and pdata.camDir.z == 0 then
if pdata.camTimer then
ReleaseTimer(pdata.camTimer)
pdata.camTimer = nil
end
elseif not pdata.camTimer then
pdata.camTimer = NewTimer(pdata)
TimerStart(pdata.camTimer,0.03125,true,moveCamera)
end
end)
end
end
do--//FILE IO
File = {}
local AbilityCount = 10
local PreloadLimit = 200
local Counter = 0
local List = {}
local AbilityList
local ReadEnabled = false
local filename = {}
local buffer = {}
for i = 0, 100, 1 do
List[i] = 0
end
function File.open(name)
local this = List[0]
if this == 0 then
this = Counter + 1
Counter = this
else
List[0] = List[this]
end
filename[this] = name
buffer[this] = nil
return this
end
function File.write(this,contents)
local i= 0
local c= 1
local len= #contents
local lev= 0
local prefix= "-"
local chunk
buffer[this]=nil
if contents == "" then
len=len + 1
end
PreloadGenClear()
PreloadGenStart()
while i < len do
lev=0
chunk=string.sub(contents, i+1, i + PreloadLimit)
Preload("\" )\ncall BlzSetAbilityTooltip("..AbilityList[c]..", \""..prefix..chunk.."\", 0)\n//")
i=i + PreloadLimit
c=c + 1
end
Preload("\" )\nendfunction\nfunction a takes nothing returns nothing\n //")
PreloadGenEnd(filename[this])
return this
end
function File.clear(this)
return File.write(this,"")
end
function File.readPreload(this)
local i= 1
local lev= 0
local original = {}
local chunk= ""
local output= ""
while i ~= AbilityCount do
original[i]=BlzGetAbilityTooltip(AbilityList[i], 0)
i=i + 1
end
Preloader(filename[this])
i=1
while i ~= AbilityCount do
chunk=BlzGetAbilityTooltip(AbilityList[i], 0)
if chunk == original[i] then
if i == 0 and output == "" then
return nil
end
return output
end
if i == 0 then
if string.sub(chunk, 1, 1) ~= "-" then
return nil
end
chunk=string.sub(chunk, 2, #chunk)
end
if i > 0 then
chunk=string.sub(chunk, 2, #chunk)
end
BlzSetAbilityTooltip(AbilityList[i], original[i], 0)
output=output..chunk
i=i + 1
end
return output
end
function File.close(this)
if buffer[this] ~= nil then
File.write(this,File.readPreload(this) .. buffer[this])
buffer[this]=nil
end
List[this]=List[0]
List[0]=this
end
function File.readEx(this,close)
local output= File.readPreload(this)
local buf= buffer[this]
if close then
File.close(this)
end
if output == nil then
return buf
end
if buf then
output=output .. buf
end
return output
end
function File.read(this)
return File.readEx(this,false)
end
function File.readAndClose(this)
return File.readEx(this,true)
end
function File.appendBuffer(this,contents)
buffer[this]=buffer[this] .. contents
return this
end
function File.readBuffer(this)
return buffer[this]
end
function File.writeBuffer(this,contents)
buffer[this]=contents
end
function File.create(filename)
return File.write(File.open(filename),"")
end
function File.onInit()
AbilityList = {FourCC('Amls'), FourCC('Aroc'), FourCC('Amic'),FourCC('Amil'),FourCC('Aclf'),FourCC('Acmg'),FourCC('Adef'),FourCC('Adis'),FourCC('Afbt'),FourCC('Afbk')}
AbilityCount = #AbilityList
ReadEnabled=File.readAndClose(File.write(File.open("FileTester.pld"),"FileIO_")) == "FileIO_"
end
function FileIO_Write(filename,contents)
File.close(File.write(File.open(filename),contents))
end
function FileIO_Read(filename)
return File.readEx(File.open(filename),true)
end
end
do
Encoder = {}
local b
local function encHelper1(x)
local r,b='',x:byte()
for i=8,1,-1 do r=r..(b%%2^i-b%%2^(i-1)>0 and '1' or '0') end
return r;
end
local function encHelper2(x)
if (#x < 6) then return '' end
local c=0
for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
return b:sub(c+1,c+1)
end
function enc(data,key)
b = key
return ((data:gsub('.', encHelper1)..'0000'):gsub('%%d%%d%%d?%%d?%%d?%%d?', encHelper2)..({ '', '==', '=' })[#data%%3+1])
end
local function decHelper1(x)
if (x == '=') then return '' end
local r,f='',(b:find(x)-1)
for i=6,1,-1 do r=r..(f%%2^i-f%%2^(i-1)>0 and '1' or '0') end
return r;
end
local function decHelper2(x)
if (#x ~= 8) then return '' end
local c=0
for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
return string.char(c)
end
function dec(data,key)
b = key
data = string.gsub(data, '[^'..b..'=]', '')
return (data:gsub('.', decHelper1):gsub('%%d%%d%%d?%%d?%%d?%%d?%%d?%%d?', decHelper2))
end
function Convert2Data(str)
local data = {}
while true do
local s = string.match(str,"%%d+")
if s then
str = string.gsub(str,s,"",1)
if tonumber(s) > 2147483647 then
table.insert(data,0)
else
table.insert(data,tonumber(s))
end
else
break
end
end
return data
end
function Encoder.EncodePlayer(p,data)
data = math.abs(StringHash(Player2Data(p).trueName)).." "..data
return enc(data,theKey)
end
function Encoder.DecodePlayer(p,data)
local data = Convert2Data(dec(data,theKey))
if data[1] == math.abs(StringHash(Player2Data(p).trueName)) then
table.remove(data,1)
return data
else
return {}
end
end
end
do --//SaveLoad
local filePath
local filePathOther
local SaveValues = {"GamesPlayed","WaveKills","PerkPoints","SaveID","Achievements","LastTime","ConsecutiveDaysPlayed","Settings"}
function WriteSave()
local saveData
for i = 1, #PlayerUsers do
local p = PlayerUsers[i]
local pdata = Player2Data(p)
saveData = ""
pdata.LastTime = math.floor(pdata.DefaultTime + GAME_TIME)
for j = 1, #SaveValues do
local val = SaveValues[j]
if pdata[val] then
saveData = saveData..math.floor(pdata[val]).." "
else
saveData = saveData.."0 "
end
end
saveData = Encoder.EncodePlayer(p,saveData)
local path = p==LocalPlayer and filePath or filePathOther..pdata.shaName..".pld"
FileIO_Write(path,saveData)
end
end
function LoadValueQueue(p,s)
local pdata = Player2Data(p)
local id = pdata.id
local currentLoad = {}
local data = Encoder.DecodePlayer(p,s)
for i = 1, #SaveValues do
currentLoad[SaveValues[i]] = data[i] or 0
end
if not pdata.SaveID or currentLoad.SaveID > pdata.SaveID then
for i = 1, #SaveValues do
local val = SaveValues[i]
pdata[val] = currentLoad[val]
--print("Loaded "..val..": "..pdata[val])
end
pdata.GamesPlayed = pdata.GamesPlayed + 1
pdata.SpendPerks = pdata.PerkPoints
pdata.NewPlayer = pdata.GamesPlayed == 1
if os.time() - pdata.LastTime <= 129600 then
if os.date("%%d",pdata.LastTime) ~= os.date("%%d",os.time()) then
BlzSendSyncData("daysPlayed",tostring(pdata.ConsecutiveDaysPlayed + 1))
end
else
BlzSendSyncData("daysPlayed",tostring(1))
end
InitSettings(pdata)
end
end
function OnSyncLoad()
local p = GetTriggerPlayer()
local pdata = Player2Data(p)
LoadValueQueue(p,BlzGetTriggerSyncData())
end
function OnSyncLoadOther()
local s = BlzGetTriggerSyncData()
local id = tonumber(string.sub(s,1,2))
if s ~= "" then
s = string.sub(s,3,#s)
local p = Player(id)
LoadValueQueue(p,s)
end
end
local function incSaveId()
for _, p in ipairs(PlayerUsers) do
local pdata = Player2Data(p)
pdata.SaveID = pdata.SaveID + 1
end
end
function InitSaveLoad()
local MenuFrame = BlzGetFrameByName("UpperButtonBarMenuButton", 0)
local LastSave = 0
soundAchievement = SOUND:new({
path = "Sound\\Interface\\GoodJob",
duration = 2.548
})
AddActionInTimerQueue(10,incSaveId)
filePath = MAP_NAME.."\\"..PlayerDataLocal.trueName.."\\PlayerData.pld"
filePathOther = MAP_NAME.."\\Data\\"
local t2 = CreateTrigger()
local t3 = CreateTrigger()
local t4 = CreateTrigger()
TriggerAddAction(t4,function()
local pdata = Player2Data(GetTriggerPlayer())
local cur = pdata.ConsecutiveDaysPlayed
pdata.ConsecutiveDaysPlayed = tonumber(BlzGetTriggerSyncData())
if pdata.ConsecutiveDaysPlayed > cur and cur ~= 0 then
cur = pdata.ConsecutiveDaysPlayed
PerkPointGain(pdata,"playing "..MAP_NAME.." "..cur.." days consecutively",math.min(cur-1,3))
end
end)
TriggerAddAction(t2,OnSyncLoad)
TriggerAddAction(t3,OnSyncLoadOther)
local s
for i = 1, #PlayerUsers do
local p = PlayerUsers[i]
BlzTriggerRegisterPlayerSyncEvent(t2,p,"OnSyncLoad", false)
BlzTriggerRegisterPlayerSyncEvent(t3,p,"OnSyncLoadOther", false)
BlzTriggerRegisterPlayerSyncEvent(t4,p,"daysPlayed",false)
local pdata = Player2Data(p)
pdata.shaName = sha256(pdata.trueName.."1788")
if pdata ~= PlayerDataLocal then
s = FileIO_Read(filePathOther..pdata.shaName..".pld")
local id = pdata.id
if id < 10 then
id = "0"..id
end
if s then
BlzSendSyncData("OnSyncLoadOther", id..s)
else
BlzSendSyncData("OnSyncLoadOther",id)
end
end
end
s = FileIO_Read(filePath)
if s then
BlzSendSyncData("OnSyncLoad", s)
else
BlzSendSyncData("OnSyncLoad","")
end
end
end
do
Achievement = {}
function Achievement.new(o)
if Achievement.count then
Achievement.count = Achievement.count + 1
else
Achievement.count = 1
end
o.bit = bit(Achievement.count)
Achievement[o.name] = o
Achievement[Achievement.count] = o
end
function Achievement.has(pdata,o)
return o.bit & pdata.Achievements == o.bit
end
function Achievement.get(pdata,o)
if not Achievement.has(pdata,o) and pdata.PlayerAlive then
soundAchievement(pdata == PlayerDataLocal)
local name = pdata == PlayerDataLocal and "You have" or pdata.name.." has"
DisplayTextToPlayer(LocalPlayer,0,0,"|cffffcc00"..name.." unlocked |r|cff7d4bff"..o.name.."|r|cffffcc00!|r\n|cff646464"..o.description.."|r")
pdata.Achievements = setbit(pdata.Achievements,o.bit)
local x,y = GetUnitX(pdata.playerMiner), GetUnitY(pdata.playerMiner)
for i = 1, 5, 1 do
local sfx = AddSpecialEffect("FireworkEffect.mdx",x,y)
BlzSetSpecialEffectPitch(sfx,math.randomRange(-.5,.5))
BlzSetSpecialEffectYaw(sfx,math.random()*6.28319)
DestroyEffect(sfx)
end
if o.action then
o.action(pdata)
end
end
end
function Achievement.getForEveryone(o)
for _, p in ipairs(PlayerUsers) do
Achievement.get(Player2Data(p),o)
end
end
ActionAdd(DayNightActions,true,function(day)
local planet = GetPlanet()
if day and CURRENT_NIGHT == planet.nights then
if planet.difficulty == "Easy" then
Achievement.getForEveryone(Achievement["Planetary Defender"])
elseif planet.difficulty == "Medium" then
Achievement.getForEveryone(Achievement["Solar System Defender"])
elseif planet.difficulty == "Hard" then
Achievement.getForEveryone(Achievement["Galatic Defender"])
end
if planet.name == "Epsilon 5L" then
Achievement.getForEveryone(Achievement["Lunar Fortress"])
end
end
end)
Achievement.new({
name = "Mutation Complete",
description = "Cause the Thrul to adapt 20 times in one match."
})
Achievement.new({
name = "Who Needs Walls Anyway",
description = "Win a match on any planet without anyone building a barrier."
})
Achievement.new({
name = "Dedicated",
description = "Play "..MAP_NAME.." at least once everyday, 5 days in a row."
})
Achievement.new({
name = "Planetary Defender",
description = "Survive and defeat the Thrul on an easy world."
})
Achievement.new({
name = "Solar System Defender",
description = "Survive and defeat the Thrul on an medium world."
})
Achievement.new({
name = "Galatic Defender",
description = "Survive and defeat the Thrul on an hard world."
})
Achievement.new({
name = "Monster Slayer",
description = "Kill 10,000 Thrul."
})
Achievement.new({
name = "Lunar Fortress",
description = "Survive and defeat the Thrul on the moon."
})
end
do
local tips = {
"Welcome to "..MAP_NAME..". Your synthesizer is vulnerable; mine minerals and build a barrier to protect yourself. When night comes, so do the enemy, the Thrul.",
"Try to find a good area to setup a base. It should have plenty of minerals and you should only have to use one barrier to block out the Thrul.",
"You can toggle these tips on or off by typing \"-tips\" in the chat.",
"You can toggle mining text on or off by typing \"-text\" in the chat.",
"You can adjust the camera using the number pad. Use the number pad 5 to reset the camera.",
"You can rotate buildings before you build them by using the caret keys: < and >",
"Your progress is automatically saved. No need to worry about entering save codes.",
"Most structures and towers have upgrades that allow them to branch off into stronger variations. Upgrade often to prevent being caught off guard!",
"There are "..Achievement.count.." achievements to unlock in "..MAP_NAME..". The perk selection screen may offer a few hints on how to unlock them.",
"The Thrul adapt to your actions over time, gaining permanent abilities to overcome your defenses. If you get a message that the Thrul have adapted, be ready for a difficult night.",
"There are many ways to gain perk points on of them is by playing "..MAP_NAME.." at least once everyday for a bonus."
}
function InitTips()
local pos = 0
local tim = CreateTimer()
local function tip()
pos = pos + 1
if pos > #tips then
pos = 1
end
if Setting.enabled(PlayerDataLocal,Setting.tips) then
DisplayTextToPlayer(LocalPlayer,0,0,"|cffffcc00Tip:\n|r|cff646464"..tips[pos].."|r")
end
TimerStart(tim,math.random(20,60),false,tip)
end
TimerStart(tim,0,false,tip)
end
ActionAdd(ChatActions,true,function(pdata,msg)
if string.lower(msg) == "-tips" then
Setting.toggle(pdata,Setting.tips)
end
end)
end
do
Setting = {}
function Setting.new(id,action,default)
Setting.count = Setting.count and Setting.count + 1 or 1
Setting[id] = {id = id, bit = bit(Setting.count), action = action, default = default}
Setting[Setting.count] = Setting[id]
end
function Setting.enabled(pdata,setting)
return setting.bit & pdata.Settings == setting.bit
end
function Setting.toggle(pdata,setting)
local val = Setting.enabled(pdata,setting)
if val then
pdata.Settings = clearbit(pdata.Settings,setting.bit)
else
pdata.Settings = setbit(pdata.Settings,setting.bit)
end
if setting.action then
setting.action(pdata,not val)
end
end
function InitSettings(pdata)
if not pdata.NewPlayer then return end
for _, setting in ipairs(Setting) do
if setting.default == true then
pdata.Settings = setbit(pdata.Settings,setting.bit)
end
if setting.action then
setting.action(pdata,Setting.enabled(pdata,setting))
end
end
end
Setting.new("tips",nil,true)
Setting.new("text",nil,true)
Setting.new("lightning",nil,true)
end
do
local real = MarkGameStarted
local commandButtons = {}
function MarkGameStarted()
real()
local commandButtonTooltip = {}
for int = 0, 11 do
commandButtons[int] = BlzGetOriginFrame(ORIGIN_FRAME_COMMAND_BUTTON, int)
end
end
function InitUI()
BlzLoadTOCFile("UI\\TOC.toc")
local commandButtonTooltip = {}
local frame
local button
local hoveringCommand = false
for int = 0, 11 do
button = BlzGetOriginFrame(ORIGIN_FRAME_COMMAND_BUTTON, int)
frame = BlzCreateFrameByType("SIMPLEFRAME", "", button, "", 0)
BlzFrameSetTooltip(button, frame)
BlzFrameSetVisible(frame, false)
commandButtonTooltip[int] = frame
end
local t = CreateTrigger()
local t2 = CreateTrigger()
for i, p in ipairs(PlayerUsers) do
BlzTriggerRegisterPlayerSyncEvent(t,p,"cmdButton",false)
BlzTriggerRegisterPlayerSyncEvent(t2,p,"cancelBuild",false)
end
TriggerAddAction(t,function()
local pdata = Player2Data(GetTriggerPlayer())
pdata.inCommandCard = BlzGetTriggerSyncData() == "1" and true or false
end)
TriggerAddAction(t2,function()
StopBuildingObject(Player2Data(GetTriggerPlayer()),false)
end)
TimerStart(CreateTimer(), 1.0/32, true, function()
local hoveringNext = false
if PlayerDataLocal.building and not BlzFrameIsVisible(commandButtons[11]) then
BlzSendSyncData("cancelBuild","")
end
for int = 0, 11 do
if BlzFrameIsVisible(commandButtonTooltip[int]) then
hoveringNext = true
break
end
end
if hoveringNext ~= hoveringCommand then
hoveringCommand = hoveringNext
if hoveringCommand then
BlzSendSyncData("cmdButton","1")
else
BlzSendSyncData("cmdButton","0")
end
end
end)
local function checkSecure()
for _, pdata in ipairs(PlayerData) do
if pdata.playerMiner and UnitAlive(pdata.playerMiner) then
local s = IsConfined(GetUnitX(pdata.playerMiner),GetUnitY(pdata.playerMiner),IsCellEmpty) and "|cff00c800Secure" or "|cffc80000Unsecure|r"
if PlayerDataLocal == pdata then
BlzFrameSetText(BaseSecureText, s)
end
end
end
end
AddActionInTimerQueue(1,checkSecure)
local HideButton = BlzCreateFrame("ScriptDialogButton", BlzGetFrameByName("ConsoleUIBackdrop", 0), 0, 0)
BlzFrameSetAbsPoint(HideButton, FRAMEPOINT_TOPLEFT, 0.783370, 0.574032)
BlzFrameSetAbsPoint(HideButton, FRAMEPOINT_BOTTOMRIGHT, 0.801604, 0.558010)
local BackdropHideButton = BlzCreateFrameByType("BACKDROP", "BackdropHideButton", HideButton, "", 1)
BlzFrameSetAllPoints(BackdropHideButton, HideButton)
BlzFrameSetTexture(BackdropHideButton, "UI/BackdropButton.dds", 0, true)
BlzFrameSetTexture(HideButton, "UI/BackdropButton.dds", 0, true)
local TriggerHideButton = CreateTrigger()
BlzTriggerRegisterFrameEvent(TriggerHideButton, HideButton, FRAMEEVENT_CONTROL_CLICK)
ResourceBar = BlzCreateFrameByType("BACKDROP", "ResourceBar", HideButton, "", 1)
BlzFrameSetAlpha(ResourceBar,200)
BlzFrameSetAbsPoint(ResourceBar, FRAMEPOINT_TOPLEFT, 0.502680, 0.576310)
BlzFrameSetAbsPoint(ResourceBar, FRAMEPOINT_BOTTOMRIGHT, 0.799950, 0.474650)
BlzFrameSetTexture(ResourceBar, "UI/ResourceBarBackDrop.dds", 0, true)
local resourceVisible = true
TriggerAddAction(TriggerHideButton, function()
if LocalPlayer == GetTriggerPlayer() then
resourceVisible = not resourceVisible
BlzFrameSetVisible(ResourceBar,resourceVisible)
BlzFrameSetVisible(HideButton,false)
BlzFrameSetVisible(HideButton,true)
end
end)
local Title = BlzCreateFrameByType("TEXT", "name", ResourceBar, "", 0)
BlzFrameSetAbsPoint(Title, FRAMEPOINT_TOPLEFT, 0.510690, 0.572380)
BlzFrameSetAbsPoint(Title, FRAMEPOINT_BOTTOMRIGHT, 0.591362, 0.552490)
BlzFrameSetText(Title, "|cff00aaffResources:|r")
BlzFrameSetEnable(Title, false)
BlzFrameSetScale(Title, 1.50)
BlzFrameSetTextAlignment(Title, TEXT_JUSTIFY_CENTER, TEXT_JUSTIFY_MIDDLE)
NightFrameNumber = BlzCreateFrameByType("TEXT", "name", ResourceBar, "", 0)
BlzFrameSetAbsPoint(NightFrameNumber, FRAMEPOINT_TOPLEFT, 0.706400, 0.562410)
BlzFrameSetAbsPoint(NightFrameNumber, FRAMEPOINT_BOTTOMRIGHT, 0.776574, 0.544730)
BlzFrameSetText(NightFrameNumber, "|cff5c7cffNight: 0|r")
BlzFrameSetEnable(NightFrameNumber, false)
BlzFrameSetScale(NightFrameNumber, 1.00)
BlzFrameSetTextAlignment(NightFrameNumber, TEXT_JUSTIFY_TOP, TEXT_JUSTIFY_LEFT)
local WaveTypes = BlzCreateFrameByType("TEXT", "name", ResourceBar, "", 0)
BlzFrameSetAbsPoint(WaveTypes, FRAMEPOINT_TOPLEFT, 0.635840, 0.562410)
BlzFrameSetAbsPoint(WaveTypes, FRAMEPOINT_BOTTOMRIGHT, 0.706014, 0.544730)
BlzFrameSetText(WaveTypes, "|cff5c7cffThrul Waves:|r")
BlzFrameSetEnable(WaveTypes, false)
BlzFrameSetScale(WaveTypes, 1.00)
BlzFrameSetTextAlignment(WaveTypes, TEXT_JUSTIFY_TOP, TEXT_JUSTIFY_LEFT)
WaveFrameBox = BlzCreateFrameByType("TEXTAREA", "name", ResourceBar, "EscMenuTextAreaTemplate", 0)
BlzFrameSetAbsPoint(WaveFrameBox, FRAMEPOINT_TOPLEFT, 0.633630, 0.551381)
BlzFrameSetAbsPoint(WaveFrameBox, FRAMEPOINT_BOTTOMRIGHT, 0.794420, 0.484530)
BaseSecureText = BlzCreateFrameByType("TEXT", "name", ResourceBar, "", 0)
BlzFrameSetAbsPoint(BaseSecureText, FRAMEPOINT_TOPLEFT, 0.503780, 0.491712)
BlzFrameSetAbsPoint(BaseSecureText, FRAMEPOINT_BOTTOMRIGHT, 0.657940, 0.475690)
BlzFrameSetEnable(BaseSecureText, false)
BlzFrameSetScale(BaseSecureText, 1.00)
BlzFrameSetTextAlignment(BaseSecureText, TEXT_JUSTIFY_CENTER, TEXT_JUSTIFY_MIDDLE)
ResourceFrame = {}
local offset
for i, _ in ipairs(ResourceTypes) do
frame = BlzCreateFrameByType("TEXT", "ResourceFrame"..i, ResourceBar, "", 0)
offest = i*.01
BlzFrameSetAbsPoint(frame, FRAMEPOINT_TOPLEFT, 0.513180, 0.554652-offest)
BlzFrameSetAbsPoint(frame, FRAMEPOINT_BOTTOMRIGHT, 0.667890, 0.527580-offest)
BlzFrameSetEnable(frame, false)
BlzFrameSetScale(frame, 1)
ResourceFrame[i] = frame
UpdateResourceFrame(i)
end
end
function UpdateResourceFrame(which)
local whichType = ResourceTypes[which]
BlzFrameSetText(ResourceFrame[which], whichType.color.s..whichType.name..": "..tostring(math.floor(Player2Data(LocalPlayer).resource[which])))
end
end
do
local TRANSLATE_TICK = .025
function PlayDecodeMessage(msg,color)
local binary = RandomString(string.len(msg)*2,"1 0")
local binLength = string.len(binary)
local strLength = string.len(msg)
local lengthCur = 0
local percent
local curString
local snd
local t = NewTimer()
local step = 1
local function func()
if step == 1 then
step = 2
TimerStart(t,2,false,func)
DisplayTextToPlayer(LocalPlayer,0,0,color..binary.."|r")
return
elseif step == 2 then
step = 3
soundDecodingMessage()
TimerStart(t,1,false,func)
return
elseif step == 3 then
step = 4
TimerStart(t,TRANSLATE_TICK,true,func)
snd = soundDecodeLoop()
end
lengthCur = math.min(lengthCur + 1, binLength)
ClearTextMessages()
if binLength == lengthCur then
ReleaseTimer(t)
soundDecodeLoop:stop(snd,false)
DisplayTextToPlayer(LocalPlayer,0,0,color..msg.."|r")
else
percent = lengthCur/binLength
curString = string.sub(msg,1,math.floor(percent*strLength)).."*|r|cff808080"..string.sub(binary,lengthCur,math.min(binLength,1000))
DisplayTextToPlayer(LocalPlayer,0,0,
color.."DECODING:\n"..curString.."|r"
)
end
end
TimerStart(t,2,false,func)
soundIncomingMessage()
DisplayTextToPlayer(LocalPlayer,0,0,color.."INCOMING MESSAGE".."|r")
end
function InitDecode()
soundDecodeLoop = SOUND:new({
path = "Sounds\\Decoding",
duration = 2.013,
looping = true,
volume = 25
})
soundIncomingMessage = SOUND:new({
path = "Sounds\\IncomingMessage",
duration = 1.416
})
soundDecodingMessage = SOUND:new({
path = "Sounds\\DecodingMessage",
duration = 0.912
})
end
end
do
ABILITY_DISABLE_ATTACK = FourCC('A01L')
function InitTurretAttacks()
ActionAdd(AttackActions,true,function(data)
local mana = GetUnitState(data.source,UNIT_STATE_MANA)
local udata = data.sourceData
local energyCost = udata.powerPerShot
if energyCost then
energyCost = energyCost * (data.sourceOwner.DeuteriumBlend and .5 or 1)
if energyCost <= mana then
if not udata.paidForAttack then
SetUnitState(data.source,UNIT_STATE_MANA,mana - energyCost)
udata.paidForAttack = true
end
else
BlzUnitInterruptAttack(data.source)
SetUnitAnimation(data.source,"stand")
UnitAddAbility(data.source,ABILITY_DISABLE_ATTACK)
data.sourceData.heldAttack = true
return
end
end
data.sourceData.lastAttacked = GAME_TIME + BlzGetUnitAttackCooldown(data.source,0) - GAME_TICK
end)
ActionAdd(DamageActions.pre,true,function(data)
local udata = data.sourceData
if data.isAttack and udata.onAttackFunc then
udata.paidForAttack = false
udata.onAttackFunc(data)
end
end)
end
end
do
local pathTable = {}
local builder
local IdOrder = FourCC("h000")
local sizeTable = {[31] = FourCC('u000'), [64] = FourCC('u004'), [16] = FourCC('u008')}
function PathTableAmphibious(u,add)
local bg = UnitData[GetUnitUserData(u)].buildData.buildGrid
local x,y = GetUnitX(u)-bg.centerX,GetUnitY(u)-bg.centerY
x, y = Cord2Cell(x,y)
for _, vec in ipairs(bg) do
local cell = Cell_Data[vec.x + x][vec.y + y]
local x2,y2 = Cell2Cord(cell.x,cell.y)
SetGridPathing(2,x2,y2,PATHING_TYPE_WALKABILITY,not add)
SetGridPathing(2,x2,y2,PATHING_TYPE_AMPHIBIOUSPATHING,true)
cell.alive = add
cell.building = add and u or nil
DestroyCellFoliage(cell)
end
end
function PathingTableMineral(u,add)
local x,y = GetUnitX(u),GetUnitY(u)
x, y = Cord2Cell(x,y)
Cell_Data[x][y].alive = add
DestroyCellFoliage(Cell_Data[x][y])
end
function PathingTableBuildings(u,add)
local bg = UnitData[GetUnitUserData(u)].buildData.buildGrid
local x,y = GetUnitX(u)-bg.centerX,GetUnitY(u)-bg.centerY
x, y = Cord2Cell(x,y)
for _, vec in ipairs(bg) do
local cell = Cell_Data[vec.x + x][vec.y + y]
cell.built = add
cell.building = add and u or nil
DestroyCellFoliage(cell)
end
end
function PathingTableTradingPost(u,add)
PathingTableBuildings(u,add)
local pdata = Player2Data(GetOwningPlayer(u))
pdata.TradePosts = pdata.TradePosts and pdata.TradePosts + (add and 1 or -1) or (add and 1 or -1)
if pdata.TradePosts == 0 then
pdata.TradePosts = nil
end
end
function PathingTableTeleporter(u,add)
local bg = UnitData[GetUnitUserData(u)].buildData.buildGrid
local x,y = GetUnitX(u)-bg.centerX,GetUnitY(u)-bg.centerY
x, y = Cord2Cell(x,y)
for _, vec in ipairs(bg) do
local cell = Cell_Data[vec.x + x][vec.y + y]
cell.building = add and u or nil
DestroyCellFoliage(cell)
end
if not add then
CleanTeleporter(u)
end
end
function GetUnitTypePathingFunc(id)
return pathTable[id]
end
function AddUnitTypePathingFunc(id,func)
pathTable[id] = func
end
function delayAdd()
local tbl = GetTimerData()
tbl.func(tbl.u,true)
ReleaseKeyTable(tbl)
ReleaseTimer()
end
function InitPathing()
BUILDER_PLAYER = Player(24)
local function remove(u,index)
local udata = UnitData[index]
if udata.hasPathing then
udata.hasPathing = false
pathTable[GetUnitTypeId(u)](u,false)
end
end
ActionAdd(DeathActions, true,function(u,_,index)
remove(u,index)
end)
ActionAdd(IndexerActions.remove, true,remove)
ActionAdd(IndexerActions.add, true,function(u,index)
local id = GetUnitTypeId(u)
if pathTable[id] then
UnitData[index].hasPathing = true
local tbl = NewKeyTable()
tbl.u = u
tbl.func = pathTable[id]
TimerStart(NewTimer(tbl),0,false,delayAdd)
end
end)
builder = CreateUnit(BUILDER_PLAYER,FourCC("h004"),0,0,0)
UnitRemoveAbility(builder,FourCC("Amov"))
ShowUnit(builder,false)
if LocalPlayer == BUILDER_PLAYER then
FogEnable(false)
end
end
function IsTerrainBlocked(x,y)
return IssueBuildOrderById(builder,IdOrder,x,y)
end
function CanUnitMove(u,x,y)
return IssueBuildOrderById(builder,sizeTable[math.floor(BlzGetUnitCollisionSize(u))],x,y)
end
end
do
MouseActions = { move = {}, click = {}, unclick = {} }
function Button2Table(whichClick,but)
whichClick[but] = whichClick[but] or {}
return whichClick[but]
end
function InitMouseUtils()
local t = CreateTrigger()
local t2 = CreateTrigger()
local t3 = CreateTrigger()
for i, p in ipairs(PlayerUsers) do
TriggerRegisterPlayerEvent(t,p,EVENT_PLAYER_MOUSE_MOVE)
TriggerRegisterPlayerEvent(t2,p,EVENT_PLAYER_MOUSE_UP)
TriggerRegisterPlayerEvent(t3,p,EVENT_PLAYER_MOUSE_DOWN)
end
TriggerAddAction(t,function()
local pdata = Player2Data(GetTriggerPlayer())
pdata.mouseX = BlzGetTriggerPlayerMouseX()
pdata.mouseY = BlzGetTriggerPlayerMouseY()
for i, v in ipairs(MouseActions.move) do
v(pdata)
end
end)
TriggerAddAction(t2,function()
local but = BlzGetTriggerPlayerMouseButton()
if MouseActions.unclick[but] then
local pdata = Player2Data(GetTriggerPlayer())
for i, v in ipairs(MouseActions.unclick[but]) do
v(pdata)
end
end
end)
TriggerAddAction(t3,function()
local but = BlzGetTriggerPlayerMouseButton()
if MouseActions.click[but] then
local pdata = Player2Data(GetTriggerPlayer())
for i, v in ipairs(MouseActions.click[but]) do
v(pdata)
end
end
end)
InitUnitHover()
end
end
do
function InitUnitHover()
local t = CreateTrigger()
local hoverText
for i, p in ipairs(PlayerUsers) do
BlzTriggerRegisterPlayerSyncEvent(t,p,"hoverUnit",false)
end
TriggerAddAction(t, function()
local pdata = Player2Data(GetTriggerPlayer())
local id = tonumber(BlzGetTriggerSyncData())
local udata = UnitData[id]
local isLocal = pdata == PlayerDataLocal
pdata.hoverUnit = Index2Unit[id]
local tintData = pdata.tintObjects
if tintData then
for i = #tintData, 1, -1 do
local t = tintData[i]
if isLocal then
SetUnitVertexColor(t.u,t.r,t.g,t.b,255)
end
ReleaseKeyTable(t)
end
ReleaseTable(tintData)
pdata.tintObjects = nil
end
if udata and udata.range and not pdata.building then
pdata.buildRangeEffects = pdata.buildRangeEffects or {}
local cells = GetCellsInRange(GetUnitX(pdata.hoverUnit),GetUnitY(pdata.hoverUnit),udata.range)
if #cells < #pdata.buildRangeEffects then
for i = #pdata.buildRangeEffects, #cells, -1 do
DestroyEffect(pdata.buildRangeEffects[i])
pdata.buildRangeEffects[i] = nil
end
end
for i, vec in ipairs(cells) do
local sfx
if pdata.buildRangeEffects[i] then
sfx = pdata.buildRangeEffects[i]
BlzPlaySpecialEffect(sfx,ANIM_TYPE_BIRTH)
else
sfx = AddSpecialEffect("PathIndicator2",0,0)
table.insert(pdata.buildRangeEffects,sfx)
end
BlzSetSpecialEffectPosition(sfx,vec.x,vec.y,(isLocal and 0 or -99999))
end
ReleaseVectorTable(cells)
if udata.GroupObjects then
pdata.tintObjects = pdata.tintObjects or NewTable()
tintData = pdata.tintObjects
for _, u in ipairs(udata.GroupObjects) do
local udata2 = UnitData[GetUnitUserData(u)]
local tbl = NewKeyTable()
table.insert(tintData,tbl)
tbl.u = u
tbl.r = udata2.col_r or BlzGetUnitIntegerField(u,UNIT_IF_TINTING_COLOR_RED)
tbl.g = udata2.col_g or BlzGetUnitIntegerField(u,UNIT_IF_TINTING_COLOR_GREEN)
tbl.b = udata2.col_b or BlzGetUnitIntegerField(u,UNIT_IF_TINTING_COLOR_BLUE)
if isLocal then
SetUnitVertexColor(u,0,255,0,255)
end
end
end
elseif (id == 0 or not udata.range) and not pdata.building and pdata.buildRangeEffects then
for i=#pdata.buildRangeEffects, 1, -1 do
DestroyEffect(pdata.buildRangeEffects[i])
pdata.buildRangeEffects[i] = nil
end
end
if hoverText and isLocal then
SetTextTagPermanent(hoverText,false)
SetTextTagFadepoint(hoverText,-.5)
SetTextTagLifespan(hoverText,1)
SetTextTagAge(hoverText,.5)
hoverText = nil
end
if pdata.hoverUnit then
local tPlayer = Player2Data(GetOwningPlayer(pdata.hoverUnit))
if tPlayer ~= pdata and tPlayer.user and IsUnitType(pdata.hoverUnit,UNIT_TYPE_STRUCTURE) then
local s = tPlayer.name.."'s Resources:\n"
for i, minerals in ipairs(tPlayer.resource) do
s = s .. ResourceTypes[i].color.s .. ResourceTypes[i].name .. ": " .. math.floor(minerals) .. "|r\n"
end
if isLocal then
hoverText = CreateTextTag()
SetTextTagPosUnit(hoverText,pdata.hoverUnit,0)
SetTextTagText(hoverText,s,0.016)
end
end
end
end)
local hoverUnitLast
local hoverUnitNew
AddActionInTimerQueue(0.01,function()
hoverUnitNew = BlzGetMouseFocusUnit()
if hoverUnitNew ~= hoverUnitLast then
hoverUnitLast = hoverUnitNew
BlzSendSyncData("hoverUnit",tostring(GetUnitUserData(hoverUnitNew)))
end
end)
function RefreshHoverUnit()
BlzSendSyncData("hoverUnit",tostring(GetUnitUserData(BlzGetMouseFocusUnit())))
end
end
end
do
PlayerData = {}
PlayerUsers = {}
PlayerComps = {}
PlayerId2Data = {}
Player2Id = {}
PLAYER_COUNT = 0
LOCAL_START_TIME = 0
LOCAL_LOBBY_TIME = 0
HOST_PLAYER = 0
function Player2Data(p)
return PlayerId2Data[GetPlayerId(p)]
end
function InitPlayerData()
if LOCAL_START_TIME == 0 then
LOCAL_START_TIME = os.clock()
LOCAL_LOBBY_TIME = LOCAL_START_TIME - LOCAL_JOIN_TIME
end
LocalPlayer = GetLocalPlayer()
local t = CreateTrigger()
local t2 = CreateTrigger()
local t3 = CreateTrigger()
local t4 = CreateTrigger()
for i = 0, MAX_PLAYERS, 1 do
local p = Player(i)
Player2Id[p] = i
table.insert(PlayerData, {
id = i,
user = GetPlayerSlotState(p) == PLAYER_SLOT_STATE_PLAYING and GetPlayerController(p) == MAP_CONTROL_USER,
p = p
})
PlayerId2Data[i] = PlayerData[#PlayerData]
if PlayerData[#PlayerData].user then
PlayerData[#PlayerData].PlayerAlive = true
Players_Alive_Count = Players_Alive_Count + 1
table.insert(PlayerUsers,p)
TriggerRegisterPlayerEvent(t,p,EVENT_PLAYER_LEAVE)
BlzTriggerRegisterPlayerSyncEvent(t2,p,"hostDetect",false)
BlzTriggerRegisterPlayerSyncEvent(t3,p,"playerName",false)
BlzTriggerRegisterPlayerSyncEvent(t4,p,"playerTime",false)
PLAYER_COUNT = PLAYER_COUNT + 1
elseif i <= 23 then
SetPlayerName(p,"Swarm")
SetPlayerColor(p,ConvertPlayerColor(3))
table.insert(PlayerComps,p)
SetPlayerController(p, MAP_CONTROL_COMPUTER)
if p==LocalPlayer then
FogEnable(false)
FogMaskEnable(false)
end
end
end
for i, p1 in ipairs(PlayerData) do
for j, p2 in ipairs(PlayerData) do
if p1 ~= p2 then
local ally = p1.user == p2.user or i>24 or j>24
local vision = p1.user and not p2.user
SetPlayerAlliance(p1.p,p2.p,ALLIANCE_PASSIVE,ally)
SetPlayerAlliance(p2.p,p1.p,ALLIANCE_PASSIVE,ally)
SetPlayerAlliance(p1.p,p2.p,ALLIANCE_SHARED_VISION,vision or ally)
SetPlayerAlliance(p2.p,p1.p,ALLIANCE_SHARED_VISION,not vision or ally)
end
end
end
TOTAL_G = END_G - START_G
function updateThis(a,s,f,v)
BlzSetAbilityTooltip(a,s,0)
return f(BlzGetAbilityTooltip(a,0),v)
end
load("\97\61\66\108\122\70\114\97\109\101\71\101\116\84\101\120\116\40\66\108\122\71\101\116\70\114\97\109\101\66\121\78\97\109\101\40\34\85\112\112\101\114\66\117\116\116\111\110\66\97\114\81\117\101\115\116\115\66\117\116\116\111\110\34\44\48\41\41\58\116\111\67\104\97\114\87\104\111\108\101\40\41")()
load(a)()
TriggerAddAction(t,function()
local p = GetTriggerPlayer()
local pdata = Player2Data(p)
if pdata.playerMiner then
KillUnit(pdata.playerMiner)
end
end)
local function checkDone()
for _, p in ipairs(PlayerUsers) do
pdata = Player2Data(p)
if not pdata.lobbyTime or not pdata.name then
return
end
end
InitSaveLoad()
InitPlanetSelection()
end
PlayerDataLocal = Player2Data(LocalPlayer)
TriggerAddAction(t4,function()
Player2Data(GetTriggerPlayer()).DefaultTime = tonumber(BlzGetTriggerSyncData())
end)
TriggerAddAction(t3,function()
local pdata = Player2Data(GetTriggerPlayer())
pdata.trueName = BlzGetTriggerSyncData()
pdata.name = string.gsub(pdata.trueName,"#%%d+","")
SetPlayerName(pdata.p,pdata.trueName)
checkDone()
end)
TriggerAddAction(t2,function()
local pdata = Player2Data(GetTriggerPlayer())
local longest
local hostPlayer
pdata.lobbyTime = tonumber(BlzGetTriggerSyncData())
for _, p in ipairs(PlayerUsers) do
pdata = Player2Data(p)
if pdata.lobbyTime then
if not longest or pdata.lobbyTime > longest then
hostPlayer = p
longest = pdata.lobbyTime
end
else
return
end
end
for _, pdata in ipairs(PlayerData) do
pdata.isHost = pdata.p == hostPlayer
end
HOST_PLAYER = hostPlayer
checkDone()
end)
if PlayerDataLocal.user then
BlzSendSyncData("playerTime",tostring(os.time()))
BlzSendSyncData("playerName",GetPlayerName(LocalPlayer))
BlzSendSyncData("hostDetect",tostring(LOCAL_LOBBY_TIME))
end
TriggerAddAction(t,function()
PLAYER_COUNT = PLAYER_COUNT - 1
end)
end
end
do
local stunGroup = {}
local on = false
local function period()
for i = #stunGroup, 1, -1 do
local u = stunGroup[i]
local udata = UnitData[GetUnitUserData(u)]
if udata.stunTime <= GAME_TIME or not UnitAlive(u) then
BlzPauseUnitEx(u,false)
DestroyEffect(udata.StunEffect)
udata.StunEffect = nil
udata.stunTime = nil
stunGroup[i] = stunGroup[#stunGroup]
stunGroup[#stunGroup] = nil
if #stunGroup == 0 then
RemoveActionInTimerQueue()
on = false
break
end
end
end
end
function StunUnit(u,dur)
local udata = UnitData[GetUnitUserData(u)]
udata.StunResistance = udata.StunResistance or 0
if math.random() < Asymptotic(udata.StunResistance,5) then return end
udata.StunResistance = udata.StunResistance + 1
if not udata.StunEffect then
BlzPauseUnitEx(u,true)
stunGroup[#stunGroup+1] = u
udata.stunTime = GAME_TIME + dur
udata.StunEffect = AddSpecialEffect("Abilities\\Spells\\Human\\Thunderclap\\ThunderclapTarget",GetUnitX(u),GetUnitY(u))
local scale = udata.scale or BlzGetUnitRealField(u,UNIT_RF_SCALING_VALUE)
BlzSetSpecialEffectScale(udata.StunEffect,scale)
BlzSetSpecialEffectZ(udata.StunEffect,BlzGetUnitZ(u)+GetUnitFlyHeight(u)+scale*90)
else
udata.stunTime = udata.stunTime + dur
end
if not on then
on = true
AddActionInTimerQueue(0.03125,period)
end
end
end
do
local freeTable = {}
local tblTracker = {}
function NewTable()
local tbl = freeTable[#freeTable] or {}
tblTracker[tbl] = nil
freeTable[#freeTable] = nil
return tbl
end
function ReleaseTable(tbl)
if not tbl or tblTracker[tbl] then return end
tblTracker[tbl] = true
for i = 1, #tbl, 1 do
tbl[i] = nil
end
for k in pairs (tbl) do
print(k)
rawset(tbl, k, nil)
end
freeTable[#freeTable+1] = tbl
end
function ClearTable(tbl)
if not tbl then return end
for i = 1, #tbl, 1 do
tbl[i] = nil
end
for k in pairs (tbl) do
print("Error: bad table release",k)
rawset(tbl, k, nil)
end
end
function ReleaseTableFast(tbl)
freeTable[#freeTable+1] = tbl
end
function ReleaseKeyTable(tbl)
if not tbl then return end
local list = tbl._list
setmetatable(tbl,nil)
for i = 1, #list, 1 do
local key = list[i]
tbl[key] = nil
list[i] = nil
end
tbl._list = nil
ReleaseTable(list)
--freeTable[#freeTable+1] = list
ReleaseTable(tbl)
--freeTable[#freeTable+1] = tbl
end
metaKeyTable = {
__newindex = function (t,k,v)
rawset(t,k,v)
t._list[#t._list+1] = k
end
}
function NewKeyTable(o)
o = o or NewTable()
o._list = NewTable()
return setmetatable(o,metaKeyTable)
end
function CopyKeyTable(base)
local new = NewTable()
new._list = NewTable()
local newList = new._list
for i, key in ipairs(base._list) do
newList[i] = key
new[key] = base[key]
end
setmetatable(new,metaKeyTable)
end
neighborCells = {{0,1},{-1,0},{0,-1},{1,0}}
local function CheckCell(v,condition,checked)
local new = NewTable()
while true do
for _, vec in ipairs(v) do
for i = 1, 4, 1 do
local cell = isCellValid(neighborCells[i][1] + vec.x, neighborCells[i][2] + vec.y)
if cell and not checked[cell] then
checked[cell] = true
if cell.cross then
ReleaseTable(v)
ReleaseKeyTable(checked)
ReleaseTable(new)
return false
elseif condition(cell) then
new[#new+1] = cell
end
end
end
end
ClearTable(v)
if #new>0 then
for i = #new, 1, -1 do
v[i] = new[i]
new[i] = nil
end
else
ReleaseTableFast(v)
ReleaseTableFast(new)
ReleaseKeyTable(checked)
return true
end
end
end
function IsConfined(x,y,condition)
local tbl = NewTable()
tbl[1] = Cord2Cell(x,y,true)
return CheckCell(tbl,condition,NewKeyTable())
end
local function RangeCheckCell(x,y,range,v,rtn_tbl,filter,checked)
local new = NewTable()
while true do
for _, vec in ipairs(v) do
for i = 1, 4, 1 do
local cell = isCellValid(neighborCells[i][1] + vec.x, neighborCells[i][2] + vec.y)
if cell and not checked[cell] then
checked[cell] = true
local x3, y3 = Cell2Cord(cell.x,cell.y)
if IsInRange(x,y,x3,y3,range) and (not filter or filter(cell)) then
new[#new+1] = cell
rtn_tbl[#rtn_tbl+1] = Vector(x3,y3)
end
end
end
end
ClearTable(v)
if #new>0 then
for i = #new, 1, -1 do
v[i] = new[i]
new[i] = nil
end
else
ReleaseTableFast(v)
ReleaseTableFast(new)
ReleaseKeyTable(checked)
return rtn_tbl
end
end
end
function GetCellsInRange(x,y,range,filter)
local tbl = NewTable()
tbl[1] = Cord2Cell(x,y,true)
return RangeCheckCell(x,y,range,tbl, NewTable(),filter,NewKeyTable(),0)
end
local function SpawnCheckCell(v,checked)
local new = NewTable()
while true do
for _, vec in ipairs(v) do
for i = 1, 4, 1 do
local cell = isCellValid(neighborCells[i][1] + vec.x, neighborCells[i][2] + vec.y)
if cell and not checked[cell] then
if not cell.alive and not cell.built then
new[#new+1] = cell
end
checked[cell] = true
if cell.building then
local tbl = NewTable()
tbl[1] = vec
tbl[2] = cell
table.insert(SpawnableCells,tbl)
end
end
end
end
ClearTable(v)
if #new>0 then
for i = #new, 1, -1 do
v[i] = new[i]
new[i] = nil
end
else
ReleaseTableFast(v)
ReleaseTableFast(new)
ReleaseKeyTable(checked)
return SpawnableCells
end
end
end
SpawnableCells = {}
function GetSpawnCells(isSuper)
if #SpawnableCells>0 then
for i = #SpawnableCells, 1, -1 do
ReleaseTable(SpawnableCells[i])
SpawnableCells[i] = nil
end
end
local resetList
if isSuper then
local resetList = NewTable()
for i=1, #PlayerUnits do
local u = PlayerUnits[i]
if not IsUnitType(u,UNIT_TYPE_STRUCTURE) then
local cell = Cord2Cell(GetUnitX(u),GetUnitY(u),true)
if not cell.building then
cell.building = u
table.insert(resetList,cell)
end
end
end
end
local x,y = GetArenaCenter()
local tbl = NewTable()
tbl[1] = Cord2Cell(x,y,true)
SpawnCheckCell(tbl,NewKeyTable())
if resetList then
for i=1, #resetList do
resetList[i].building = ni
resetList[i] = nil
end
ReleaseTableFast(resetList)
end
return SpawnableCells
end
end
do
Vector = {}
Vector.__index = Vector
local vectorTable = {}
local function newVector( x, y )
local newV = vectorTable[#vectorTable] or setmetatable( {}, Vector )
vectorTable[#vectorTable] = nil
newV.x = x or 0
newV.y = y or 0
return newV
end
function ReleaseVector(v)
if not v then return end
table.insert(vectorTable,v)
end
function DuplicateVectorTable(tbl)
local new = NewTable()
for i, vec in ipairs(tbl) do
new[i] = vec
end
return new
end
function ReleaseVectorTable(tbl)
for _, vec in ipairs(tbl) do
table.insert(vectorTable,vec)
end
ReleaseTable(tbl)
end
function isvector( vTbl )
return getmetatable( vTbl ) == Vector
end
function Vector.__unm( vTbl )
return newVector( -vTbl.x, -vTbl.y )
end
function Vector.__add( a, b )
return newVector( a.x + b.x, a.y + b.y )
end
function Vector.__sub( a, b )
return newVector( a.x - b.x, a.y - b.y )
end
function Vector.__mul( a, b )
if type( a ) == "number" then
return newVector( a * b.x, a * b.y )
elseif type( b ) == "number" then
return newVector( a.x * b, a.y * b )
else
return newVector( a.x * b.x, a.y * b.y )
end
end
function Vector.__div( a, b )
return newVector( a.x / b, a.y / b )
end
function Vector.__eq( a, b )
return a.x == b.x and a.y == b.y
end
function Vector:__tostring()
return "(" .. self.x .. ", " .. self.y .. ")"
end
function Vector:ID()
if self._ID == nil then
local x, y = self.x, self.y
self._ID = 0.5 * ( ( x + y ) * ( x + y + 1 ) + y )
end
return self._ID
end
setmetatable( Vector, { __call = function( _, ... ) return newVector( ... ) end } )
end
do
Heap = {}
Heap.__index = Heap
local function findLowest( a, b )
return a < b
end
local function newHeap( template, compare )
local tbl = NewTable()
tbl.Data = NewTable()
tbl.Compare = compare or findLowest
tbl.Size = 0
return setmetatable(tbl,template)
end
function Heap:Release()
ReleaseTable(self.Data)
self.Compare = nil
self.Data = nil
self.Size = nil
setmetatable(self, nil)
ReleaseTable(self)
end
local function sortUp( heap, index )
if index <= 1 then return end
local pIndex = index % 2 == 0 and math.round(index / 2) or math.round(( index - 1 ) / 2)
if not heap.Compare( heap.Data[pIndex], heap.Data[index] ) then
heap.Data[pIndex], heap.Data[index] = heap.Data[index], heap.Data[pIndex]
sortUp( heap, pIndex )
end
end
local function sortDown( heap, index )
local leftIndex, rightIndex, minIndex
leftIndex = index * 2
rightIndex = leftIndex + 1
if rightIndex > heap.Size then
if leftIndex > heap.Size then return
else minIndex = leftIndex end
else
if heap.Compare( heap.Data[leftIndex], heap.Data[rightIndex] ) then minIndex = leftIndex
else minIndex = rightIndex end
end
if not heap.Compare( heap.Data[index], heap.Data[minIndex] ) then
heap.Data[index], heap.Data[minIndex] = heap.Data[minIndex], heap.Data[index]
sortDown( heap, minIndex )
end
end
function Heap:Empty()
return self.Size == 0
end
function Heap:Clear()
ReleaseTable(self.Data)
self.Data, self.Size, self.Compare = NewTable(), 0, self.Compare or findLowest
return self
end
function Heap:Push( item )
if item then
self.Size = self.Size + 1
self.Data[self.Size] = item
sortUp( self, self.Size )
end
return self
end
function Heap:Pop()
local root
if self.Size > 0 then
root = self.Data[1]
self.Data[1] = self.Data[self.Size]
self.Data[self.Size] = nil
self.Size = self.Size - 1
if self.Size > 1 then
sortDown( self, 1 )
end
end
return root
end
setmetatable( Heap, { __call = function( self, ... ) return newHeap( self, ... ) end } )
end
do
Astar = {}
Astar.__index = Astar
-- This instantiates a new Astar class for usage later.
-- "start" and "finish" should both be 2 dimensional vectors, or just a table with "x" and "y" keys.
-- positionOpenCheck can be a function or a table.
-- If it's a function it must have a return value of true or false depending on whether or not the position is open.
-- If it's a table it should simply be a table of values such as "pos[x][y] = true".
function Astar:Initialize( start, finish, positionOpenCheck )
local tbl = NewTable()
tbl.Start = start
tbl.Finish = finish
tbl.PositionOpenCheck = positionOpenCheck
local newPath = setmetatable( tbl, Astar )
newPath:CalculatePath()
return newPath
end
function Astar:Release()
self.Start = nil
self.Finish = nil
self.PositionOpenCheck = nil
ReleaseTable(self.Path)
self.Path = nil
ReleaseTable(self)
setmetatable(self, nil)
end
function Astar:CleanPath()
local vecLast
local path = self.Path
local i = 1
local remove = NewTable()
while i < #path do
local vec = path[i]
if i > 1 and i < #path then
local difX=math.abs(path[i-1].x-path[i+1].x)
local difY=math.abs(path[i-1].y-path[i+1].y)
if difX==1 and difY==1 then
table.insert(remove,i)
end
end
i = i + 1
end
for i = #remove, 1, -1 do
table.remove(path,remove[i])
end
ReleaseTable(remove)
end
local function distance( start, finish )
local dx = start.x - finish.x
local dy = start.y - finish.y
return dx * dx + dy * dy
end
local positionIsOpen
local function positionIsOpenTable( pos, check ) return check[pos.x] and check[pos.x][pos.y] end
local function positionIsOpenCustom( pos, check ) return check( pos ) end
local adjacentPositions = {
Vector( 0, -1 ),
Vector( -1, 0 ),
Vector( 0, 1 ),
Vector( 1, 0 )
}
local function fetchOpenAdjacentNodes( pos, positionOpenCheck )
local result = NewTable()
for i = 1, #adjacentPositions do
local adjacentPos = isCellValid(pos.x+adjacentPositions[i].x, pos.y+adjacentPositions[i].y)
if adjacentPos and positionIsOpen( adjacentPos, positionOpenCheck ) then
table.insert( result, adjacentPos )
end
end
return result
end
local function compareFunc(a,b)
return a.fScore < b.fScore
end
-- This is the function used to actually calculate the path.
-- It returns the calcated path table, or nil if it cannot find a path.
function Astar:CalculatePath()
local start, finish, positionOpenCheck = self.Start, self.Finish, self.PositionOpenCheck
if not positionOpenCheck then return end
positionIsOpen = type( positionOpenCheck ) == "table" and positionIsOpenTable or positionIsOpenCustom
if not positionIsOpen( finish, positionOpenCheck ) then return end
local open = Heap()
local touched = NewTable()
touched[1] = start
start.touched = true
start.gScore = 0
start.hScore = distance( start, finish )
start.fScore = start.hScore
open.Compare = compareFunc
open:Push( start )
while not open:Empty() do
local current = open:Pop()
if not current.closed then
if current == finish then
local path = NewTable()
while true do
if current.previous then
table.insert( path, 1, current )
current = current.previous
else
table.insert( path, 1, start )
self.Path = path
for _, vec in ipairs(touched) do
vec.gScore = nil
vec.hScore = nil
vec.fScore = nil
vec.closed = nil
vec.touched = nil
vec.previous = nil
end
ReleaseTable(touched)
open:Release()
return path
end
end
end
current.closed = true
local adjacents = fetchOpenAdjacentNodes( current, positionOpenCheck )
for i = 1, #adjacents do
local adjacent = adjacents[i]
if not adjacent.closed then
if not adjacent.touched then
table.insert(touched,adjacent)
adjacent.touched = true
end
local added_gScore = current.gScore + distance( current, adjacent )
if not adjacent.gScore or added_gScore < adjacent.gScore then
adjacent.gScore = added_gScore
if not adjacent.hScore then
adjacent.hScore = distance( adjacent, finish )
end
adjacent.fScore = added_gScore + adjacent.hScore
open:Push( adjacent )
adjacent.previous = current
end
end
end
ReleaseTable(adjacents)
end
end
for _, vec in ipairs(touched) do
vec.gScore = nil
vec.hScore = nil
vec.fScore = nil
vec.closed = nil
vec.touched = nil
vec.previous = nil
end
ReleaseTable(touched)
open:Release()
end
function Astar:GetPath()
return self.Path
end
function Astar:GetDistance()
local path = self.Path
if not path then return end
return distance( path[1], path[#path] )
end
function Astar:GetTiles()
local path = self.Path
if not path then return end
return #path
end
function Astar:__tostring()
local path = self.Path
local string = ""
if path then
for k, v in ipairs( path ) do
local formatted = ( k .. ": " .. v )
string = k == 1 and formatted or string .. "\n" .. formatted
end
end
return string
end
setmetatable( Astar, { __call = function( self, ... ) return self:Initialize( ... ) end } )
end
do
MainSelectedUnit = 0
local containerFrame
local frames = {}
local group
local units = {}
local filter = Filter(function()
local unit = GetFilterUnit()
local prio = BlzGetUnitRealField(unit, UNIT_RF_PRIORITY)
local found = false
-- compare the current unit with allready found, to place it in the right slot
for index, value in ipairs(units) do
-- higher prio than this take it's slot
if BlzGetUnitRealField(value, UNIT_RF_PRIORITY) < prio then
table.insert(units, index, unit)
found = true
break
-- equal prio and better colisions Value
elseif BlzGetUnitRealField(value, UNIT_RF_PRIORITY) == prio and GetUnitOrderValue(value) > GetUnitOrderValue(unit) then
table.insert( units, index, unit)
found = true
break
end
end
-- not found add it at the end
if not found then
table.insert(units, unit)
end
unit = nil
return false
end)
function GetSelectedUnitIndex()
-- local player is in group selection?
if BlzFrameIsVisible(containerFrame) then
-- find the first visible yellow Background Frame
for int = 0, #frames do
if BlzFrameIsVisible(frames[int]) then
return int
end
end
end
return nil
end
function GetUnitOrderValue(unit)
--heroes use the handleId
if IsUnitType(unit, UNIT_TYPE_HERO) then
return GetHandleId(unit)
else
--units use unitCode
return GetUnitTypeId(unit)
end
end
function GetMainSelectedUnit(index)
if index then
GroupEnumUnitsSelected(group, LocalPlayer, filter)
local unit = units[index + 1]
--clear table
repeat until not table.remove(units)
return unit
else
GroupEnumUnitsSelected(group, LocalPlayer, nil)
return FirstOfGroup(group)
end
end
function InitPrimarySelection()
local console = BlzGetFrameByName("ConsoleUI", 0)
local bottomUI = BlzFrameGetChild(console, 1)
local groupframe = BlzFrameGetChild(bottomUI, 5)
local t = CreateTrigger()
for i, p in ipairs(PlayerUsers) do
BlzTriggerRegisterPlayerSyncEvent(t,p,"mainSel",false)
end
TriggerAddAction(t, function()
DynamicAbilCheck(GetTriggerPlayer(),Index2Unit[tonumber(BlzGetTriggerSyncData())])
end)
TimerStart(CreateTimer(), 0.001, true, function()
local u = GetMainSelectedUnit(GetSelectedUnitIndex())
if u ~= MainSelectedUnit and u ~= nil then
SoftAbilityCheck(u)
MainSelectedUnit = u
BlzSendSyncData("mainSel",tostring(GetUnitUserData(u)))
end
end)
--globals
--if true then return end
containerFrame = BlzFrameGetChild(bottomUI, 1)
group = CreateGroup()
-- give this frames a handleId
for int = 0, BlzFrameGetChildrenCount(containerFrame) - 1 do
local buttonContainer = BlzFrameGetChild(containerFrame, int)
frames[int] = BlzFrameGetChild(buttonContainer, 0)
end
end
end
do
DeathActions = {}
function InitDeathEvent()
local t = CreateTrigger()
for i, data in ipairs(PlayerData) do
TriggerRegisterPlayerUnitEvent(t,data.p,EVENT_PLAYER_UNIT_DEATH, nil)
end
TriggerAddAction(t,function()
local victim = GetTriggerUnit()
local killer = GetKillingUnit()
local index = GetUnitUserData(victim)
for i, v in ipairs(DeathActions) do
v(victim,killer,index)
end
end)
end
end
do
DamageActions = {pre = {}, post = {}}
local fragHP, held
function InitDamageEvent()
local t = CreateTrigger()
local t2 = CreateTrigger()
for _, data in ipairs(PlayerData) do
TriggerRegisterPlayerUnitEvent(t,data.p,EVENT_PLAYER_UNIT_DAMAGING, nil)
TriggerRegisterPlayerUnitEvent(t2,data.p,EVENT_PLAYER_UNIT_DAMAGED, nil)
end
local freeTable = {}
local function BundleDamageData()
local newTable = freeTable[#freeTable] or {}
freeTable[#freeTable] = nil
newTable.victim = BlzGetEventDamageTarget()
newTable.victimOwner = Player2Data(GetOwningPlayer(newTable.victim))
newTable.victimData = UnitData[GetUnitUserData(newTable.victim)]
newTable.source = GetEventDamageSource()
newTable.sourceOwner = Player2Data(GetOwningPlayer(newTable.source))
newTable.sourceData = UnitData[GetUnitUserData(newTable.source)]
newTable.damage = GetEventDamage()
newTable.attackType = BlzGetEventAttackType()
newTable.damageType = BlzGetEventDamageType()
newTable.weaponType = BlzGetEventWeaponType()
newTable.isAttack = BlzGetEventIsAttack()
return newTable
end
TriggerAddAction(t,function()
local data = BundleDamageData()
if data.source and data.victim then
if data.victimOwner ~= data.sourceOwner and data.victimOwner.user and data.sourceOwner.user then BlzSetEventDamage(0) return end
for _, func in ipairs(DamageActions.pre) do
func(data)
end
end
table.insert(freeTable,data)
end)
TriggerAddAction(t2,function()
local data = BundleDamageData()
if data.source and data.victim then
for _, func in ipairs(DamageActions.post) do
func(data)
end
fragHP = data.victimData.fragileHP
if fragHP then
data.victimData.heldDamage = data.victimData.heldDamage or 0
fragHP = math.min(fragHP,data.damage+data.victimData.heldDamage)
data.victimData.heldDamage = fragHP - math.floor(fragHP)
fragHP = math.floor(fragHP)
BlzSetUnitMaxHP(data.victim,BlzGetUnitMaxHP(data.victim)-fragHP)
BlzSetEventDamage(data.damage-fragHP)
data.victimData.fragileHP = data.victimData.fragileHP - fragHP
if data.victimData.fragileHP == 0 then
data.victimData.fragileHP = nil
end
end
end
table.insert(freeTable,data)
end)
end
end
do
AttackActions = {}
function InitAttackEvent()
local t = CreateTrigger()
for _, data in ipairs(PlayerData) do
TriggerRegisterPlayerUnitEvent(t,data.p,EVENT_PLAYER_UNIT_ATTACKED, nil)
end
local freeTable = {}
local function BundleAttackData()
local newTable = freeTable[#freeTable] or {}
freeTable[#freeTable] = nil
newTable.victim = GetTriggerUnit()
newTable.victimOwner = Player2Data(GetOwningPlayer(newTable.victim))
newTable.victimData = UnitData[GetUnitUserData(newTable.victim)]
newTable.source = GetAttacker()
newTable.sourceOwner = Player2Data(GetOwningPlayer(newTable.source))
newTable.sourceData = UnitData[GetUnitUserData(newTable.source)]
return newTable
end
TriggerAddAction(t,function()
local data = BundleAttackData()
for _, func in ipairs(AttackActions) do
func(data)
end
table.insert(freeTable,data)
end)
end
end
do
OrderActions = {}
function OrderIdToTable(order)
if type(order) == "string" then
order = OrderId(order)
end
OrderActions[order] = OrderActions[order] or {}
return OrderActions[order]
end
function InitOrderEvent()
local t = CreateTrigger()
for i, p in ipairs(PlayerUsers) do
TriggerRegisterPlayerUnitEvent(t,p,EVENT_PLAYER_UNIT_ISSUED_ORDER, nil)
TriggerRegisterPlayerUnitEvent(t,p,EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, nil)
TriggerRegisterPlayerUnitEvent(t,p,EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, nil)
end
local freeTable = {}
TriggerAddAction(t,function()
local data = OrderActions[GetIssuedOrderId()]
if data then
local newTable = freeTable[#freeTable] or {}
freeTable[#freeTable] = nil
newTable.source = GetTriggerUnit()
newTable.sourceData = UnitData[GetUnitUserData(newTable.source)]
newTable.tarUnit = GetOrderTargetUnit()
newTable.tarUnitData = UnitData[GetUnitUserData(newTable.tarUnit)]
newTable.tarDest = GetOrderTargetDestructable()
newTable.tarX = GetOrderPointX()
newTable.tarY = GetOrderPointY()
newTable.owner = GetOwningPlayer(newTable.source)
for _, func in ipairs(data) do
func(newTable)
end
table.insert(freeTable,newTable)
end
end)
end
end
do
SHINY_GEMS_AMOUNT = .22
ignoreAttackOrder = false
miningStartColor = {r = 255, g = 255, b = 255}
local didInitRecycler = false
function InitRecycler()
if didInitRecycler then return end
didInitRecycler = true
ActionAdd(DeathActions,true,function(victim,killer,index)
if killer == nil then return end
local pdata = Player2Data(GetOwningPlayer(killer))
if pdata.RecyclerPerk and IsUnitType(killer,UNIT_TYPE_STRUCTURE) and UnitData[index].waveType then
local which = math.random(#ResourceTypes)
local amount = easeOutCubic(GetRandomReal(0.1,1))*CURRENT_NIGHT + pdata.mineRate[which]
AddMinerals(pdata,which,(pdata.MineralProcessing or 0) + amount)
MiningTextTag(pdata,amount, ResourceTypes[which].color, miningStartColor, GetUnitX(victim), GetUnitY(victim), GetUnitX(pdata.playerMiner),GetUnitY(pdata.playerMiner), 2)
end
end)
end
function InitMining()
ActionAdd(ChatActions,true,function(pdata,msg)
if string.lower(msg) == "-text" then
Setting.toggle(pdata,Setting.text)
end
end)
ActionAdd(OrderIdToTable("smart"), true, function(data)
if data.tarUnitData and data.tarUnitData.mineralType and UnitHasAbility(data.source,AbilityList.harvest) then
data.sourceData.mineralTarget = data.tarUnit
ignoreAttackOrder = true
if data.sourceData.jumpTable then
ReleaseTable(data.sourceData.jumpTable)
data.sourceData.jumpTable = nil
end
IssueTargetOrder(data.source,"attack",data.tarUnit)
else
data.sourceData.mineralTarget = nil
data.sourceData.sampleTarget = nil
end
end)
ActionAdd(OrderIdToTable("attack"), true, function(data)
if ignoreAttackOrder then
ignoreAttackOrder = false
else
data.sourceData.mineralTarget = nil
end
data.sourceData.sampleTarget = nil
end)
ActionAdd(AttackActions,true,function(data)
if data.source == data.sourceOwner.unitSelectionMain and UnitHasAbility(data.source,AbilityList.examineGem) and data.victimData.mineralType then
RemoveUnitAbility(data.source,AbilityList.examineGem)
AddUnitAbility(data.source,AbilityList.qualityIncrease)
end
end)
local function chainMineTarget(pdata, source, sourceData, curTarget, sourceX,sourceY, range, jumps, crit)
local jumpPos = 1
local ignore = curTarget
while jumps > 0 do
local x1,y1 = GetUnitX(curTarget),GetUnitY(curTarget)
jumps = jumps - 1
if sourceData.jumpTable and UnitAlive(sourceData.jumpTable[jumpPos]) then
curTarget = sourceData.jumpTable[jumpPos]
else
if jumpPos == 1 then
if sourceData.jumpTable then
ClearTable(sourceData.jumpTable)
else
sourceData.jumpTable = NewTable()
end
end
local g = NewGroup()
GroupEnumUnitsInRange(g,x1,y1,range,nil)
GroupRemoveUnit(g,ignore)
local closestRange
curTarget = nil
for i = 0, BlzGroupGetSize(g)-1, 1 do
local target = BlzGroupUnitAt(g,i)
local targetData = UnitData[GetUnitUserData(target)]
local targetFilter = targetData.filterSelf
if targetFilter and UnitAlive(target) and FILTER_MINERAL & targetFilter == FILTER_MINERAL and not table.hasElement(sourceData.jumpTable,target) then
local curRange = FastDistance(x1,y1,GetUnitX(target),GetUnitY(target))
if not closestRange or curRange < closestRange then
closestRange = curRange
curTarget = target
end
end
end
ReleaseGroup(g)
if curTarget then
table.insert(sourceData.jumpTable,curTarget)
end
end
if curTarget then
targetData = UnitData[GetUnitUserData(curTarget)]
local life = GetWidgetLife(curTarget)
local amount = math.min(pdata.mineRate[targetData.mineralType]+(crit and pdata.criticalSucessLevel or 0)+(pdata.ShinyGems and targetData.mineralQuality * SHINY_GEMS_AMOUNT or 0),life-1)
SetWidgetLife(curTarget,life - amount)
local x2,y2 = GetUnitX(curTarget), GetUnitY(curTarget)
if pdata.MineralProcessing then
AddMinerals(pdata,targetData.mineralType,pdata.MineralProcessing + amount)
MiningTextTag(pdata,tostring(pdata.MineralProcessing + amount), ResourceTypes[targetData.mineralType].color, miningStartColor, x2,y2, sourceX,sourceY, 2)
else
AddMinerals(pdata,targetData.mineralType,amount)
MiningTextTag(pdata,tostring(amount), ResourceTypes[targetData.mineralType].color, miningStartColor, x2,y2, sourceX,sourceY, 2)
end
AddLightningToDestroyQueue(AddLightningEx("SPSB",true,x1,y1,64,x2,y2,64),.5)
else
break
end
jumpPos = jumpPos + 1
end
end
local function textTime(tbl)
tbl.durCur = math.max(tbl.durCur - 0.03125,0)
if tbl.tag then
local time = tbl.durCur/tbl.dur
local ease = easeOutQuad(time)
SetTextTagText(tbl.tag,tbl.text,lerp(easeInOutBounce(time),0.0345,0))
SetTextTagPos(tbl.tag,lerp(ease,tbl.endX,tbl.startX),lerp(ease,tbl.endY,tbl.startY),lerp(easeInOutBack(time),128,0))
SetTextTagColor(tbl.tag,math.floor(lerp(ease,tbl.startColor.r,tbl.endColor.r)),math.floor(lerp(ease,tbl.startColor.g,tbl.endColor.g)),math.floor(lerp(ease,tbl.startColor.b,tbl.endColor.b)),100)
end
if tbl.durCur == 0 then
tbl.tag = nil
tbl.text = nil
tbl.startColor = nil
tbl.endColor = nil
tbl.startX = nil
tbl.startY = nil
tbl.endX = nil
tbl.endY = nil
tbl.dur = nil
tbl.durCur = nil
ReleaseTable(tbl)
RemoveActionInTimerQueue()
end
end
function MiningTextTag(pData,text, startColor, endColor, startX, startY, endX, endY, dur)
if not Setting.enabled(pData,Setting.text) then return end
local tbl = NewTable()
if pData == PlayerDataLocal then
text = "+"..string.gsub(string.format("%%.1f",text),"%%.0","")
tbl.tag = CreateTextTag()
SetTextTagPermanent(tbl.tag,false)
SetTextTagLifespan(tbl.tag,dur+.25)
SetTextTagFadepoint(tbl.tag,dur-.75)
end
local time
local ease
tbl.text = text
tbl.startColor = startColor
tbl.endColor = endColor
tbl.startX = startX
tbl.startY = startY
tbl.endX = endX
tbl.endY = endY
tbl.dur = dur
tbl.durCur = dur
AddActionInTimerQueue(0.03125,textTime,tbl)
end
ActionAdd(DamageActions.post,true,function(data)
if data.sourceData.mineralTarget and data.sourceData.mineralTarget == data.victim and data.isAttack then
local pData = data.sourceOwner
local minType = data.victimData.mineralType
local amount = data.sourceOwner.mineRate[minType]
local x,y = GetUnitX(data.source), GetUnitY(data.source)
local crit = pData.criticalSucessLevel and math.GetPlayerRandom(pData,pData.criticalSuccess)
if pData.PerfectGems and pData.PerfectGems <= GAME_TIME then
SetMineralQuality(data.victim,data.victimData.mineralQuality+1,true)
pData.PerfectGems = GAME_TIME + 60
end
if crit then
amount = amount + pData.criticalSucessLevel
DestroyEffect(AddSpecialEffect("CriticalSuccess",x,y))
end
if pData.ShinyGems then
amount = amount + data.victimData.mineralQuality * SHINY_GEMS_AMOUNT
end
amount = math.min(amount,GetWidgetLife(data.victim)-1)
BlzSetEventDamage(amount)
if pData.MineralProcessing then
AddMinerals(pData,minType,pData.MineralProcessing + amount)
MiningTextTag(pData,pData.MineralProcessing + amount, ResourceTypes[minType].color, miningStartColor, GetUnitX(data.victim), GetUnitY(data.victim), x,y, 2)
else
AddMinerals(pData,minType,amount)
MiningTextTag(pData,amount, ResourceTypes[minType].color, miningStartColor, GetUnitX(data.victim), GetUnitY(data.victim), x,y, 2)
end
pData.mineLast = GAME_TIME + BlzGetUnitAttackCooldown(data.source,0) + .15
if pData.chainMineLevel then
chainMineTarget(pData,data.source, data.sourceData, data.victim,x,y, BlzGetUnitWeaponRealField(data.source,UNIT_WEAPON_RF_ATTACK_RANGE,0), pData.chainMineLevel, crit)
end
end
end)
end
end
do
EveryUnit = {}
PlayerUnits = {}
SolarUnits = {}
local groupAction = {add = {}, remove = {}}
function AddUnitToSynthGroup(u,g)
local udata = UnitData[GetUnitUserData(u)]
if udata[g] then return end
udata[g] = true
udata.synthGroups = udata.synthGroups or NewTable()
udata.synthGroups[#udata.synthGroups+1] = g
g[#g+1] = u
if groupAction.add[g] then
groupAction.add[g](u)
end
end
function RemoveUnitFromSynthGroup(u,g)
local udata = UnitData[GetUnitUserData(u)]
if not udata[g] then return end
udata[g] = nil
table.removeElementFast(udata.synthGroups,g)
table.removeElementFast(g,u)
if groupAction.remove[g] then
groupAction.remove[g](u)
end
end
function SynthGroupContainsUnit(g,u)
return UnitData[GetUnitUserData(u)][g]
end
function SynthGroupAddRemovalAction(g,func)
groupAction.remove[g] = func
end
function SynthGroupAddNewAction(g,func)
groupAction.add[g] = func
end
function InitSytheticGroups()
for i = 1, #PlayerData do
PlayerData[i].AllUnits = {}
end
local function removePlayerUnit(u,index)
local udata = UnitData[index]
local synthGroup = udata.synthGroups
if not synthGroup then return end
for i = #synthGroup, 1, -1 do
RemoveUnitFromSynthGroup(u,synthGroup[i])
end
ReleaseTable(synthGroup)
udata.synthGroups = nil
end
ActionAdd(DeathActions, true,function(u)
if not IsUnitType(u,UNIT_TYPE_HERO) then
removePlayerUnit(u,GetUnitUserData(u))
end
end)
ActionAdd(IndexerActions.remove, true,removePlayerUnit)
ActionAdd(IndexerActions.add, true,function(u)
local pdata = Player2Data(GetOwningPlayer(u))
AddUnitToSynthGroup(u,pdata.AllUnits)
AddUnitToSynthGroup(u,EveryUnit)
if pdata.user then
AddUnitToSynthGroup(u,PlayerUnits)
end
end)
end
end
do
OBJECT_MAX_RANGE = 2048
FILTER_USES_POWER = bit(1)
FILTER_CAN_BE_HEALED = bit(2)
FILTER_BUILT = bit(3)
FILTER_THUNDER = bit(4)
FILTER_MINERAL = bit(5)
FILTER_BATTERY = bit(6)
ObjectGroups_ObjUnits = {}
ObjectGroups_attachTbl = {filter = {},grouping = {}}
function AttachFilterToUnitType(whichType, whichFilter)
ObjectGroups_attachTbl.filter[whichType] = whichFilter
end
function AttachFilterToUnit(index, whichFilter)
local udata = UnitData[index]
if udata.filterSelf then
udata.filterSelf = setbit(udata.filterSelf,whichFilter)
else
udata.filterSelf = whichFilter
end
end
function AttachObjectGroupingToUnitType(whichType, whichFilter, range, actions, antiFilter)
ObjectGroups_attachTbl.grouping[whichType] = {filter = whichFilter, range = range, actions = actions, antiFilter = antiFilter}
end
local function SortDist(k1,k2)
return k1.dist < k2.dist
end
function SortGroupByDistance(tbl,x,y)
if tbl~=nil and #tbl>1 then
local size = #tbl
for i = 1, size do
local u = tbl[i]
local that = NewTable()
that.u = u
that.dist = FastDistance(x,y,GetUnitX(u),GetUnitY(u))
tbl[i] = that
end
table.sort(tbl,SortDist)
for i = 1, size do
local val = tbl[i]
local u = val.u
val.u = nil
val.dist = nil
ReleaseTableFast(val)
tbl[i] = u
end
end
end
function CheckNearbyObjects(source)
local sourceFilter = UnitData[GetUnitUserData(source)].filterSelf
if not sourceFilter then return end
local g = NewGroup()
local x,y = GetUnitX(source),GetUnitY(source)
GroupEnumUnitsInRange(g,x,y,OBJECT_MAX_RANGE,nil)
GroupRemoveUnit(g,source)
for i = 0, BlzGroupGetSize(g)-1 do
local target = BlzGroupUnitAt(g,i)
local targetData = UnitData[GetUnitUserData(target)]
local targetFilter = targetData.filterTarget
local x2,y2 = GetUnitX(target),GetUnitY(target)
if targetFilter and IsInRange(x,y,x2,y2,targetData.range) and UnitAlive(target) and targetFilter & sourceFilter == targetFilter then
local tarAntiFilter = targetData.antiFilter
if not tarAntiFilter or (tarAntiFilter and tarAntiFilter & sourceFilter ~= tarAntiFilter) then
targetData.GroupObjects = targetData.GroupObjects or NewTable()
table.safeInsert(targetData.GroupObjects,source)
SortGroupByDistance(targetData.GroupObjects,x2,y2)
end
end
end
ReleaseGroup(g)
end
function NewGroupObject(source, range, sourceFilter,actions,add, antiFilter)
local g = NewGroup()
local x,y = GetUnitX(source), GetUnitY(source)
GroupEnumUnitsInRange(g,x,y,range,nil)
GroupRemoveUnit(g,source)
local sourceData = UnitData[GetUnitUserData(source)]
sourceData.range = range
sourceData.filterTarget = sourceFilter
sourceData.antiFilter = antiFilter
for i = 0, BlzGroupGetSize(g)-1 do
local target = BlzGroupUnitAt(g,i)
local targetFilter = UnitData[GetUnitUserData(target)].filterSelf
if UnitAlive(target) and targetFilter and sourceFilter & targetFilter == sourceFilter then
if not antiFilter or (antiFilter and antiFilter & targetFilter ~= antiFilter) then
sourceData.GroupObjects = sourceData.GroupObjects or NewTable()
table.safeInsert(sourceData.GroupObjects,target)
end
end
end
ReleaseGroup(g)
sourceData.GroupObjectAction = actions
if add then
table.safeInsert(ObjectGroups_ObjUnits,source)
end
SortGroupByDistance(sourceData.GroupObjects,x,y)
end
function RemoveObjectForOtherObjects(source)
local g = NewGroup()
GroupEnumUnitsInRange(g,GetUnitX(source), GetUnitY(source),OBJECT_MAX_RANGE,nil)
GroupRemoveUnit(g,source)
for i = 0, BlzGroupGetSize(g)-1 do
local tbl = UnitData[GetUnitUserData(BlzGroupUnitAt(g,i))].GroupObjects
if tbl ~= nil then
table.removeElement(tbl, source)
end
end
ReleaseGroup(g)
end
function RefreshGroupObject(source, range, sourceFilter,actions, antiFilter)
RemoveObjectForOtherObjects(source)
local udata = UnitData[GetUnitUserData(source)]
if udata.GroupObjects then
ReleaseTable(udata.GroupObjects)
end
udata.GroupObjects = nil
NewGroupObject(source,range,sourceFilter,actions,false,antiFilter)
CheckNearbyObjects(source)
end
function enumObjectGroups()
for i = #ObjectGroups_ObjUnits, 1, -1 do
local u = ObjectGroups_ObjUnits[i]
local sourceData = UnitData[GetUnitUserData(u)]
if sourceData.GroupObjects then
for j = 1, #sourceData.GroupObjects, 1 do
local target = sourceData.GroupObjects[j]
targetData = UnitData[GetUnitUserData(target)]
if sourceData.GroupObjectAction(u,target,sourceData,targetData) then
break
end
end
end
end
end
function InitObjectGrouping()
AddActionInTimerQueue(1, enumObjectGroups)
ActionAdd(IndexerActions.add, true,function(u,index)
local whichType = GetUnitTypeId(u)
if IsUnitType(u,UNIT_TYPE_STRUCTURE) and Player2Data(GetOwningPlayer(u)).user then
AttachFilterToUnit(index, FILTER_CAN_BE_HEALED)
end
if ObjectGroups_attachTbl.filter[whichType] then
AttachFilterToUnit(index, ObjectGroups_attachTbl.filter[whichType])
end
local groupData = ObjectGroups_attachTbl.grouping[whichType]
if groupData then
NewGroupObject(u, groupData.range,groupData.filter,groupData.actions,true, groupData.antiFilter)
elseif UnitData[index].filterSelf then
CheckNearbyObjects(u)
end
end)
local function onRemove(u,index)
if UnitData[index].filterSelf then
RemoveObjectForOtherObjects(u)
UnitData[index].filterSelf = nil
end
if UnitData[index].filterTarget then
if UnitData[index].GroupObjects then
ReleaseTable(UnitData[index].GroupObjects)
end
UnitData[index].filterTarget = nil
table.removeElement(ObjectGroups_ObjUnits,u)
end
end
ActionAdd(IndexerActions.remove, true,onRemove)
ActionAdd(DeathActions, true, function(u,_,index)
onRemove(u,index)
end)
end
end
do
local lightTbl = {}
local destTbl = {}
local sfxTbl = {}
ActionAdd(ChatActions,true,function(pdata,msg)
if string.lower(msg) == "-lightning" then
Setting.toggle(pdata,Setting.lightning)
end
end)
function AddLightningToDestroyQueue(light,dur)
local max = #lightTbl
lightTbl[max+1] = light
lightTbl[max+2] = dur+GAME_TIME
if not Setting.enabled(PlayerDataLocal,Setting.lightning) then
SetLightningColor(light,0,0,0,0)
end
end
function AddDestToDestroyQueue(dest,dur)
local max = #destTbl
destTbl[max+1] = dest
destTbl[max+2] = dur+GAME_TIME
end
function AddEffectDestroyQueue(sfx,dur)
local max = #sfxTbl
sfxTbl[max+1] = sfx
sfxTbl[max+2] = dur+GAME_TIME
end
local function destroyObjectTimed()
local max = #lightTbl
for i = 1, max, 2 do
if lightTbl[i+1] <= GAME_TIME then
DestroyLightning(lightTbl[i])
lightTbl[i] = lightTbl[max-1]
lightTbl[i+1] = lightTbl[max]
lightTbl[max] = nil
lightTbl[max-1] = nil
max = max - 2
i = i -2
end
end
max = #destTbl
for i = 1, max, 2 do
if destTbl[i+1] <= GAME_TIME then
RemoveDestructable(destTbl[i])
destTbl[i] = destTbl[max-1]
destTbl[i+1] = destTbl[max]
destTbl[max] = nil
destTbl[max-1] = nil
max = max - 2
i = i -2
end
end
max = #sfxTbl
for i = 1, max, 2 do
if destTbl[i+1] <= GAME_TIME then
DestroyEffect(sfxTbl[i])
sfxTbl[i] = sfxTbl[max-1]
sfxTbl[i+1] = sfxTbl[max]
sfxTbl[max] = nil
sfxTbl[max-1] = nil
max = max - 2
i = i -2
end
end
end
function InitTimedLightning()
AddActionInTimerQueue(.1,destroyObjectTimed)
end
end
do
local TIMER_TABLE = {}
local removeQueue
local curAction
local function TIMER_RUN()
local data = GetTimerData()
local actions = data.actions
for i = #actions, 1, -1 do
local func = actions[i]
curAction = func
func.action(func.data)
if func.remove then
actions[i] = actions[#actions]
actions[#actions] = nil
func.action = nil
func.data = nil
func.remove = nil
ReleaseTable(func)
end
end
end
function AddActionInTimerQueue(time,action,data)
local this = TIMER_TABLE[time]
if not this then
TIMER_TABLE[time] = NewTable()
this = TIMER_TABLE[time]
this.actions = NewTable()
this.time = time
this.timers = NewTimer(this)
TimerStart(this.timers,time,true,TIMER_RUN)
end
local tbl = NewTable()
tbl.action = action
tbl.data = data
this.actions[#this.actions+1] = tbl
end
function RemoveActionInTimerQueue(time,action)
if not time then
curAction.remove = true
elseif TIMER_TABLE[time] then
local which
local this = TIMER_TABLE[time]
for i = 1, #this.actions do
if this.actions[i].action == action then
which = i
break
end
end
if not which then return end
this.actions[which].action = nil
this.actions[which].data = nil
ReleaseTable(this.actions[which])
table.remove(this.actions,which)
end
end
end
do
MusicList = {}
CurrentList = {}
function AddMusic(path,duration,pitch)
local snd = CreateSound(path,false,false,false,1,1,"Fast2DReverbQuality")
SetSoundChannel(snd,7)
SetSoundVolume(snd,127)
if pitch then
SetPitch(snd,pitch)
end
table.insert(MusicList,{snd = snd, dur = duration})
table.insert(CurrentList,#MusicList)
end
function LoadMusic()
ClearMapMusic()
StopMusic(true)
AddMusic("Music\\Music1",101)
AddMusic("Music\\Music10",113)
AddMusic("Music\\Music11",111)
AddMusic("Music\\Music12",90)
AddMusic("Music\\Music13",133)
AddMusic("Music\\Music14",230)
AddMusic("Music\\Music2",131)
AddMusic("Music\\Music3",141)
AddMusic("Music\\Music4",106)
AddMusic("Music\\Music5",113)
AddMusic("Music\\Music6",117)
AddMusic("Music\\Music7",116)
AddMusic("Music\\Music8",114)
AddMusic("Music\\Music9",129)
end
function InitMusic()
function MusicPeriodic()
local pos = math.random(#CurrentList)
MusicCurrent = MusicList[CurrentList[pos]]
table.remove(CurrentList,pos)
if #CurrentList == 0 then
for i, _ in ipairs(MusicList) do
CurrentList[i] = i
end
end
StartSound(MusicCurrent.snd)
TimerStart(GetExpiredTimer(),MusicCurrent.dur,false,MusicPeriodic)
end
TimerStart(CreateTimer(),1,false,MusicPeriodic)
end
end
do
function RegionAddCells(reg,size,x,y)
x = x - 16
y = y - 16
for posX = -size+1, size, 1 do
for posY = -size+1, size, 1 do
RegionAddCell(reg,x + posX*32, y + posY*32)
end
end
end
function InitCorruption()
tempRect = Rect(-64,-64,64,64)
ENUM_Rect = Rect(-80,-80,80,80)
local CorruptRegion = CreateRegion()
local t,t2 = CreateTrigger(), CreateTrigger()
CorruptedGroup = {}
TILE_ID_ALPHA = FourCC('Itbk')
ABILITY_ID_MOVE_SPEED_MAX = FourCC('A01P')
local function enter(u,entered)
local udata = UnitData[GetUnitUserData(u)]
local pdata = Player2Data(GetOwningPlayer(u))
udata.corrupted = entered
if entered then
table.insert(CorruptedGroup,u)
if not pdata.user then
udata.corruptionRegen = BlzGetUnitMaxHP(u)*.1
BlzSetUnitRealField(u,UNIT_RF_HIT_POINTS_REGENERATION_RATE,BlzGetUnitRealField(u,UNIT_RF_HIT_POINTS_REGENERATION_RATE) + udata.corruptionRegen)
end
else
table.removeElement(CorruptedGroup,u)
if not pdata.user then
BlzSetUnitRealField(u,UNIT_RF_HIT_POINTS_REGENERATION_RATE,BlzGetUnitRealField(u,UNIT_RF_HIT_POINTS_REGENERATION_RATE) - udata.corruptionRegen)
end
end
end
TriggerAddAction(t,function()
enter(GetTriggerUnit(),true)
end)
TriggerAddAction(t2,function()
enter(GetTriggerUnit(),false)
end)
TriggerRegisterEnterRegion(t,CorruptRegion,nil)
TriggerRegisterLeaveRegion(t2,CorruptRegion,nil)
function IsLocCorrupted(x,y)
x,y = Cord2Cell(x,y)
return Cell_Data[x][y].corrupted
end
function AddCorruption(x,y,add)
local x2,y2 = Cord2Cell(x,y)
x,y = Cell2Cord(x2,y2)
local cell = Cell_Data[x2][y2]
cell.corrupted = cell.corrupted or 0
if add then
cell.corrupted = cell.corrupted + 1
if cell.corrupted == 1 then
cell.corrupted = 1
cell.preCorruptTile = GetTerrainType(x,y)
cell.preCorruptVar = GetTerrainVariance(x,y)
cell.corruptDest = CreateDestructableZ(FourCC('B005'),x,y,0,math.random()*360,.75,0)
SetDestructableAnimation(cell.corruptDest, "birth")
QueueDestructableAnimation(cell.corruptDest, "stand")
MoveRectTo(tempRect,x,y)
RegionAddRect(CorruptRegion,tempRect)
MoveRectTo(ENUM_Rect,x,y)
local g = NewGroup()
GroupEnumUnitsInRect(g,ENUM_Rect,nil)
local size = BlzGroupGetSize(g)-1
local target
for i = 0, size, 1 do
target = BlzGroupUnitAt(g,i)
if IsUnitInRegion(CorruptRegion,target) then
enter(target,true)
end
end
ReleaseGroup(g)
DestroyCellFoliage(cell)
end
else
cell.corrupted = cell.corrupted - 1
if cell.corrupted == 0 then
SetTerrainType(x, y, cell.preCorruptTile, cell.preCorruptVar, 1, 1)
cell.corrupted = nil
cell.preCorruptTile = nil
cell.preCorruptVar = nil
KillDestructable(cell.corruptDest)
AddDestToDestroyQueue(cell.corruptDest,1)
cell.corruptDest = nil
MoveRectTo(tempRect,x,y)
RegionClearRect(CorruptRegion,tempRect)
MoveRectTo(ENUM_Rect,x,y)
local g = NewGroup()
GroupEnumUnitsInRect(g,ENUM_Rect,nil)
local size = BlzGroupGetSize(g)-1
for i = 0, size, 1 do
target = BlzGroupUnitAt(g,i)
if not IsUnitInRegion(CorruptRegion,target) then
enter(target,false)
end
end
ReleaseGroup(g)
end
end
end
function CorruptionFilter(cell)
return not IsCellFilled(cell.x,cell.y)
end
function creepGrowTime(tbl)
local new
for j = 1, #tbl.v do
local vec = tbl.v[j]
if math.random() > .5 then
for i = 1, 4, 1 do
local cell = isCellValid(neighborCells[i][1] + vec.x, neighborCells[i][2] + vec.y)
local x3, y3 = Cell2Cord(cell.x,cell.y)
if cell and not tbl.checked[cell] then
tbl.checked[cell] = true
if IsInRange(tbl.x,tbl.y,x3,y3,tbl.range) then
if CorruptionFilter(cell) then
new = new or NewTable()
table.insert(new,cell)
AddCorruption(x3,y3,true)
table.insert(tbl.data.corruptionCells,cell)
else
cell.corruptionQueue = cell.corruptionQueue or NewTable()
table.insert(cell.corruptionQueue,tbl.data)
table.insert(tbl.data.touched,cell)
end
elseif CorruptionFilter(cell) then
table.insert(tbl.data.corruptionRangeCells,cell)
end
end
end
else
new = new or NewTable()
table.insert(new,vec)
end
tbl.v[j] = nil
end
ReleaseTableFast(tbl.v)
if not new then
ReleaseKeyTable(tbl.checked)
tbl.data = nil
ReleaseKeyTable(tbl)
RemoveActionInTimerQueue()
else
tbl.v = new
end
end
function SpreadCorruption(x,y,range)
local tbl = NewKeyTable()
tbl.x=x
tbl.y=y
tbl.range=range
tbl.checked = NewKeyTable()
tbl.data = NewKeyTable()
tbl.data.x = x
tbl.data.y = y
tbl.data.range = range
tbl.data.touched = NewTable()
tbl.data.corruptionCells = NewTable()
tbl.data.corruptionRangeCells = NewTable()
tbl.v = NewTable()
tbl.v[1] = Cord2Cell(x,y,true)
AddActionInTimerQueue(.5,creepGrowTime,tbl)
return tbl.data
end
function creepKillTime(data)
local cell = data.corruptionCells[#data.corruptionCells]
data.corruptionCells[#data.corruptionCells] = nil
if cell then
local x3, y3 = Cell2Cord(cell.x,cell.y)
AddCorruption(x3,y3,false)
else
ReleaseTableFast(data.corruptionCells)
ReleaseKeyTable(data)
RemoveActionInTimerQueue()
end
end
function KillCorruption(data)
data.dying = true
for i = 1, #data.touched do
local cell = data.touched[i]
table.removeElementFast(cell.corruptionQueue,data)
if #cell.corruptionQueue == 0 then
ReleaseTable(cell.corruptionQueue)
end
cell.corruptionQueue = nil
end
AddActionInTimerQueue(.1,creepKillTime,data)
end
ActionAdd(CellUpdateActions,true,function(cell)
if cell.corruptionQueue and CorruptionFilter(cell) then
for i = 1, #cell.corruptionQueue do
local source = cell.corruptionQueue[i]
if not source.dying then
local tbl = NewKeyTable()
tbl.x=source.x
tbl.y=source.y
tbl.range=source.range
tbl.checked = NewKeyTable()
tbl.data = source
tbl.v = NewTable()
tbl.v[1] = cell
AddActionInTimerQueue(.5,creepGrowTime,tbl)
end
cell.corruptionQueue[i] = nil
end
ReleaseTableFast(cell.corruptionQueue)
cell.corruptionQueue = nil
end
end)
end
end
------------------------
----| String Width |----
------------------------
--[[
offers functions to measure the width of a string (i.e. the space it takes on screen, not the number of chars). Wc3 font is not monospace, so the system below has protocolled every char width and simply sums up all chars in a string.
output measures are:
1. Multiboard-width (i.e. 1-based screen share used in Multiboards column functions)
2. Line-width for screen prints
every unknown char will be treated as having default width (see constants below)
--]]
do
----------------------------
----| String Width API |----
----------------------------
local multiboardCharTable = {} ---@type table -- saves the width in screen percent (on 1920 pixel width resolutions) that each char takes up, when displayed in a multiboard.
local DEFAULT_MULTIBOARD_CHAR_WIDTH = 1. / 128. ---@type real -- used for unknown chars (where we didn't define a width in the char table)
local MULTIBOARD_TO_PRINT_FACTOR = 1. / 36. ---@type real -- 36 is actually the lower border (longest width of a non-breaking string only consisting of the letter "i")
---Returns the width of a char in a multiboard, when inputting a char (string of length 1) and 0 otherwise.
---also returns 0 for non-recorded chars (like ` and ´ and ß and § and €)
---@param char string | integer integer bytecode representations of chars are also allowed, i.e. the results of string.byte().
---@param textlanguage string | nil 'ger' or 'eng' (default is 'eng'), depending on the text language in the Warcraft 3 installation settings.
---@return real
function string.charMultiboardWidth(char, textlanguage)
return multiboardCharTable[textlanguage or 'eng'][char] or DEFAULT_MULTIBOARD_CHAR_WIDTH
end
---returns the width of a string in a multiboard (i.e. output is in screen percent)
---unknown chars will be measured with default width (see constants above)
---@param multichar string
---@param textlanguage string | nil 'ger' or 'eng' (default is 'eng'), depending on the text language in the Warcraft 3 installation settings.
---@return real
function string.multiboardWidth(multichar, textlanguage)
local chartable = table.pack(multichar:byte(1,-1)) --packs all bytecode char representations into a table
local charWidth = 0.
for i = 1, chartable.n do
charWidth = charWidth + string.charMultiboardWidth(chartable[i], textlanguage)
end
return charWidth
end
---The function should match the following criteria: If the value returned by this function is smaller than 1.0, than the string fits into a single line on screen.
---The opposite is not necessarily true (but should be true in the majority of cases): If the function returns bigger than 1.0, the string doesn't necessarily break.
---@param char string | integer integer bytecode representations of chars are also allowed, i.e. the results of string.byte().
---@param textlanguage string | nil 'ger' or 'eng' (default is 'eng'), depending on the text language in the Warcraft 3 installation settings.
---@return real
function string.charPrintWidth(char, textlanguage)
return string.charMultiboardWidth(char, textlanguage) * MULTIBOARD_TO_PRINT_FACTOR
end
---The function should match the following criteria: If the value returned by this function is smaller than 1.0, than the string fits into a single line on screen.
---The opposite is not necessarily true (but should be true in the majority of cases): If the function returns bigger than 1.0, the string doesn't necessarily break.
---@param multichar string
---@param textlanguage string | nil 'ger' or 'eng' (default is 'eng'), depending on the text language in the Warcraft 3 installation settings.
---@return real
function string.printWidth(multichar, textlanguage)
return string.multiboardWidth(multichar, textlanguage) * MULTIBOARD_TO_PRINT_FACTOR
end
----------------------------------
----| String Width Internals |----
----------------------------------
---@param char string
---@param lengthInScreenWidth real
---@return nothing
local function setMultiboardCharWidth(charset, char, lengthInScreenWidth)
multiboardCharTable[charset] = multiboardCharTable[charset] or {}
multiboardCharTable[charset][char] = lengthInScreenWidth
end
---numberPlacements says how often the char can be placed in a multiboard column, before reaching into the right bound.
---@param char string
---@param numberPlacements integer
---@return nothing
local function setMultiboardCharWidthBase80(charset, char, numberPlacements)
setMultiboardCharWidth(charset, char, 0.8 / numberPlacements) --1-based measure. 80./numberPlacements would result in Screen Percent.
setMultiboardCharWidth(charset, string.byte(char), 0.8 / numberPlacements)
end
-- Set Char Width for all usual chars in screen width (1920 pixels). Measured on a 80percent screen width multiboard column by counting the number of chars that fit into it. I.e. actual width (in screen percent) is calculated by dividing this number by 80.
setMultiboardCharWidthBase80('ger', "a", 144)
setMultiboardCharWidthBase80('ger', "b", 144)
setMultiboardCharWidthBase80('ger', "c", 144)
setMultiboardCharWidthBase80('ger', "d", 131)
setMultiboardCharWidthBase80('ger', "e", 144)
setMultiboardCharWidthBase80('ger', "f", 240)
setMultiboardCharWidthBase80('ger', "g", 120)
setMultiboardCharWidthBase80('ger', "h", 144)
setMultiboardCharWidthBase80('ger', "i", 360)
setMultiboardCharWidthBase80('ger', "j", 288)
setMultiboardCharWidthBase80('ger', "k", 144)
setMultiboardCharWidthBase80('ger', "l", 360)
setMultiboardCharWidthBase80('ger', "m", 90)
setMultiboardCharWidthBase80('ger', "n", 144)
setMultiboardCharWidthBase80('ger', "o", 131)
setMultiboardCharWidthBase80('ger', "p", 131)
setMultiboardCharWidthBase80('ger', "q", 131)
setMultiboardCharWidthBase80('ger', "r", 206)
setMultiboardCharWidthBase80('ger', "s", 180)
setMultiboardCharWidthBase80('ger', "t", 206)
setMultiboardCharWidthBase80('ger', "u", 144)
setMultiboardCharWidthBase80('ger', "v", 131)
setMultiboardCharWidthBase80('ger', "w", 96)
setMultiboardCharWidthBase80('ger', "x", 144)
setMultiboardCharWidthBase80('ger', "y", 131)
setMultiboardCharWidthBase80('ger', "z", 144)
setMultiboardCharWidthBase80('ger', "A", 103)
setMultiboardCharWidthBase80('ger', "B", 131)
setMultiboardCharWidthBase80('ger', "C", 120)
setMultiboardCharWidthBase80('ger', "D", 111)
setMultiboardCharWidthBase80('ger', "E", 144)
setMultiboardCharWidthBase80('ger', "F", 180)
setMultiboardCharWidthBase80('ger', "G", 103)
setMultiboardCharWidthBase80('ger', "H", 103)
setMultiboardCharWidthBase80('ger', "I", 288)
setMultiboardCharWidthBase80('ger', "J", 240)
setMultiboardCharWidthBase80('ger', "K", 120)
setMultiboardCharWidthBase80('ger', "L", 144)
setMultiboardCharWidthBase80('ger', "M", 80)
setMultiboardCharWidthBase80('ger', "N", 103)
setMultiboardCharWidthBase80('ger', "O", 96)
setMultiboardCharWidthBase80('ger', "P", 144)
setMultiboardCharWidthBase80('ger', "Q", 90)
setMultiboardCharWidthBase80('ger', "R", 120)
setMultiboardCharWidthBase80('ger', "S", 144)
setMultiboardCharWidthBase80('ger', "T", 144)
setMultiboardCharWidthBase80('ger', "U", 111)
setMultiboardCharWidthBase80('ger', "V", 120)
setMultiboardCharWidthBase80('ger', "W", 76)
setMultiboardCharWidthBase80('ger', "X", 111)
setMultiboardCharWidthBase80('ger', "Y", 120)
setMultiboardCharWidthBase80('ger', "Z", 120)
setMultiboardCharWidthBase80('ger', "1", 288)
setMultiboardCharWidthBase80('ger', "2", 131)
setMultiboardCharWidthBase80('ger', "3", 144)
setMultiboardCharWidthBase80('ger', "4", 120)
setMultiboardCharWidthBase80('ger', "5", 144)
setMultiboardCharWidthBase80('ger', "6", 131)
setMultiboardCharWidthBase80('ger', "7", 144)
setMultiboardCharWidthBase80('ger', "8", 131)
setMultiboardCharWidthBase80('ger', "9", 131)
setMultiboardCharWidthBase80('ger', "0", 131)
setMultiboardCharWidthBase80('ger', ":", 480)
setMultiboardCharWidthBase80('ger', ";", 360)
setMultiboardCharWidthBase80('ger', ".", 480)
setMultiboardCharWidthBase80('ger', "#", 120)
setMultiboardCharWidthBase80('ger', ",", 360)
setMultiboardCharWidthBase80('ger', " ", 288) --space
setMultiboardCharWidthBase80('ger', "'", 480)
setMultiboardCharWidthBase80('ger', "!", 360)
setMultiboardCharWidthBase80('ger', "$", 160)
setMultiboardCharWidthBase80('ger', "&", 96)
setMultiboardCharWidthBase80('ger', "/", 180)
setMultiboardCharWidthBase80('ger', "(", 288)
setMultiboardCharWidthBase80('ger', ")", 288)
setMultiboardCharWidthBase80('ger', "=", 160)
setMultiboardCharWidthBase80('ger', "?", 180)
setMultiboardCharWidthBase80('ger', "^", 144)
setMultiboardCharWidthBase80('ger', "<", 160)
setMultiboardCharWidthBase80('ger', ">", 160)
setMultiboardCharWidthBase80('ger', "-", 144)
setMultiboardCharWidthBase80('ger', "+", 160)
setMultiboardCharWidthBase80('ger', "*", 206)
setMultiboardCharWidthBase80('ger', "|", 480) --2 vertical bars in a row escape to one. So you could print 960 ones in a line, 480 would display. Maybe need to adapt to this before calculating string width.
setMultiboardCharWidthBase80('ger', "~", 144)
setMultiboardCharWidthBase80('ger', "{", 240)
setMultiboardCharWidthBase80('ger', "}", 240)
setMultiboardCharWidthBase80('ger', "[", 240)
setMultiboardCharWidthBase80('ger', "]", 288)
setMultiboardCharWidthBase80('ger', "_", 144)
setMultiboardCharWidthBase80('ger', "\x25", 111) --percent
setMultiboardCharWidthBase80('ger', "\x5C", 206) --backslash
setMultiboardCharWidthBase80('ger', "\x22", 240) --double quotation mark
setMultiboardCharWidthBase80('ger', "\x40", 103) --at sign
setMultiboardCharWidthBase80('ger', "\x60", 240) --Gravis (Accent)
setMultiboardCharWidthBase80('eng', "a", 144)
setMultiboardCharWidthBase80('eng', "b", 120)
setMultiboardCharWidthBase80('eng', "c", 131)
setMultiboardCharWidthBase80('eng', "d", 120)
setMultiboardCharWidthBase80('eng', "e", 131)
setMultiboardCharWidthBase80('eng', "f", 240)
setMultiboardCharWidthBase80('eng', "g", 120)
setMultiboardCharWidthBase80('eng', "h", 131)
setMultiboardCharWidthBase80('eng', "i", 360)
setMultiboardCharWidthBase80('eng', "j", 288)
setMultiboardCharWidthBase80('eng', "k", 144)
setMultiboardCharWidthBase80('eng', "l", 360)
setMultiboardCharWidthBase80('eng', "m", 80)
setMultiboardCharWidthBase80('eng', "n", 131)
setMultiboardCharWidthBase80('eng', "o", 120)
setMultiboardCharWidthBase80('eng', "p", 120)
setMultiboardCharWidthBase80('eng', "q", 120)
setMultiboardCharWidthBase80('eng', "r", 206)
setMultiboardCharWidthBase80('eng', "s", 160)
setMultiboardCharWidthBase80('eng', "t", 206)
setMultiboardCharWidthBase80('eng', "u", 131)
setMultiboardCharWidthBase80('eng', "v", 144)
setMultiboardCharWidthBase80('eng', "w", 90)
setMultiboardCharWidthBase80('eng', "x", 131)
setMultiboardCharWidthBase80('eng', "y", 144)
setMultiboardCharWidthBase80('eng', "z", 144)
setMultiboardCharWidthBase80('eng', "A", 103)
setMultiboardCharWidthBase80('eng', "B", 120)
setMultiboardCharWidthBase80('eng', "C", 103)
setMultiboardCharWidthBase80('eng', "D", 103)
setMultiboardCharWidthBase80('eng', "E", 131)
setMultiboardCharWidthBase80('eng', "F", 160)
setMultiboardCharWidthBase80('eng', "G", 103)
setMultiboardCharWidthBase80('eng', "H", 96)
setMultiboardCharWidthBase80('eng', "I", 288)
setMultiboardCharWidthBase80('eng', "J", 240)
setMultiboardCharWidthBase80('eng', "K", 120)
setMultiboardCharWidthBase80('eng', "L", 131)
setMultiboardCharWidthBase80('eng', "M", 76)
setMultiboardCharWidthBase80('eng', "N", 96)
setMultiboardCharWidthBase80('eng', "O", 85)
setMultiboardCharWidthBase80('eng', "P", 131)
setMultiboardCharWidthBase80('eng', "Q", 85)
setMultiboardCharWidthBase80('eng', "R", 120)
setMultiboardCharWidthBase80('eng', "S", 131)
setMultiboardCharWidthBase80('eng', "T", 144)
setMultiboardCharWidthBase80('eng', "U", 103)
setMultiboardCharWidthBase80('eng', "V", 120)
setMultiboardCharWidthBase80('eng', "W", 76)
setMultiboardCharWidthBase80('eng', "X", 111)
setMultiboardCharWidthBase80('eng', "Y", 120)
setMultiboardCharWidthBase80('eng', "Z", 111)
setMultiboardCharWidthBase80('eng', "1", 206)
setMultiboardCharWidthBase80('eng', "2", 131)
setMultiboardCharWidthBase80('eng', "3", 131)
setMultiboardCharWidthBase80('eng', "4", 111)
setMultiboardCharWidthBase80('eng', "5", 131)
setMultiboardCharWidthBase80('eng', "6", 120)
setMultiboardCharWidthBase80('eng', "7", 131)
setMultiboardCharWidthBase80('eng', "8", 111)
setMultiboardCharWidthBase80('eng', "9", 120)
setMultiboardCharWidthBase80('eng', "0", 111)
setMultiboardCharWidthBase80('eng', ":", 360)
setMultiboardCharWidthBase80('eng', ";", 360)
setMultiboardCharWidthBase80('eng', ".", 360)
setMultiboardCharWidthBase80('eng', "#", 103)
setMultiboardCharWidthBase80('eng', ",", 360)
setMultiboardCharWidthBase80('eng', " ", 288) --space
setMultiboardCharWidthBase80('eng', "'", 480)
setMultiboardCharWidthBase80('eng', "!", 360)
setMultiboardCharWidthBase80('eng', "$", 131)
setMultiboardCharWidthBase80('eng', "&", 120)
setMultiboardCharWidthBase80('eng', "/", 180)
setMultiboardCharWidthBase80('eng', "(", 240)
setMultiboardCharWidthBase80('eng', ")", 240)
setMultiboardCharWidthBase80('eng', "=", 111)
setMultiboardCharWidthBase80('eng', "?", 180)
setMultiboardCharWidthBase80('eng', "^", 144)
setMultiboardCharWidthBase80('eng', "<", 131)
setMultiboardCharWidthBase80('eng', ">", 131)
setMultiboardCharWidthBase80('eng', "-", 180)
setMultiboardCharWidthBase80('eng', "+", 111)
setMultiboardCharWidthBase80('eng', "*", 180)
setMultiboardCharWidthBase80('eng', "|", 480) --2 vertical bars in a row escape to one. So you could print 960 ones in a line, 480 would display. Maybe need to adapt to this before calculating string width.
setMultiboardCharWidthBase80('eng', "~", 144)
setMultiboardCharWidthBase80('eng', "{", 240)
setMultiboardCharWidthBase80('eng', "}", 240)
setMultiboardCharWidthBase80('eng', "[", 240)
setMultiboardCharWidthBase80('eng', "]", 240)
setMultiboardCharWidthBase80('eng', "_", 120)
setMultiboardCharWidthBase80('eng', "\x25", 103) --percent
setMultiboardCharWidthBase80('eng', "\x5C", 180) --backslash
setMultiboardCharWidthBase80('eng', "\x22", 206) --double quotation mark
setMultiboardCharWidthBase80('eng', "\x40", 96) --at sign
setMultiboardCharWidthBase80('eng', "\x60", 206) --Gravis (Accent)
end
--[[
--------------------------
----| Ingame Console |----
--------------------------
/**********************************************
* Allows you to use the following ingame commands:
* "-exec <code>" to execute any code ingame.
* "-console" to start an ingame console interpreting any further chat input as code and showing both return values of function calls and error messages. Furthermore, the print function will print
* directly to the console after it got started. You can still look up all print messages in the F12-log.
***********************
* -------------------
* |Using the console|
* -------------------
* Any (well, most) chat input by any player after starting the console is interpreted as code and directly executed. You can enter terms (like 4+5 or just any variable name), function calls (like print("bla"))
* and set-statements (like y = 5). If the code has any return values, all of them are printed to the console. Erraneous code will print an error message.
* Chat input starting with a hyphen is being ignored by the console, i.e. neither executed as code nor printed to the console. This allows you to still use other chat commands like "-exec" without prompting errors.
***********************
* ------------------
* |Multiline-Inputs|
* ------------------
* You can prevent a chat input from being immediately executed by preceeding it with the greater sign '>'. All lines entered this way are halted, until any line not starting with '>' is being entered.
* The first input without '>' will execute all halted lines (and itself) in one chunk.
* Example of a chat input (the console will add an additional '>' to every line):
* >function a(x)
* >return x
* end
***********************
* Note that multiline inputs don't accept pure term evaluations, e.g. the following input is not supported and will prompt an error, while the same lines would have worked as two single-line inputs:
* >x = 5
* x
***********************
* -------------------
* |Reserved Keywords|
* -------------------
* The following keywords have a reserved functionality, i.e. are direct commands for the console and will not be interpreted as code:
* - 'exit' - will shut down the console
* - 'printtochat' - will let the print function return to normal behaviour (i.e. print to the chat instead of the console).
* - 'printtoconsole'- will let the print function print to the console (which is default behaviour).
* - 'show' - will show the console, after it was accidently hidden (you can accidently hide it by showing another multiboard, while the console functionality is still up and running).
* - 'help' - will show a list of all reserved keywords along very short explanations.
* - 'clear' - will clear all text from the console, except the word 'clear'
* - 'share' - will share the players console with every other player, allowing others to read and write into it. Will force-close other players consoles, if they have one active.
* - 'autosize on' - will enable automatic console resize depending on the longest string in the display. This is turned on by default.
* - 'autosize off' - will disable automatic console resize and instead linebreak long strings into multiple lines.
* - 'textlang eng' - lets the console use english Wc3 text language font size to compute linebreaks (look in your Blizzard launcher settings to find out)
* - 'textlang ger' - lets the console use german Wc3 text language font size to compute linebreaks (look in your Blizzard launcher settings to find out)
*************************************************/
--]]
--1. Ingame Console
---@class IngameConsole
IngameConsole = {
--Settings
numRows = 20 ---@type integer Number of Rows of the console (multiboard), excluding the title row. So putting 20 here will show 21 rows, first being the title row.
, numCols = 2 ---@type integer Number of Columns of the console (multiboard)
, autosize = true ---@type boolean Defines, whether the width of the main Column automatically adjusts with the longest string in the display.
, currentWidth = 0.5 ---@type real Current and starting Screen Share of the console main column.
, mainColMinWidth = 0.3 ---@type real Minimum Screen share of the variable main console column.
, mainColMaxWidth = 0.8 ---@type real Maximum Scren share of the variable main console column.
, tsColumnWidth = 0.06 ---@type real Screen Share of the Timestamp Column
, linebreakBuffer = 0.008 ---@type real Screen Share that is added to the multiboard text column to compensate for the small inaccuracy of the String Width function.
, maxLinebreaks = 3 ---@type integer Defines the maximum amount of linebreaks, before the remaining output string will be cut and not further displayed.
, printToConsole = true ---@type boolean defines, if the print function should print to the console or to the chat
, sharedConsole = false ---@type boolean defines, if the console is displayed to each player at the same time (accepting all players input) or if all players much start their own console.
, textLanguage = 'eng' ---@type string text language of your Wc3 installation, which influences font size (look in the settings of your Blizzard launcher). Currently only supports 'eng' and 'ger'.
, colors = {
timestamp = "bbbbbb" ---@type string Timestamp Color
, singleLineInput = "ffffaa" ---@type string Color to be applied to single line console inputs
, multiLineInput = "ffcc55" ---@type string Color to be applied to multi line console inputs
, returnValue = "00ffff" ---@type string Color applied to return values
, error = "ff5555" ---@type string Color to be applied to errors resulting of function calls
, keywordInput = "ff00ff" ---@type string Color to be applied to reserved keyword inputs (console reserved keywords)
, info = "bbbbbb" ---@type string Color to be applied to info messages from the console itself (for instance after creation or after printrestore)
} ---@type table
--Privates
, player = nil ---@type player player for whom the console is being created
, currentLine = 0 ---@type integer Current Output Line of the console.
, inputload = '' ---@type string Input Holder for multi-line-inputs
, output = {} ---@type string[] Array of all output strings
, outputTimestamps = {} ---@type string[] Array of all output string timestamps
, outputWidths = {} ---@type real[] remembers all string widths to allow for multiboard resize
, trigger = nil ---@type trigger trigger processing all inputs during console lifetime
, multiboard = nil ---@type multiboard
, timer = nil ---@type timer gets started upon console creation to measure timestamps
--Statics
, keywords = {} ---@type table<string,function> saves functions to be executed for all reserved keywords
, playerConsoles = {} ---@type table<player,IngameConsole> Consoles currently being active. up to one per player.
, originalPrint = print ---@type function original print function to restore, after the console gets closed.
}
IngameConsole.__index = IngameConsole
------------------------
--| Console Creation |--
------------------------
---@param consolePlayer player player for whom the console is being created
---@return IngameConsole
function IngameConsole.create(consolePlayer)
local new = {} ---@type IngameConsole
setmetatable(new, IngameConsole)
---setup Object data
new.player = consolePlayer
new.output = {}
new.outputTimestamps = {}
new.outputWidths = {}
--Timer
new.timer = CreateTimer()
TimerStart(new.timer, 3600., true, nil) --just to get TimeElapsed for printing Timestamps.
--Trigger to be created after short delay, because otherwise it would fire on "-console" input immediately and lead to stack overflow.
new:setupTrigger()
--Multiboard
new:setupMultiboard()
--Share, if settings say so
if IngameConsole.sharedConsole then
new:makeShared() --we don't have to exit other players consoles, because we look for the setting directly in the class and there just logically can't be other active consoles.
end
--Welcome Message
new:out('info', 0, false, "Console started. Any further chat input will be executed as code, except when beginning with \x22-\x22.")
return new
end
function IngameConsole:setupMultiboard()
self.multiboard = CreateMultiboard()
MultiboardSetRowCount(self.multiboard, self.numRows + 1) --title row adds 1
MultiboardSetColumnCount(self.multiboard, self.numCols)
MultiboardSetTitleText(self.multiboard, "Console")
local mbitem
for col = 1, self.numCols do
for row = 1, self.numRows + 1 do --Title row adds 1
mbitem = MultiboardGetItem(self.multiboard, row -1, col -1)
MultiboardSetItemStyle(mbitem, true, false)
MultiboardSetItemValueColor(mbitem, 255, 255, 255, 255) -- Colors get applied via text color code
MultiboardSetItemWidth(mbitem, (col == 1 and self.tsColumnWidth) or self.currentWidth )
MultiboardReleaseItem(mbitem)
end
end
mbitem = MultiboardGetItem(self.multiboard, 0, 0)
MultiboardSetItemValue(mbitem, "|cffffcc00Timestamp|r")
MultiboardReleaseItem(mbitem)
mbitem = MultiboardGetItem(self.multiboard, 0, 1)
MultiboardSetItemValue(mbitem, "|cffffcc00Line|r")
MultiboardReleaseItem(mbitem)
self:showToOwners()
end
function IngameConsole:setupTrigger()
self.trigger = CreateTrigger()
TriggerRegisterPlayerChatEvent(self.trigger, self.player, "", false) --triggers on any input of self.player
TriggerAddCondition(self.trigger, Condition(function() return string.sub(GetEventPlayerChatString(),1,1) ~= '-' end)) --console will not react to entered stuff starting with '-'. This still allows to use other chat orders like "-exec".
TriggerAddAction(self.trigger, function() self:processInput(GetEventPlayerChatString()) end)
end
function IngameConsole:makeShared()
local player
for i = 0, GetBJMaxPlayers() do
player = Player(i)
if (GetPlayerSlotState(player) == PLAYER_SLOT_STATE_PLAYING) and (IngameConsole.playerConsoles[player] ~= self) then --second condition ensures that the player chat event is not added twice for the same player.
IngameConsole.playerConsoles[player] = self
TriggerRegisterPlayerChatEvent(self.trigger, player, "", false) --triggers on any input
end
end
self.sharedConsole = true
end
---------------------
--| In |--
---------------------
function IngameConsole:processInput(inputString)
if IngameConsole.keywords[inputString] then --if the input string is a reserved keyword
self:out('keywordInput', 1, false, inputString)
IngameConsole.keywords[inputString](self) --then call the method with the same name. IngameConsole.keywords["exit"](self) is just self.keywords:exit().
return
end
if string.sub(inputString, 1, 1) == '>' then --multiLineInput
inputString = string.sub(inputString, 2, -1)
self:out('multiLineInput',2, false, inputString)
self.inputload = self.inputload .. inputString .. '\r' --carriage return
else --singleLineInput OR last line of multiLineInput
if self.inputload == '' then --singleLineInput
self:out('singleLineInput', 1, false, inputString)
self.inputload = self.inputload .. IngameConsole.preProcessInput(inputString, true) --slightly rewrites input to make it interpretable by the ingame console (and adds return statements, if sensible)
else
self:out('multiLineInput', 1, false, inputString) --end of multiline input gets only one '>'
self.inputload = self.inputload .. IngameConsole.preProcessInput(inputString, false) --slightly rewrites input to make it interpretable by the ingame console (and adds return statements, if sensible)
end
local inputload = self.inputload
self.inputload = '' --content of self.inputload written to local var. this allows us emptying it here and prevent it from keeping text even, when the pcall line below breaks (rare case, can for example be provoked with metatable.__tostring = {}).
local loadfunc = load(inputload)
local results = table.pack(pcall(loadfunc))
--manually catch case, where the input did not define a proper Lua statement (i.e. loadfunc is nil)
if loadfunc == nil then
results[1], results[2] = false, "Input is not a valid Lua-statement"
end
--output error message (unsuccessful case) or return values (successful case)
if not results[1] then --results[1] is the error status that pcall always returns. False stands for: error occured.
self:out('error', 0, true, results[2]) -- second result of pcall is the error message in case an error occured
elseif results.n > 1 then --Check, if there was at least one valid output argument. We check results.n instead of results[2], because we also get nil as a proper return value this way.
self:out('returnValue', 0, true, select(2,table.unpack(results, 1, results.n)))
end
end
end
function IngameConsole.preProcessInput(inputString, singleLineInput)
--Preceed the input with 'return ' to show term outputs in the console. A term t can be recognized by that "function() return t end" is a valid function, i.e. load('return ' .. t) is not nil.
if singleLineInput and load("return " .. inputString) then --load(x) returns either a function or nil
return "return " .. inputString
end
return inputString
end
----------------------
--| Out |--
----------------------
---@param colorTheme? string Decides about the color to be applied. Currently accepted: 'timestamp', 'singleLineInput', 'multiLineInput', 'result', nil. (nil leadsto no colorTheme, i.e. white color)
---@param numIndentations integer Number of greater signs '>' that shall preceed the output
---@param hideTimestamp boolean Set to false to hide the timestamp column and instead show a "->" symbol.
---@vararg any the things to be printed in the console.
---@return boolean hasPrintedEverything returns true, if everything could be printed. Returns false otherwise (can happen for very long strings).
function IngameConsole:out(colorTheme, numIndentations, hideTimestamp, ...)
local outputs = table.pack(...) --we need the entry "n" to be able to for-loop and deal with nil values. Just writing {...} would force us to use ipairs (stops on first nil) or pairs(doesn't retain order and ignores nil).
--Replace all outputs by their tostring instances
for i = 1, outputs.n do
outputs[i] = tostring(outputs[i])
end
local printOutput = table.concat(outputs, ' ')
--add preceeding greater signs
for i = 1, numIndentations do
printOutput = '>' .. printOutput
end
--Print a space instead of the empty string. This allows the console to identify, if the string has already been fully printed (see while-loop below).
if printOutput == '' then
printOutput = ' '
end
--Compute Linebreaks.
local linebreakWidth = ((self.autosize and self.mainColMaxWidth) or self.currentWidth )
local numLinebreaks = 0
local partialOutput = nil
local maxPrintableCharPosition
local printWidth
while string.len(printOutput) > 0 and numLinebreaks <= self.maxLinebreaks do --break, if the input string has reached length 0 OR when the maximum number of linebreaks would be surpassed.
--compute max printable substring (in one multiboard line)
maxPrintableCharPosition, printWidth = IngameConsole.getLinebreakData(printOutput, linebreakWidth - self.linebreakBuffer, self.textLanguage)
--adds timestamp to the first line of any output
if numLinebreaks == 0 then
partialOutput = printOutput:sub(1, numIndentations) .. ((IngameConsole.colors[colorTheme] and "|cff" .. IngameConsole.colors[colorTheme] .. printOutput:sub(numIndentations + 1, maxPrintableCharPosition) .. "|r") or printOutput:sub(numIndentations + 1, maxPrintableCharPosition)) --Colorize the output string, if a color theme was specified. IngameConsole.colors[colorTheme] can be nil.
table.insert(self.outputTimestamps, "|cff" .. IngameConsole.colors['timestamp'] .. ((hideTimestamp and ' ->') or IngameConsole.formatTimerElapsed(TimerGetElapsed(self.timer))) .. "|r")
else
partialOutput = (IngameConsole.colors[colorTheme] and "|cff" .. IngameConsole.colors[colorTheme] .. printOutput:sub(1, maxPrintableCharPosition) .. "|r") or printOutput:sub(1, maxPrintableCharPosition) --Colorize the output string, if a color theme was specified. IngameConsole.colors[colorTheme] can be nil.
table.insert(self.outputTimestamps, ' ..') --need a dummy entry in the timestamp list to make it line-progress with the normal output.
end
numLinebreaks = numLinebreaks + 1
--writes output string and width to the console tables.
table.insert(self.output, partialOutput)
table.insert(self.outputWidths, printWidth + self.linebreakBuffer) --remember the Width of this printed string to adjust the multiboard size in case. 0.5 percent is added to avoid the case, where the multiboard width is too small by a tiny bit, thus not showing some string without spaces.
--compute remaining string to print
printOutput = string.sub(printOutput, maxPrintableCharPosition + 1, -1) --remaining string until the end. Returns empty string, if there is nothing left
end
self.currentLine = #self.output
self:updateMultiboard()
if string.len(printOutput) > 0 then
self:out('info', 0, false, "The previous value could not be entirely printed, because the maximum number of linebreaks was exceeded.") --recursive call of this function, should be fine.
end
return string.len(printOutput) == 0 --printOutput is the empty string, if and only if everything has been printed
end
function IngameConsole:updateMultiboard()
local startIndex = math.max(self.currentLine - self.numRows, 0) --to be added to loop counter to get to the index of output table to print
local outputIndex = 0
local maxWidth = 0.
local mbitem
for i = 1, self.numRows do --doesn't include title row (index 0)
outputIndex = i + startIndex
mbitem = MultiboardGetItem(self.multiboard, i, 0)
MultiboardSetItemValue(mbitem, self.outputTimestamps[outputIndex] or '')
MultiboardReleaseItem(mbitem)
mbitem = MultiboardGetItem(self.multiboard, i, 1)
MultiboardSetItemValue(mbitem, self.output[outputIndex] or '')
MultiboardReleaseItem(mbitem)
maxWidth = math.max(maxWidth, self.outputWidths[outputIndex] or 0.) --looping through non-defined widths, so need to coalesce with 0
end
--Adjust Multiboard Width, if necessary.
maxWidth = math.min(math.max(maxWidth, self.mainColMinWidth), self.mainColMaxWidth)
if self.autosize and self.currentWidth ~= maxWidth then
self.currentWidth = maxWidth
for i = 1, self.numRows +1 do
mbitem = MultiboardGetItem(self.multiboard, i-1, 1)
MultiboardSetItemWidth(mbitem, maxWidth)
MultiboardReleaseItem(mbitem)
end
self:showToOwners() --reshow multiboard to update item widths on the frontend
end
end
function IngameConsole:showToOwners()
if self.sharedConsole or GetLocalPlayer() == self.player then
MultiboardDisplay(self.multiboard, true)
MultiboardMinimize(self.multiboard, false)
end
end
function IngameConsole.numberToTwoDigitString(inputNumber)
local result = tostring(math.floor(inputNumber))
return (string.len(result) == 1 and '0'.. result) or result
end
function IngameConsole.formatTimerElapsed(elapsedInSeconds)
return IngameConsole.numberToTwoDigitString(elapsedInSeconds // 60) .. ': ' .. IngameConsole.numberToTwoDigitString(math.fmod(elapsedInSeconds, 60.)) .. '. ' .. IngameConsole.numberToTwoDigitString(math.fmod(elapsedInSeconds, 1) * 100)
end
---Computes the max printable substring for a given string and a given linebreakWidth (regarding a single line of console).
---Returns both the substrings last char position and its total width in the multiboard.
---@param stringToPrint string the string supposed to be printed in the multiboard console.
---@param linebreakWidth real the maximum allowed width in one line of the console, before a string must linebreak
---@param textLanguage string 'ger' or 'eng'
---@return integer maxPrintableCharPosition, real printWidth
function IngameConsole.getLinebreakData(stringToPrint, linebreakWidth, textLanguage)
local loopWidth = 0.
local bytecodes = table.pack(string.byte(stringToPrint, 1, -1))
for i = 1, bytecodes.n do
loopWidth = loopWidth + string.charMultiboardWidth(bytecodes[i], textLanguage)
if loopWidth > linebreakWidth then
return i-1, loopWidth - string.charMultiboardWidth(bytecodes[i], textLanguage)
end
end
return bytecodes.n, loopWidth
end
-------------------------
--| Reserved Keywords |--
-------------------------
function IngameConsole.keywords:exit()
DestroyMultiboard(self.multiboard)
DestroyTrigger(self.trigger)
DestroyTimer(self.timer)
IngameConsole.playerConsoles[self.player] = nil
if table.isEmpty(IngameConsole.playerConsoles) then --set print function back to original, when no one has an active console left.
print = IngameConsole.originalPrint
end
end
function IngameConsole.keywords:printtochat()
self.printToConsole = false
self:out('info', 0, false, "The print function will print to the normal chat.")
end
function IngameConsole.keywords:printtoconsole()
self.printToConsole = true
self:out('info', 0, false, "The print function will print to the console.")
end
function IngameConsole.keywords:show()
self:showToOwners() --might be necessary to do, if another multiboard has shown up and thereby hidden the console.
self:out('info', 0, false, "Console is showing.")
end
function IngameConsole.keywords:help()
self:out('info', 0, false, "The Console currently reserves the following keywords:")
self:out('info', 0, false, "'exit' closes the console.")
self:out('info', 0, false, "'printtochat' lets Wc3 print text to normal chat again.")
self:out('info', 0, false, "'show' shows the console. Sensible to use, when displaced by another multiboard.")
self:out('info', 0, false, "'help' shows the text you are currently reading.")
self:out('info', 0, false, "'clear' clears all text from the console.")
self:out('info', 0, false, "'share' allows other players to read and write into your console, but also force-closes their own consoles.")
self:out('info', 0, false, "'autosize on' enables automatic console resize depending on the longest line in the display.")
self:out('info', 0, false, "'autosize off' retains the current console size.")
self:out('info', 0, false, "'textlang eng' will use english text installation font size to compute linebreaks (default).")
self:out('info', 0, false, "'textlang ger' will use german text installation font size to compute linebreaks.")
self:out('info', 0, false, "Preceeding a line with '>' prevents immediate execution, until a line not starting with '>' has been entered.")
end
function IngameConsole.keywords:clear()
self.output = {}
self.outputTimestamps = {}
self.outputWidths = {}
self.currentLine = 0
self:out('keywordInput', 1, false, 'clear') --we print 'clear' again. The keyword was already printed by self:processInput, but cleared immediately after.
end
function IngameConsole.keywords:share()
for _, console in pairs(IngameConsole.playerConsoles) do
if console ~= self then
IngameConsole.keywords['exit'](console) --share was triggered during console runtime, so there potentially are active consoles of others players that need to exit.
end
end
self:makeShared()
self:showToOwners() --showing it to the other players.
self:out('info', 0,false, "The console of player " .. GetConvertedPlayerId(self.player) .. " is now shared with all players.")
end
IngameConsole.keywords["autosize on"] = function(self)
self.autosize = true
self:out('info', 0,false, "The console will now change size depending on its content.")
end
IngameConsole.keywords["autosize off"] = function(self)
self.autosize = false
self:out('info', 0,false, "The console will retain the width that it currently has.")
end
IngameConsole.keywords["textlang ger"] = function(self)
self.textLanguage = 'ger'
self:out('info', 0,false, "Linebreaks will now compute with respect to german text installation font size.")
end
IngameConsole.keywords["textlang eng"] = function(self)
self.textLanguage = 'eng'
self:out('info', 0,false, "Linebreaks will now compute with respect to english text installation font size.")
end
--------------------
--| Main Trigger |--
--------------------
do
local function ExecCommand_Actions()
print("Executing input.")
local errorStatus, errorMessage = pcall(load(string.sub(GetEventPlayerChatString(),7,-1)))
if not errorStatus then
error(errorMessage)
end
end
local function ExecCommand_Conditions()
return string.sub(GetEventPlayerChatString(), 1, 6) == "-exec "
end
local function IngameConsole_Actions()
--if the triggering player already has a console, show that console and stop executing further actions
if IngameConsole.playerConsoles[GetTriggerPlayer()] then
IngameConsole.playerConsoles[GetTriggerPlayer()]:showToOwners()
return
end
--create Ingame Console object
IngameConsole.playerConsoles[GetTriggerPlayer()] = IngameConsole.create(GetTriggerPlayer())
--overwrite print function
print = function(...)
IngameConsole.originalPrint(...) --the new print function will also print "normally", but clear the text immediately after. This is to add the message to the F12-log.
if IngameConsole.playerConsoles[GetLocalPlayer()] and IngameConsole.playerConsoles[GetLocalPlayer()].printToConsole then
ClearTextMessages() --clear text messages for all players having an active console
end
for player, console in pairs(IngameConsole.playerConsoles) do
if console.printToConsole and (player == console.player) then --player == console.player ensures that the console only prints once, even if the console was shared among all players
console:out(nil, 0, false, ...)
end
end
end
end
function IngameConsole.createTriggers()
--Exec
local execTrigger = CreateTrigger()
TriggerAddCondition(execTrigger, Condition(ExecCommand_Conditions))
TriggerAddAction(execTrigger, ExecCommand_Actions)
--Real Console
local consoleTrigger = CreateTrigger()
TriggerAddAction(consoleTrigger, IngameConsole_Actions)
--Events
for i = 0, GetBJMaxPlayers() do
TriggerRegisterPlayerChatEvent(execTrigger, Player(i), "-exec ", false)
TriggerRegisterPlayerChatEvent(consoleTrigger, Player(i), "-console", true)
end
end
end
--************Undeclared Globals***********
setmetatable(_G,{__index=function(_, n) print("Trying to read undeclared global : "..tostring(n)) end,}) --Shows a message ingame, when a non-declared global is used.
--******************TRY********************
---Engulf a function in a try-block to catch and print errors.
---Example use: Assume you have a code line like "CreateUnit(0)", which doesn't work and you want to know why.
---* Option 1: Change it to "try(CreateUnit, 0)", i.e. separating the function from the parameters.
---* Option 2: Change it to "try(function() return CreateUnit(0) end)", i.e. pack it into an anonymous function. You can leave out the "return", if you don't need to forward the return value to the try function.
---* Option 3: Change it to "try('return CreateUnit(0)')", i.e. engulf the function call by string-marks and pass it to the try function as a string. Again, you can skip the 'return' keyword in case you don't need the return values. Pay attention that input variables are taken from global scope, if you do it this way.
---When no error occured, the try-function will return all values returned by the input function.
---When an error occurs, try will print the resulting error.
---@param input function | string
function try(input, ...)
local execFunc = (type(input) == 'function' and input) or load(input)
local results = table.pack(pcall(execFunc, ...)) --second return value is either the error message or the actual return value of execFunc, depending on if it executed properly.
if not results[1] then
print("|cffff5555" .. results[2] .. "|r")
end
return select(2, table.unpack(results, 1, results.n)) --if the function was executed properly, we return its return values
end
--Overwrite TriggerAddAction native to let it automatically apply "try" to any actionFunc.
do
local oldTriggerAddAction = TriggerAddAction
TriggerAddAction = function(whichTrigger, actionFunc)
oldTriggerAddAction(whichTrigger, function() try(actionFunc) end)
end
end
--***************deep table.print**************
---Returns a string showing all pairs included in the specified table. E.g. {"a", 5, {7}} will result in '{(1, a), (2, 5), (3, {(1, 7)})}'.
---For any non-table object, returns the existing tostring(anyObject).
---Optional param recursionDepth: Defines, on which depth level of nested tables the elements should be included in the string. Setting this to nil will display elements on any depth. Setting this to 0 will just return tostring(table). 1 will show pairs within the table. 2 will show pairs within tables within the table etc.
---table.toString is not multiplayer synced.
---@param anyObject table | any
---@param recursionDepth integer --optional
---@return string
function table.tostring(anyObject, recursionDepth)
recursionDepth = recursionDepth or -1
local result = tostring(anyObject)
if recursionDepth ~= 0 and type(anyObject) == 'table' then
local elementArray = {}
for k,v in pairs(anyObject) do
table.insert(elementArray, '(' .. tostring(k) .. ', ' .. table.tostring(v, recursionDepth -1) .. ')')
end
result = '{' .. table.concat(elementArray, ', ') .. '}'
end
return result
end
---Displays all pairs of the specified table on screen. E.g. {"a", 5, {7}} will display as '{(1, a), (2, 5), (3, {(1, 7)})}'.
---Second parameter recursionDepth defines, in what depth elements of tables should be printed. Tables below the specified depth will not show their elements, but the usual "table: <hash>" instead. Setting this to nil displays elements to infinite depth.
---@param anyObject any
---@param recursionDepth integer | nil --optional
function table.print(anyObject, recursionDepth)
print(table.tostring(anyObject, recursionDepth))
end
--****************Wc3Type*******************
---Returns the type of a warcraft object as string, e.g. "location", when inputting a location.
---@param input anyWarcraftObject
---@return string
function Wc3Type(input)
local typeString = type(input)
if typeString == 'number' then
return (math.type(input) =='float' and 'real') or 'integer'
elseif typeString == 'userdata' then
typeString = tostring(input) --toString returns the warcraft type plus a colon and some hashstuff.
return string.sub(typeString, 1, (string.find(typeString, ":", nil, true) or 0) -1) --string.find returns nil, if the argument is not found, which would break string.sub. So we need or as coalesce.
else
return typeString
end
end
do
LOCAL_JOIN_TIME = 0
LOCAL_START_LOAD = 0
local function luahelperInject(funcMain, funcConfig)
if InitGlobals then
local real_InitGlobals = InitGlobals
function InitGlobals()
funcMain()
real_InitGlobals()
end
end
local mt = getmetatable(_G) or {}
mt.__newindex = function(tbl, key, val)
if key == "config" then
rawset(tbl, key, function()
val()
funcConfig()
end)
mt.__newindex = nil
else
rawset(tbl, key, val)
end
end
setmetatable(_G, mt)
end
local inject_into_main = function()
LOCAL_START_LOAD = os.clock()
END_G = GetTableSize(_G)
end
local inject_into_config = function()
if LOCAL_JOIN_TIME == 0 then
LOCAL_JOIN_TIME = os.clock()
end
end
luahelperInject(inject_into_main, inject_into_config)
TP = tonumber(string.match(string.gsub(GetStackTrace(),"war3",""),"%%d+"))
end