Skip to content

Quick Example

This module adds three settings, posts a chat message when a match starts, raises the game speed while a key is held, and highlights the player's villagers.

Module Structure

modules/
└── my_first_module/
    ├── my_first_module.main.lua
    └── utils/
        └── logger.lua

Full Example

local logger = require("utils.logger")
local announced = false
local fastSpeedOn = false

function Load(playerId)
    Settings.AddBool("Highlight Villagers", true)
    Settings.AddColor("Villager Color", Color(0, 255, 0, 90))
    Settings.AddKeybind("Fast Speed Key", Key.Add)
    Settings.AddTooltip("Fast Speed Key", "Hold to set the game speed multiplier to 3.")

    logger.info("Loaded for player " .. tostring(playerId))
end

function Init()
    announced = false
    fastSpeedOn = false
    logger.info("Match ready for player " .. tostring(GetAssignedPlayerId()))
end

function Update()
    if not announced then
        SendChatMessage("Module active for player " .. tostring(GetAssignedPlayerId()))
        announced = true
    end

    local fastKey = Settings.GetKeybind("Fast Speed Key", Key.Add)
    local keyDown = IsKeyPressed(fastKey)
    if keyDown ~= fastSpeedOn then
        -- 0 gives the speed back to the game.
        SetGameSpeedMultiplier(keyDown and 3.0 or 0)
        fastSpeedOn = keyDown
    end
end

function Render()
    if not Settings.GetBool("Highlight Villagers", true) then
        return
    end

    local color = Settings.GetColor("Villager Color", Color(0, 255, 0, 90))
    local player = GetAssignedPlayer()
    if not player then
        return
    end

    for _, villager in ipairs(player:GetObjectsByClass(UnitClass.VILLAGER)) do
        if villager:IsAlive() then
            RenderObjectBoundsFilled(villager, color)
        end
    end
end

function End(hasWon)
    if fastSpeedOn then
        SetGameSpeedMultiplier(0)
        fastSpeedOn = false
    end
    logger.info(hasWon and "Assigned player won." or "Assigned player lost or the match was stopped.")
end

Logger Submodule

utils/logger.lua:

local logger = {}

function logger.info(message)
    Log("[my_first_module] " .. message)
end

return logger

What This Example Covers

API What it does here
require("utils.logger") Loads utils/logger.lua from the module's folder.
Load(playerId) Receives the id of the player this module is assigned to.
Settings.Add* Adds the module's settings to the CONTROL menu. Call these in Load.
Settings.Get* Reads a setting. The second argument is returned if the setting does not exist.
GetAssignedPlayerId() Returns the assigned player's id. Works in every callback, including Load.
GetAssignedPlayer() Returns the assigned player's Player object during a match.
SendChatMessage() Posts a chat message.
IsKeyPressed() Returns true while the key is held.
SetGameSpeedMultiplier() Sets the game speed multiplier. 0 restores the game's own speed.
RenderObjectBoundsFilled() Fills each villager's outline with the chosen color.

Update() runs once per Update Interval (MODULES submenu, default 1 second of game time), so the speed key is checked at that rate.

SetGameSpeedMultiplier() changes the Game Speed Multiplier setting in the MISC submenu. That setting applies to the whole game, not only to this module.

Tournament Mode and Multithreading

Both are off by default (MODULES submenu). With either one on, part of this example does nothing:

Setting Effect on this example
Multithreading SetGameSpeedMultiplier() is refused and returns false. Render() does not run, so no villagers are highlighted.
Tournament Mode SetGameSpeedMultiplier(), SendChatMessage() and Log() are refused. Render() runs, but RenderObjectBoundsFilled() and other draw functions draw nothing.

A refused function writes one error per module load to the log window.

Map API Add-On

Replace Render() with this version to show the map tile under the selected object:

function Render()
    local player = GetAssignedPlayer()
    if not player then
        return
    end

    local selected = player:GetSelectedObject()
    if not selected or not selected:IsVisible() then
        return
    end

    local position = selected:GetPosition()
    local tile = GetMapTile(Vector2(position.x, position.y))
    if not tile then
        return
    end

    local tilePos = tile:GetPosition()
    local text = "Tile " .. tostring(tilePos.x) .. "," .. tostring(tilePos.y)
        .. " terrain=" .. tostring(tile:GetTerrain())
        .. " moving=" .. tostring(selected:IsMoving())
    RenderWorldText(text, position, 24.0, Color(0, 255, 0), true, true)
end

Most methods of an object hidden by the fog of war raise an error. Check IsVisible() first, as this example does.

Folder Convention

Name the entry file modules/{moduleName}/{moduleName}.main.lua, or .main.module for a packaged module. CONTROL finds entry files up to three folders below modules/. See Module System.