------------------------------------------------------------------------------------------------------
-- Tower Defence Toolkit (TDTK) v0.2
-- By B1Gbull#2338
------------------------------------------------------------------------------------------------------
TDTK = {}
-- Global values that can be changed inside the Settings() function
TDTK.globals = {
wave = {
interval = 30,
gold = 0
},
startResource = {
gold = 0,
lumber = 0,
life = 20
},
text = {
playerLoseLife = "${1} life remaining", -- Use ${1} to show the player life
newWave = "Wave ${1}/${2}", -- Use ${1} to show the current wave number and/or ${2} to show the last wave number
waveDialog = "Wave in:" -- =nil will remove the dialog
}
}
-- Global references
TDTK.collection = {
unit = {},
unitType = {},
region = {},
waveManager = {},
player = {},
timer = {}
}
local Event = {onEnterRegionTrigger}
local Util = {}
-- Classes
local Region = {}
TDTK.Region = function() return Region end
local WaveManager = {}
TDTK.WaveManager = function() return WaveManager end
local Wave = {}
TDTK.Wave = function() return Wave end
local WaveType = {}
TDTK.WaveType = function() return WaveType end
local Unit = {}
TDTK.Unit = function() return Unit end
local UnitType = {}
TDTK.UnitType = function() return UnitType end
local Timer = {}
TDTK.Timer = function() return Timer end
local User = {} -- Player is already taken
TDTK.Player = function() return User end
------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------
-- Global functions that can be overwritten
function TDTK.Settings()
Util.Error("Error: No Settings found")
end
function TDTK.AWaveBegins(number)
end
function TDTK.AUnitIsCreated(unitObj)
end
function TDTK.AUnitDies(unitObj)
end
function TDTK.AUnitEntersFinalDestination(unitObj)
end
function TDTK.APlayerCompletedAllWaves(playerObj)
end
function TDTK.APlayerIsDefeat(playerObj)
end
------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------
-- Initializes the system
TDTK.Init = function()
Event.onEnterRegionTrigger = CreateTrigger()
TriggerAddAction(Event.onEnterRegionTrigger, Event.OnEnterRegion)
local trig = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_DEATH)
TriggerAddAction(trig, Event.OnUnitDie)
local ExpiredFunc = function()
local t = TDTK.collection.timer[GetExpiredTimer()]
TDTK.Settings()
User.SetStartResource()
SetPlayerState(Player(23), PLAYER_STATE_GIVES_BOUNTY, 1)
Timer:Create(1, true, Unit.OrderIdle)
WaveManager.Execute()
t:Remove()
end
Timer:Create(0.01,false,ExpiredFunc)
end
------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------
--local trigger events that are used
Event.OnEnterRegion = function()
local unit = GetTriggerUnit()
local unitObj = Unit.Get(unit)
if unitObj ~= nil then
--Check if triggering region is the current destination of the unit
local region = unitObj:GetCurrentDestination()
if region.this == GetTriggeringRegion() then
--Set the next destination for triggering unit
unitObj:SetNextDestination()
if unitObj:GetCurrentDestination() ~= nil then
--Order unit to move to his destination
unitObj:Order()
else
--Final region reached
TDTK.AUnitEntersFinalDestination(unitObj)
unitObj:RemoveLife()
unitObj:Clear()
RemoveUnit(unitObj.this)
end
end
end
end
Event.OnUnitDie = function()
local unit = GetTriggerUnit()
local unitObj = Unit.Get(unit)
if unitObj ~= nil then
TDTK.AUnitDies(unitObj)
unitObj:OnDeath()
unitObj:Clear()
end
end
------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------
--Is used to track the triggering rect from GetTriggeringRegion()
function Region:Create(rect)
local this = {}
this.this = CreateRegion()
this.rect = rect
RegionAddRect(this.this, rect)
self.__index = self
setmetatable(this, self)
TDTK.collection.region[this.rect] = this
return this
end
------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------
--Contains all information about a path
function WaveManager:Create()
local this = {}
this.regions = {}
this.players = {}
this.waves = {}
this.enabled = true
self.__index = self
setmetatable(this, self)
TDTK.collection.waveManager[this] = this
return this
end
--Add a new Region as destination
function WaveManager:AddDestination(regionName)
local rectRegion = _G["gg_rct_"..tostring(regionName)]
if rectRegion then
local regionObj = TDTK.collection.region[rectRegion]
if regionObj == nil then
regionObj = Region:Create(rectRegion)
TriggerRegisterEnterRegionSimple(Event.onEnterRegionTrigger, regionObj.this)
end
table.insert(self.regions, regionObj)
return regionObj
else
return nil
end
end
function WaveManager:AddPlayer(number)
local playerNum = math.max(number-1,0)
playerNum = math.min(playerNum,23)
local player = Player(playerNum)
if GetPlayerSlotState(player) == PLAYER_SLOT_STATE_PLAYING and GetPlayerController(player) == MAP_CONTROL_USER then
local playerObj = User:Create(Player(playerNum))
playerObj.waveManagers[self] = self
self.players[player] = playerObj
table.insert(playerObj.waveManagers, self)
end
end
--Return the region by index. 1 = spawn region ...
function WaveManager:GetDestinationByIndex(index)
return self.regions[index]
end
--Return the wave by index. 1 = first wave ...
function WaveManager:GetWaveByIndex(index)
return self.waves[index]
end
function WaveManager:AddWave(unitCode)
local id = unitCode and FourCC(unitCode) or nil
local waveType = WaveType:Create(id)
local wave = Wave:Create(waveType,self)
table.insert(self.waves, wave)
return waveType
end
--Copy the waves from another WaveManager
function WaveManager:CopyWaves(waveManager)
for _, w in pairs(waveManager.waves) do
local wave = Wave:Create(w.waveType,self)
table.insert(self.waves, wave)
end
end
--Copy the player from another WaveManager
function WaveManager:CopyPlayers(waveManager)
self.players = waveManager.players
end
--Return the last wave number of the current WaveManager
function WaveManager:GetWaveCount()
local count = 0
for _ in pairs(self.waves) do
count = count + 1
end
return count
end
--Checks if the WaveManager has at least 2 destinations, 1 player and 1 wave
--Is Called once after Settings()
function WaveManager:Validate()
local count = 0
for _ in pairs(self.regions) do count = count + 1 end
if count < 2 or next(self.players) == nil or next(self.waves) == nil then
return false
end
return true
end
--Checks whether the manager has completed all waves
function WaveManager:IsComplete()
for _, w in pairs(self.waves) do
if w:IsComplete() == false then
return false
end
end
return true
end
function WaveManager:Disable()
self.enabled = false
end
--Starts all waves of all WaveManagers
--Called once after Settings()
WaveManager.Execute = function()
local ExpiredFunc = function()
local t = TDTK.collection.timer[GetExpiredTimer()]
local currentIndex = t.arg[1]
local lastIndex = t.arg[2]
local dialog = t.arg[3]
--Start new wave if the timer expire
for _, wm in pairs(TDTK.collection.waveManager) do
if wm.enabled then
local w = wm:GetWaveByIndex(currentIndex)
w:Execute()
end
end
--Add gold and send a message
local changeGold = TDTK.globals.wave.gold ~= 0 and true or false
for _, p in pairs(TDTK.collection.player) do
if TDTK.globals.text.newWave then
Util.WarningToPlayer(Util.EmbedToString(TDTK.globals.text.newWave,currentIndex,lastIndex),p.this)
end
if changeGold then
p:AddGold(TDTK.globals.wave.gold)
end
end
TDTK.AWaveBegins(currentIndex)
--Stops the timer if it is the last one
if currentIndex >= lastIndex then
if dialog then
DestroyTimerDialog(dialog)
end
t:Remove()
end
--Increase current wave index
t.arg[1] = t.arg[1] + 1
end
local lastIndex = 1
--Find out the last wave index
for _, wm in pairs(TDTK.collection.waveManager) do
if wm:Validate() then
local count = wm:GetWaveCount()
if lastIndex < count then
lastIndex = count
end
else
wm:Disable()
end
end
--Create the wave timer that starts all waves
local t = Timer:Create(TDTK.globals.wave.interval,true,ExpiredFunc,1,lastIndex)
if TDTK.globals.text.waveDialog then
t.arg[3] = CreateTimerDialogBJ(t.this, tostring(TDTK.globals.text.waveDialog))
end
end
------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------
--Stores the static wave information (like UnitType)
function WaveType:Create(unitId)
local this = {}
this.unitId = unitId
this.count = 10
this.interval = 2
this.damage = 1
self.__index = self
setmetatable(this, self)
return this
end
------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------
function Wave:Create(waveType,waveManager)
local this = {}
this.waveType = waveType
this.waveManager = waveManager
this.spawnCounter = 0
this.enabled = waveType.unitId ~= nil and true or false
this.units = {}
self.__index = self
setmetatable(this, self)
return this
end
--Starts the wave
--Is called when the global wave timer expire
function Wave:Execute()
if self.enabled then
self:Spawn()
local ExpiredFunc = function()
local t = TDTK.collection.timer[GetExpiredTimer()]
local wave = t.arg[1]
if wave.spawnCounter<wave.waveType.count then
wave:Spawn()
else
wave:Disable()
t:Remove()
end
end
Timer:Create(self.waveType.interval,true,ExpiredFunc,self)
end
end
--Creates a unit that belongs to the wave
--Is called when the internal spawn timer expire
function Wave:Spawn()
local loc = GetRandomLocInRect(self.waveManager.regions[1].rect)
local unitObj = Unit.NewUnit(loc, self.waveType.unitId)
RemoveLocation(loc)
self:AddUnit(unitObj)
self.spawnCounter = self.spawnCounter + 1
end
--Is called every time a unit die
function Wave:IsComplete()
-- enabled = false means it will no longer spawn untis
-- But there might be units alive, that belongs to this wave
if self.enabled == false and next(self.units) == nil then
return true
else
return false
end
end
function Wave:Disable()
self.enabled = false
end
--Called when a unit is created to link the unit to the wave
function Wave:AddUnit(unitObj)
unitObj.wave = self
self.units[unitObj.this] = unitObj
TDTK.AUnitIsCreated(unitObj)
unitObj:OnAlive()
end
------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------
-- Player
function User:Create(player)
local this = TDTK.collection.player[player]
if this == nil then
this = {}
this.this = player
this.life = 1
this.waveManagers = {}
self.__index = self
setmetatable(this, self)
TDTK.collection.player[player] = this
end
return this
end
--Set startig resource for all player
--Called once after Settings()
User.SetStartResource = function()
for _, p in pairs(TDTK.collection.player) do
if TDTK.globals.startResource.life > 0 then
p.life = TDTK.globals.startResource.life
end
if TDTK.globals.startResource.gold > 0 then
SetPlayerState(p.this, PLAYER_STATE_RESOURCE_GOLD, TDTK.globals.startResource.gold)
end
if TDTK.globals.startResource.lumber > 0 then
SetPlayerState(p.this, PLAYER_STATE_RESOURCE_LUMBER, TDTK.globals.startResource.lumber)
end
end
end
User.Get = function(player)
return TDTK.collection.player[player]
end
--Remove life from Player
--Is called when a unit enters final region
function User:Damage(value)
self.life = math.max(self.life - value, 0)
if TDTK.globals.text.playerLoseLife then
Util.WarningToPlayer(Util.EmbedToString(TDTK.globals.text.playerLoseLife,self.life),self.this)
end
if self.life <= 0 then
--Player is defeat
TDTK.APlayerIsDefeat(self)
self:Lose()
self:Clear()
end
end
--Player completed all waves
--Is called every time a unit dies
function User:IsComplete()
for _, wm in pairs(self.waveManagers) do
if wm:IsComplete() == false then
return false
end
end
return true
end
function User:Win()
CustomVictoryBJ(self.this, true, true)
end
function User:Lose()
CustomDefeatBJ(self.this, "Defeat!")
end
--Remove all references to prevent leaks
--Called after a player win or lose
function User:Clear()
for _, wm in pairs(self.waveManagers) do
wm.players[self.this] = nil
--TDTK.collection.player[self.this] = nil
if next(wm.players) == nil then
wm.Disable()
end
end
end
function User:AddGold(value)
local gold = GetPlayerState(self.this, PLAYER_STATE_RESOURCE_GOLD)
SetPlayerState(self.this, PLAYER_STATE_RESOURCE_GOLD, gold+value)
end
function User:AddLumber(value)
local lumber = GetPlayerState(self.this, PLAYER_STATE_RESOURCE_LUMBER)
SetPlayerState(self.this, PLAYER_STATE_RESOURCE_LUMBER,lumber+value)
end
------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------
--Base class to inherit from
--See custom unit tutorial
function UnitType:Create(id)
local this = TDTK.collection.unitType[id]
if this == nil then
this = {}
this.id = id
self.__index = self
setmetatable(this, self)
if id ~= nil then TDTK.collection.unitType[id] = this end
end
return this
end
function UnitType:OnAlive(unitObj) end
function UnitType:OnDeath(unitObj) end
------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------
function Unit:Create(unit)
local this = TDTK.collection.unit[unit]
if this == nil then
this = {}
this.this = unit
this.typeObj = UnitType:Create(GetUnitTypeId(unit))
this.wave = nil
this.destinationIndex = 1
self.__index = self
setmetatable(this, self)
TDTK.collection.unit[unit] = this
end
return this
end
function Unit:OnAlive()
self.typeObj:OnAlive(self)
end
function Unit:OnDeath()
self.typeObj:OnDeath(self)
end
Unit.Get = function(unit)
return TDTK.collection.unit[unit]
end
--Remove all references to prevent leaks
--Called after a unit dies or before is removed
function Unit:Clear()
self.wave.units[self.this] = nil
TDTK.collection.unit[self.this] = nil
local players = self.wave.waveManager.players
for _, p in pairs(players) do
if p:IsComplete() and p.life > 0 then
TDTK.APlayerCompletedAllWaves(p)
p:Win()
p:Clear()
end
end
end
--Is used to create units for the TDTK System
Unit.NewUnit = function(loc, id)
local unit = CreateUnitAtLoc(Player(23), id, loc, bj_UNIT_FACING)
local unitObj = Unit:Create(unit)
return unitObj
end
--Transfers all information from one to another unit
--See custom unit tutorial
function Unit:CopyState(unitObj)
self.destinationIndex = unitObj.destinationIndex
unitObj.wave:AddUnit(self)
end
--Reorder idle units.
--Is called every 1 sec
Unit.OrderIdle = function()
for _, unitObj in pairs(TDTK.collection.unit) do
if GetUnitCurrentOrder(unitObj.this) == 0 then
unitObj:Order()
end
end
end
--Order the unit to his current destination
--Is called if the unit reaches his destination or is idle
function Unit:Order()
region = self:GetCurrentDestination()
if region ~= nil then
local loc = GetRectCenter(region.rect)
IssuePointOrderLoc(self.this, "move", loc)
RemoveLocation(loc)
end
end
--Gives the unit the next destination
--Is called if the unit has enter his destination region
function Unit:SetNextDestination()
self.destinationIndex=self.destinationIndex+1
end
function Unit:GetPlayers()
return self.wave.waveManager.players
end
function Unit:GetCurrentDestination()
return self.wave.waveManager:GetDestinationByIndex(self.destinationIndex)
end
--Is Called when the unit reaches the final destination
function Unit:RemoveLife()
for _, p in pairs(self.wave.waveManager.players) do
p:Damage(self.wave.waveType.damage)
end
end
------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------
function Timer:Create(duration,isPeriodic,ExpiredFunc,...)
local this = {}
this.this = CreateTimer()
this.arg = {...}
self.__index = self
setmetatable(this, self)
TDTK.collection.timer[this.this] = this
TimerStart(this.this, duration, isPeriodic, ExpiredFunc)
return this
end
function Timer:Remove()
DestroyTimer(self.this)
TDTK.collection.timer[self] = nil
end
------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------
Util.WarningToPlayer = function(text,player)
DisplayTimedTextToPlayer(player, 0, 0, 2, "|cffe6be00"..tostring(text).."|r")
local sound = "Sound\\Interface\\Error.flac"
if GetLocalPlayer() ~= player then
sound = ""
end
PlaySound(sound)
end
Util.Error = function(text)
DisplayTextToForce( GetPlayersAll(),"|cffff0000"..tostring(text).."|r" )
end
Util.EmbedToString = function(txt,...)
local arg = {...}
local newTxt = tostring(txt)
for i, var in ipairs(arg) do
newTxt = string.gsub(newTxt,[[${]]..tostring(i)..[[}]], tostring(var))
end
return newTxt
end