• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.
  • The Hive's 22nd Icon Contest: Creep Abilities is now concluded, time to vote for your favourite set of icons! Click here to vote!
  • ✅ The POLL for Hive's Texturing Contest #34 is OPEN! Vote for the TOP 3 SKINS! 🔗Click here to cast your vote!
  • ✅ The POLL for Hive's Techtree Contest #20 is OPEN! Vote for the TOP 3 FACTIONS! 🔗Click here to cast your vote!

Anti-cheat for single player

Simple and easy system that allows you to catch a player who using cheat codes in a single player game.

How it works?​

The system uses three triggers. The first two track TEXT_CHANGED and ENTER events for the Edit Box frame. The third trigger is auxiliary, and it is needed to re-register frame events for the main triggers when loading a saved game (otherwise, the framehandle and its events will be lost). Each text input will be compared with the contents of the cheat code table, and if a match is found, it can most likely be regarded as a cheat code input.

Installation​

1. Paste the code into the map.
2. Call the AntiCheat:init(debugMode) function. The debugMode argument is used to enable/disable debug messages (true/false).

Setting up​

1. The reaction to entering a cheat code is in the AntiCheat:_punish(cheat) function. You can change it as you wish. The function takes the string of the found cheat code.
2. The AnitCheat.list table stores a list of cheat codes, which can be changed if necessary.
3. The AntiCheat:setEnable(true/false) function allows you to enable/disable the system if necessary.
4. You can delete all system triggers at runtime by calling AntiCheat:destroy()

Lua:
--[[
    AntiCheat — Simple cheat code detection for single-player Warcraft 3 maps.

    Last tested patch: Reforged 2.0.3

    How it works:
    — Listens for player input in the Edit Box UI frame.
    — Compares input against a table of known cheat codes.
    — If a match is found, triggers a punishment (by default: player defeat).

    Configuration:
    — Customize the AntiCheat:_punish(cheat) function for your own cheat code responses.
    — Edit AntiCheat.list to add or remove cheat codes.

    Control:
    — Initialize with AntiCheat:init(debugMode). Set debugMode to true or false.
    — Disable or re-enable the system with AntiCheat:setEnable(false/true).
    — Remove the system with AntiCheat:destroy().

    Important note:
    UI frame events must not be initialized too early, as the game UI may not be available yet.
    This system uses a short delay and checks for null frames to minimize the risk,
    but always ensure you are not calling AntiCheat:init() before the game UI is fully loaded.

    Naming convention:
    Functions prefixed with an underscore (`_`) are internal. Do not call these directly.
--]]

AntiCheat = {}
AntiCheat.debug = true

function AntiCheat:_punish(cheat)
    self:_displayMessage(cheat) -- Debug

    -- You can add any reaction to entering a cheat code to this function
    -- In this example, the player gets defeat

    CustomDefeatBJ(GetLocalPlayer(), "Good luck next time!")
end

AntiCheat.list = { -- Table of known cheat codes in Warcraft 3
    "allyourbasearebelongtous", -- Instantly win the current mission
    "daylightsavings", -- Switches from day to night, halts or restarts the flow of the day/night cycle
    "greedisgood", -- Instantly obtain set number of lumber and gold
    "iocanepowder", -- Enables fast acting death/decay of bodies
    "iseedeadpeople", -- Full map is revealed, fog of war disabled
    "itvexesme", -- Disables victory conditions
    "keysersoze", -- Instantly obtain set number of gold
    "leafittome", -- Instantly obtain set number of lumber
    "lightsout", -- Sets time of day to evening
    "motherland", -- Selects a mission number for the chosen race to warp to
    "pointbreak", -- Disables food limit for creating new units
    "riseandshine", -- Sets time of day to morning
    "sharpandshiny", -- Instantly grants all upgrades
    "somebodysetupthebomb", -- Instantly lose the current mission
    "strengthandhonor", -- Disables game over screen after losing objectives in campaign mode
    "synergy", -- Unlocks the full tech tree
    "tenthleveltaurenchieftan", -- Plays a special music track "Power of the Horde"
    "thedudeabides", -- Enables faster spell cooldown times
    "thereisnospoon", -- All units gain infinite mana
    "warpten", -- Enables faster building times
    "whoisjohngalt", -- Enables faster research time for upgrades
    "whosyourdaddy", -- All units and buildings gain full invulnerability, units will be able to 1-hit kill any opponent or enemy structure (does not effect friendly fire)
    -- Source: https://www.ign.com/wikis/warcraft-3/PC_Cheats_and_Secrets_-_List_of_Warcraft_3_Cheat_Codes
}

-- Debug Messages
AntiCheat.messages = {
    success = "|c0000FF80Anti-Cheat system is enabled.|r",
cancel = "|c00EDED12Not a single-player mode. Initialization of the Anti-Cheat system was canceled.|r",
error = "|c00FF0000Error when trying to access Edit Box.\r\nThe Anti-Cheat system was not enabled.|r",
detect = "|c00FF0000\"CHEATCODE\" cheat code detected.|r",
disabled = "|c00EDED12Anti-Cheat system was disabled.|r",
destroyed = "|c00EDED12Anti-Cheat system was removed from game.|r"
}

-- “Impossible” index for nil-frame comparison
-- (do not use this index for real frames or change it if you need)
AntiCheat.nullFrameIndex = 93242

AntiCheat.states = {}

---@param debugMode boolean output debug messages
function AntiCheat:init(debugMode)
    self.debug = debugMode

    if not ReloadGameCachesFromDisk() then
        self:_displayMessage("cancel")
        return
    end

    local saveLoadedTrigger, textChangedTrigger, textEnteredTrigger = CreateTrigger(), CreateTrigger(), CreateTrigger()
    local states, cheats = self.states, self.list

TriggerRegisterGameEvent(saveLoadedTrigger, EVENT_GAME_LOADED)
    TriggerAddCondition(saveLoadedTrigger, Condition(function() self:_setBoxEvents() end))

    TriggerAddCondition(textChangedTrigger, Condition(function()
        states[2] = states[1]
        states[1] = BlzGetTriggerFrameText()
    end))

    TriggerAddCondition(textEnteredTrigger, Condition(function()
        local enteredText = states[2]:lower()

        for i = 1, #cheats do
            if enteredText:find(cheats[i]) then
                self:_punish(cheats[i])
                break
            end
        end
    end))

    self.triggers = {textChangedTrigger, textEnteredTrigger, saveLoadedTrigger}
    self:_setBoxEvents()
end

function AntiCheat:_setBoxEvents()
    -- Event registration has been moved to a separate function,
    -- since when loading a saved game it will need to be done again.

    local t = CreateTimer()
    -- Accessing to the frame while the map is initializing can result in a fatal error, so delay is needed

    TimerStart(t, .1, false, function()
        local eBox = self:_getEditBox()

        if eBox then
            BlzTriggerRegisterFrameEvent(self.triggers[1], eBox, FRAMEEVENT_EDITBOX_TEXT_CHANGED)
            BlzTriggerRegisterFrameEvent(self.triggers[2], eBox, FRAMEEVENT_EDITBOX_ENTER)
            self:_displayMessage("success")
        else
            self:_displayMessage("error")
            self:destroy()
        end

        DestroyTimer(t)
    end)
end

function AntiCheat:_getEditBox()
    -- Includes frame access check
    local index = self.nullFrameIndex

local nullFrame = BlzGetOriginFrame(ConvertOriginFrameType(index), 0)
    local gameUI = BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0)
    if gameUI == nullFrame then return nil end

    nullFrame = BlzFrameGetChild(gameUI, index)
    local msgFrame = BlzFrameGetChild(gameUI, 11)
    if msgFrame == nullFrame then return nil end

    local editBox = BlzFrameGetChild(msgFrame, 1)
    if editBox == nullFrame then return nil end

    return editBox
end

function AntiCheat:_displayMessage(mType)
    if not self.debug then return end

    if self.messages[mType] then
        print(self.messages[mType])
        return
    end

    for _, v in ipairs(self.list) do
        if v == mType then
            local message = self.messages.detect:gsub("CHEATCODE", mType)
            print(message)
            return
        end
    end
end

---@param enable boolean
function AntiCheat:setEnable(enable)
    local triggers = self.triggers
if not triggers then return end

    if enable then
        EnableTrigger(triggers[2])
        self:_displayMessage("success")
        return
    end

    DisableTrigger(triggers[2])
    self:_displayMessage("disabled")
end

function AntiCheat:destroy()
    local triggers = self.triggers

if triggers then
        for _, v in ipairs(triggers) do
            DisableTrigger(v)
            DestroyTrigger(v)
        end
    end

    self.triggers = nil
    self:_displayMessage("destroyed")
end

Links​

List of cheat codes
The Big UI-Frame Tutorial
Previous version
Contents

Anti-cheat for single player (Map)

Reviews
Antares
All issues were addressed. Works perfectly. A great system for singleplayer maps. Approved
The system works well and the code is well written. I didn't actually know it was possible to detect cheat codes this way.

Just a few small things:
  • You're detecting if a cheat code is a substring of the entered string. I don't see the reason for this, you could just do if enteredText == cheats[i] then.
  • The system should disable itself automatically in multiplayer. I have both singleplayer highscore hunt and multiplayer in my map. Paired with point 1, there could be a siutation where one player asks "What's the cheat code for infinite mana again?" "It's thereisnospoon I think", someone replies and gets booted from the game.
  • A clearer delimitation of the configurable parts/API and some documentation at the top of your script file would be appreciated.
  • You can use the Total Initialization library as the way to init your library, if you want to. We're setting this as the standard for all Lua resources on hive.
  • You're stating "Switch the map to lua mode if not already." in your description. This is obvious to everyone who knows what this means and you shouldn't recommend this to those that don't. You can probably remove this.

Awaiting Update
 
  • You're detecting if a cheat code is a substring of the entered string. I don't see the reason for this, you could just do if enteredText == cheats[i] then.
I think that string equality check is not enough, since cheats can be entered with spaces at the beginning (like the string " warpten" is a valid cheat code input). Also, some cheats have arbitrary integer arguments, like "greedisgood 1234". It seems that string.find() is the easiest way to check for a cheat code in an entered string, although I could make a smarter string parser, but that would be a bit overengineering.

As for the other points, I'll try to update code later.
 
I think that string equality check is not enough, since cheats can be entered with spaces at the beginning (like the string " warpten" is a valid cheat code input). Also, some cheats have arbitrary integer arguments, like "greedisgood 1234". It seems that string.find() is the easiest way to check for a cheat code in an entered string, although I could make a smarter string parser, but that would be a bit overengineering.
You're correct, I retract that point.
 
Great system. Too bad it's in LUA and there is the limitation of Warcraft, which makes you choose between LUA and JASS. Is there a possibility of a JASS version?

My map has a lot of great systems in JASS, and I wouldn't want to give them up.
 
Great system. Too bad it's in LUA and there is the limitation of Warcraft, which makes you choose between LUA and JASS. Is there a possibility of a JASS version?
This should be possible, except that it would be necessary to implement analogies of string.lower() and string.find() functions for string processing.

Unfortunately I've never coded in jass, so it's a little out of my competence, hope someone else will do it.
 
This should be possible, except that it would be necessary to implement analogies of string.lower() and string.find() functions for string processing.

Unfortunately I've never coded in jass, so it's a little out of my competence, hope someone else will do it.
I understand. In any case, your system is a great contribution.
 
Back
Top