Gotchas & Best Practices
Key rules and common pitfalls when writing in-process game thread Lua scripts.
1. Game Thread Execution & Frame Drops
Section titled β1. Game Thread Execution & Frame Dropsβ- No synchronous network I/O: Never call blocking HTTP or unzip operations inside
renderorcreatemove. - Pre-cache assets: Load fonts, images (
render.load_image), and SVGs during script init rather than every frame. - Limit raycasts: Keep trace rays and bullet penetration calls within reasonable limits.
2. Guarding nil Entity Handles
Section titled β2. Guarding nil Entity HandlesβAccessing fields on invalid or dead entities can crash the game engine:
-- BAD: Crashes if local player is dead/nillocal origin = entity.get_local_player():get_abs_origin()
-- GOOD: Validate entity handles firstlocal me = entity.get_local_player()if not me or not me:is_alive() then returnendlocal origin = me:get_abs_origin()3. render.* Is Valid Only Inside render
Section titled β3. render.* Is Valid Only Inside renderβAll drawing functions (render.text, render.rect, render.line, etc.) are valid only inside the render callback.
Calling render methods from createmove or game_events does nothing and risks graphics buffer desynchronization.
4. Netprop Arrays Are 0-Based & Unbounded
Section titled β4. Netprop Arrays Are 0-Based & UnboundedβNetprop array wrappers provide raw pointer access:
- Index from
0tosize - 1. - The
#length operator does not return array length. - Indexing past the bounds reads raw memory.
5. set() Does Not Trigger Callbacks
Section titled β5. set() Does Not Trigger CallbacksβCalling el:set(val) programmatically does not invoke callbacks registered via el:set_callback (except for combos). If you update a widget in code, invoke your state handler manually.
6. Explicit Returns in draw_chams
Section titled β6. Explicit Returns in draw_chamsβ- Return
0: Script takes over rendering (requiresmaterials.draw_chams). - Return
1: Engine draws the model normally. - Omitting
returnevaluates to0, hiding the model.
7. JSON Userdata Limitations
Section titled β7. JSON Userdata Limitationsβjson.stringify serializes userdata (vector, qangle, color) as null. Convert them to plain tables first:
local pos = me:get_abs_origin()local str = json.stringify({ x = pos.x, y = pos.y, z = pos.z })8. Widget Keys & Renaming
Section titled β8. Widget Keys & RenamingβSaved widget configs in scripts/data/<script>.cfg map to Groupbox + Widget Name + Type.
Renaming a widget or moving it to another groupbox resets its saved value.
