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() function.

Setting up​

1. Use the AntiCheat.debug = true/false property to enable/disable messages.
2. 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.
3. The AnitCheat.list table stores a list of cheat codes, which can be changed if necessary.
4. The AntiCheat.setEnable(true/false) function allows you to enable/disable the system if necessary.

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

    Configuration:
    — Use the AntiCheat.debug boolean property to enable/disable debug messages (enabled by default).

    — You can change AntiCheat.punish(cheat) function to your liking to create any reaction to entering a cheat code.
    This function takes a cheat code string as an argument, which allows you to customize the reaction for each cheat code.

    — The AntiCheat.list table stores a list of cheat codes, you can remove any from there or add something if necessary.

    Control:
    — To initialize the system, you need to call the AntiCheat.init() method.
    I would not recommend calling this function too early, since the code must access some default frames that may not have time to initialize, which will lead to a game crash.
    I took this problem into account and tried to make it more secure by adding a delay timer and checking for access to frames.
    After that, I injected the function into main() without any problems in my tests, but I still think I should warn you about it.

    — To destroy the system, you can call the AntiCheat.destroy() function.

    — To temporarily disable the system, you can use the AntiCheat.setEnable(false) function.
    Accordingly, to enable triggers later, you should call AntiCheat.setEnable(true).
--]]

AntiCheat = {}
AntiCheat.debug = true

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

    AntiCheat.displayMessage(cheat)
    CustomDefeatBJ(GetLocalPlayer(), "Good luck next time!")
end

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

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

AntiCheat.states = {}

function AntiCheat.init()
    if not ReloadGameCachesFromDisk() then
        AntiCheat.displayMessage("cancel")
        return
    end
    local saveLoadedTrigger, textChangedTrigger, textEnteredTrigger = CreateTrigger(), CreateTrigger(), CreateTrigger()
    local states, cheats, punish = AntiCheat.states, AntiCheat.list, AntiCheat.punish

TriggerRegisterGameEvent(saveLoadedTrigger, EVENT_GAME_LOADED)
    TriggerAddCondition(saveLoadedTrigger, Condition(AntiCheat.setBoxEvents))

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

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

        for i = 1, #cheats do
            if string.find(enteredText, cheats[i]) then
                punish(cheats[i])
                break
            end
        end
    end))

    AntiCheat.triggers = {textChangedTrigger, textEnteredTrigger, saveLoadedTrigger}
    AntiCheat.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, ac = CreateTimer(), AntiCheat
    -- Accessing to the frame while the map is initializing can result in a fatal error, so delay is needed

    TimerStart(t, .2, false, function()
        local eBox = ac.getEditBox()

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

        DestroyTimer(t)
    end)
end

function AntiCheat.setEnable(enable)
    local triggers = AntiCheat.triggers
if not triggers then return end

    if enable then
        EnableTrigger(triggers[2])
        return
    end
    DisableTrigger(triggers[2])
end

function AntiCheat.getEditBox()
    -- Includes frame access checks

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

    nullFrame = BlzFrameGetChild(gameUI, 93242);
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 AntiCheat.debug then
        if AntiCheat[mType] then
            print(AntiCheat[mType])
            return
        end

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

function AntiCheat.destroy()
    local triggers = AntiCheat.triggers
if triggers then
        for _, v in ipairs(triggers) do
            DestroyTrigger(v)
        end
    end
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.
 
Level 5
Joined
Oct 31, 2011
Messages
91
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.
 
Level 5
Joined
Oct 31, 2011
Messages
91
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.
 
Top