• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Tower Defence Toolkit (TDTK)

This bundle is marked as pending. It has not been reviewed by a staff member yet.
  • Like
Reactions: Synx

Tower Defence Toolkit (TDTK) v0.2
Created by B1Gbull#2338

Info:
A simple and quick System to create your own Tower Defence game.


Note:
- Each path and each wave can be customized with the number of units, spawn intervals and unit types.
- The focus this System is on handling waves and paths.
- Towers and builders can be created as usual using the object editor.
- The system itself does not require any object editor data.
- Lua knowledge is an advantage but not necessary. For the basic functionality just copy paste some predefined lines, which are shown in the example.
- Player 24 (Peanut) is used for the creep waves. Therefore this player should not be used.




Solo

Team


Lets create a very basic TD where every player has their own path.
To keep things simple, we limit the number of players to 2.
Both players have the same conditions and therefore get the same waves.

The paths that the creep should run are as follows:
Red Path: R1 > R2 > R3 > R4
Blue Path: B1 > B2 > B3 > B4

173895-f2930390d765cc5c4501d84305d37708.png


First create a new custom script in the trigger editor and copy paste the TDTK Code.
Create a second custom script which will be used for our own settings.

Create a function and name it whatever you want. In this example i use MyCustomMod().
I recommend calling the function on the Melee Initialization trigger.

  • Melee Initialization
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Custom script: MyCustomMod()
Copy paste the following code inside your second custom script.

The creep waves will be defined later within the TDTK.Settings() function.

Lua:
--Call this function on Map Init
function MyCustomMod()
    --Starts the TDTK system
    TDTK.Init()
    --Overwrite the global TDTK.Settings() function
    function TDTK.Settings()
        --Your own settings ...
    end
end


The steps are now as follows:
1. Create a WaveManager who tracks and handles all units and waves for you. Every path requires its own WaveManager
2. Add the regions to the WaveManager in the correct order.
3. Add waves of units
4. Add the accountable players who will be punished if a creep reaches the final destination
5. Repeat 1. for every path

Full code that includes 4 waves.
Lua:
--Call this function on Map Init
function MyCustomMod()
    --Initialize TDTK
    TDTK.Init()
    --Overwrite the global TDTK.Settings() function
    function TDTK.Settings()
        -- 1. Create a WaveManager for the red path
        local wmRed = TDTK.WaveManager():Create()
        -- 2. Add the regions in the correct order (R1 > R2 > R3 > R4)
        -- We need at least 2 regions, the first one is the spawning region and the last one the final destination
        wmRed:AddDestination("R1")
        wmRed:AddDestination("R2")
        wmRed:AddDestination("R3")
        wmRed:AddDestination("R4")
        -- 3. Adding unit Waves
        -- You can get the UnitType Id by pressing STRG + D in the object editor
        wmRed:AddWave("nban") -- Bandit (First Wave)
        wmRed:AddWave("nkot") -- Kobold (Second Wave)
        wmRed:AddWave("nomg") -- Ogre Mage (...)
        wmRed:AddWave("nhrh") -- Harpy (Last Wave)
        -- Four waves are enough. I think you got the idea
        -- 4. Add the Player to the path
        wmRed:AddPlayer(1) -- Player 1 (Red)
        --------------------------------------------------------------------------------------------------
        -- 5. Repeat for the blue path
        -- 1. Create a WaveManager for the blue path
        local wmBlue = TDTK.WaveManager():Create()
        -- 2. Add the regions in the correct order (B1 > B2 > B3 > B4)
        wmBlue:AddDestination("B1")
        wmBlue:AddDestination("B2")
        wmBlue:AddDestination("B3")
        wmBlue:AddDestination("B4")
        -- 3. Since the blue player should receive the same waves, we can simply copy the waves from the red manager
        wmBlue:CopyWaves(wmRed)
        -- 4. Add the second Player
        wmBlue:AddPlayer(2) -- Player 2 (Blue)
    end
end

Done!!!

Check out the Team example for a more advanced version in which individual waves are also customized.


First check out the Solo example for a more beginner friendly version where all steps are explained from the beginning.

In this example we create a Team version with 3 paths.

173896-530f27f66861247e7f7a929d91ef7633.png


The red and blue paths are supposed to spawn the same units and the green path is used for the boss waves.

For the sake of simplicity, the first waves are the same as in the solo example.

Lua:
--Call this function on Map Init
function MyCustomMod()
    --Initialize TDTK
    TDTK.Init()
    --Overwrite the global TDTK.Settings() function
    function TDTK.Settings()
        -- 1. Create a WaveManager for the red path
        local wmRed = TDTK.WaveManager():Create()
        -- 2. Add the regions in the correct order (R1 > R4 > R5 > R6 > R7 > R10 > R11)
        wmRed:AddDestination("R1") -- Spawn Region
        wmRed:AddDestination("R4")
        wmRed:AddDestination("R5")
        wmRed:AddDestination("R6")
        wmRed:AddDestination("R7")
        wmRed:AddDestination("R10")
        wmRed:AddDestination("R11") -- Final Destination
        -- 3. Adding unit Waves
        wmRed:AddWave("nban") -- Bandit
        wmRed:AddWave("nkot") -- Kobold
        wmRed:AddWave("nomg") -- Ogre
        wmRed:AddWave("nhrh") -- Harpy
        -- 4. Add both Player to the path
        wmRed:AddPlayer(1)
        wmRed:AddPlayer(2)
        --------------------------------------------------------------------------------------------------
        -- 5. Repeat the same for the blue path
        local wmBlue = TDTK.WaveManager():Create()
        wmBlue:AddDestination("R2")
        wmBlue:AddDestination("R4")
        wmBlue:AddDestination("R5")
        wmBlue:AddDestination("R8")
        wmBlue:AddDestination("R9")
        wmBlue:AddDestination("R10")
        wmBlue:AddDestination("R11")
        -- Copy the waves from the red manager
        wmBlue:CopyWaves(wmRed)
        -- Since the blue path effects the same players, lets copy them from the red path
        wmBlue:CopyPlayers(wmRed)
        --------------------------------------------------------------------------------------------------
        -- Now for the boss path
        local wmGreen = TDTK.WaveManager():Create()
        wmGreen:AddDestination("R3")
        wmGreen:AddDestination("R11")
        -- We want a boss to spawn at wave 4 so lets add 3 empty waves
        wmGreen:AddWave(nil) -- Empty wave
        wmGreen:AddWave(nil)
        wmGreen:AddWave(nil)
        -- We want to change the default settings for the boss wave
        -- These settings are only for this specific wave. You can therefore set each wave individually.
        local bossWave = wmGreen:AddWave("emtg") -- Mountain Giant
        bossWave.count = 3 -- Only 3 units will spawn in total (default is 10)
        bossWave.interval = 5 -- Units spawn every 5 seconds (default is 2 seconds)
        bossWave.damage = 5 -- Every unit will remove 5 life from the player if it reach the final destination (default is 1)
        -- Copy the players from the red path
        wmGreen:CopyPlayers(wmRed)
        --------------------------------------------------------------------------------------------------
        -- Global Settings
        TDTK.globals.startResource.gold = 200 -- All Player will start with 200 Gold (default is 0)
        TDTK.globals.startResource.life = 30 -- All Player will have 30 life (default is 20)
        TDTK.globals.wave.interval = 45 -- Every 45 seconds a new wave will start (default is 30)
        TDTK.globals.wave.gold = 20 -- All Player gain 20 gold at the start of every wave (default is 0)
    end
end


Optional texts that can be changed.

Lua:
TDTK.globals.text.playerLoseLife = "${1} life remaining", -- Use ${1} to show the player life
TDTK.globals.text.newWave = "Wave ${1}/${2}", -- Use ${1} to show the current wave number and/or ${2} to show the last wave number
TDTK.globals.text.waveDialog = "Wave in:" -- =nil will remove the dialog






Also goes into the MyCustomMod() function.
Lua:
    --Optional
    function TDTK.AWaveBegins(number)
        -- Is triggered at the start of every wave
    end
    --Optional
    function TDTK.AUnitIsCreated(unitObj)
        -- Is triggered when a creep has been created by the TDTK System
        -- unitObj.this is the triggering unit
    end
    --Optional
    function TDTK.AUnitDies(unitObj)
        -- Is triggered when a creep (which belongs to the TDTK System) dies
        -- unitObj.this is the triggering unit
    end
    --Optional
    function TDTK.AUnitEntersFinalDestination(unitObj)
        -- Is triggered when a creep (which belongs to the TDTK System) reaches the end
        -- unitObj.this is the triggering unit
    end
    --Optional
    function TDTK.APlayerCompletedAllWaves(playerObj)
        -- Is triggered when a Player has complete all Waves
        -- playerObj.this is the triggering player
    end
    --Optional
    function TDTK.APlayerIsDefeat(playerObj)
        -- Is triggered when a Player has lost all of his life
        -- playerObj.this is the triggering player
    end




In this example we create a unit that spawns other units on death, that will be registered by the TDTK System.
Also goes into the MyCustomMod() function.

You can name the new UnitType as you like. I used BreederType in this example.
Two functions can be overwritten:
Lua:
UnitType:OnAlive() -- Is called when the unit is created
UnitType:OnDeath() -- Is called when the unit dies

Full code:
Lua:
    -- Inherit from TDTK.UnitType
    local BreederType = TDTK.UnitType():Create()
    -- Create a BreederType Class
    function BreederType:Create(parentId, childId)
        local this = TDTK.UnitType():Create(FourCC(parentId))
        -------------------------------------------------------------
        --Here goes your custom values that will be use in OnAlive and OnDeath
        this.childId = FourCC(childId) -- use FourCC to convert the string to id
        this.count = 2 -- default creep count
        -------------------------------------------------------------
        self.__index = self
        setmetatable(this, self)
        return this
    end
    -- Override the OnDeath function (we dont need OnAlive in this example)
    function BreederType:OnDeath(unitObj)
        for i = 1, self.count, 1 do
            local loc = GetUnitLoc(unitObj.this)
            -- Create a creep and saves it in the TDTK database
            local childObj = TDTK.Unit().NewUnit(loc, self.childId)
            RemoveLocation(loc)
            -- Copy all information from the parent creep (path, current destination). This information is also provided to the WaveManager
            childObj:CopyState(unitObj)
            -- Immediately order the creep to move to the current destination
            childObj:Order()
        end
    end

    -- Add the new UnitTypes to the TDTK System
    -- Bandit will Spawn 2 Spiders on death
    BreederType:Create("nban","nspb")
    -- Ogre Mage will Spawn 1 Grunt on death
    local unitType = BreederType:Create("nomg","ogru")
    unitType.count = 1



Lua:
------------------------------------------------------------------------------------------------------
-- 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



- Initial release


- Readability of the code improved
- Unified embedded text var's: from #life, #number, #last to ${1}, ${2} ...

Previews
Contents

TDTK Solo Example v0.2 (Map)

TDTK Team Example v0.2 (Map)

Level 12
Joined
Jan 30, 2020
Messages
875
I like the way you coded this.
Very clean and efficient.

No need to test further than reading to find out about the quality of your work.
Takes me back to the endless hours of manual Lua conversion for my own TD :)

Well done !!!
 
Top