Old School Ops

Client Scripting

Writing Lua macros for the GoMMO client, and deciding whether your server accepts them.

Every game · updated 2026-09-11

The GoMMO client runs Lua scripts. A script drives your character through the same actions you drive it through: walk, fight, pick things up, wear things, cast. It works with a window open and it works with no window at all.

Two people read this page. If you play, the first half is how to write and run one. If you run a server, Refusing scripted clients is the setting you want.

Where scripts live

Beside the client executable, in scripts/:

GoMMO_RF.exe
scripts/
  default/        our scripts. Do not edit these; copy one instead
  farm.lua        yours. Any name, any subfolder

The folder ships with the client. Move the executable and move the folder with it: without scripts/default/connect.lua the client cannot log in at all, because logging in is itself one of those scripts.

Your own scripts go anywhere under scripts/ except default/. That folder is replaced when you update the client.

Running one

GoMMO_RF.exe -script farm
GoMMO_RF.exe -list-scripts
GoMMO_RF.exe -verbs
GoMMO_RF.exe -verb inventory

-script takes a path under scripts/, with or without the .lua. -list-scripts shows what is on disk, ours and yours, with a one line summary of each. -verbs prints everything a script can call, generated from the client itself, so it matches the build you are holding. Neither listing needs a server.

With no -script, a windowed client plays normally and a -headless run executes scripts/default/main.lua.

In the window

While a script runs, it drives your character and your movement and combat keys do nothing. The camera, the zoom, the overlay and the character panels stay yours. A banner in the top right names the script and the key that stops it.

PAUSE stops a running script immediately, wherever it has got to, and the character stops walking with it. Ctrl+Break does the same. Closing the window also stops it.

Writing one

A script owns the whole run, including logging in:

run("default/connect")

local lib = run("default/lib")

lib.equip_best()

local target = lib.nearest_monster()
if target then
  local r = lib.fight(target, 15)
  log(string.format("%d hits for %d damage", r.hits, r.damage))
  lib.loot_nearby()
end

run("default/connect") is the first line of any script that is the whole run. Without it every verb below it finds an empty world.

In the windowed client, leave that line out. The client has already logged you in by the time your script starts, and connect() refuses with a message naming the line to delete.

The two layers

default/lib is the streamlined layer, written in Lua and readable. It hides the traps: re-reading a target that moves, the attack cadence, draining combat events, walking at your character's own speed. Use it unless you need something it does not do.

FunctionDoes
lib.walk_to(target, within, limit)Close the distance and stop
lib.fight(target, seconds)Fight for that long; returns swings, hits, damage, kills
lib.nearest_monster()The closest living thing you may attack
lib.equip_best()Wear the first thing in the bag
lib.loot_nearby()Walk to the nearest drop and take it
lib.alive(e), lib.current(e)Is it alive, and where is it now

Under that is the raw layer, one verb per action: me, entities, nearest, dist, move_to, step_toward, stop, attack, use_skill, vitals, respawn, skills, combat_events, inventory, equipment, equip, unequip, use_item, pick_up, sleep, log, run.

Two of those are easy to confuse. move_to sends one movement request and does not walk anywhere; calling it in a loop restarts the same move from a position the server already has, and the character shuffles. step_toward is the one that travels: call it in a loop with a sleep, and call stop when you arrive.

Asking what the game can do

Ask what a game supports rather than which game it is, and your script survives meeting a new one:

if has_combat() then ... end
if can_set_stance() then set_stance(true) end

has_combat, has_inventory, can_select_target, can_set_stance and equip_slots answer for the game you are connected to. A verb the game cannot do refuses by name rather than doing nothing quietly. game_name() exists, and comparing against it is the thing that goes stale.

Copying a default

Copy the file, then say what you copied in the header:

-- @script  mycombat
-- @version 1
-- @basedon default/combat v4

The client then tells you, once per run, when the original moves past the version you copied. It says so and does nothing else: no merge, no rewrite, no blocked run. -list-scripts marks the same rows.

A script that CALLS a default with run("default/combat") never goes stale, so it needs no @basedon. That is the reason to extend one rather than fork it.

Checks

check(name, function() ... end) records a named result, and pass, fail and skip are the verdicts. A script that never calls check produces no report and no exit code, which is what a macro should do. A script that does can be run from a shell script and its exit code means something.

Refusing scripted clients

A client that a script is driving says so when it logs in. ALLOW_SCRIPTED_CLIENTS in your settings.cfg decides what happens next:

ALLOW_SCRIPTED_CLIENTS    1

It ships set to 1. Set it to 0 and a client that declared a script is refused with a message telling the player to start without one. GTH servers read it under [loginserver], RF servers under [gameserver].

This is a policy, not a protection. The server believes what a client tells it about itself. A modified client that simply says nothing is accepted whatever you set here, and the server has no way to tell the difference. Every client written before the declaration existed says nothing and is allowed either way. It lets you state a rule and hold it against clients that play fair. It is not anti-cheat, it will not stop anybody determined, and setting it to 0 does not mean nobody is scripting on your server.

Something here wrong?

Out of date, incomplete, or assuming something it should not. Documentation errors are bugs, and this one arrives tagged and pointed at this page.

Loading…
Open the full search page