You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

7.7 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

ARZ Web Helper is a modular web-based control panel for Arizona RP (SA-MP/MoonLoader game mod). It has two layers:

  • Rust core (rust_core/) — an Axum HTTP/WebSocket server compiled as a shared library (libarz_core.so). Exposes a C FFI (rgl_* functions) for the Lua side. Handles HTTP routing, WebSocket event broadcasting, SQLite persistence, and static file serving.
  • Lua framework (http_framework/rgl_framework.lua) — the main MoonLoader script (~1058 lines). Loads the Rust .so via LuaJIT FFI, sets up the framework API, loads modules, and runs the game-thread event loop.

Build Commands

Rust core (cross-compile for Android aarch64)

cd rust_core
cargo build --release --target aarch64-linux-android

The output .so is deployed to the game device at lib/libarz_core.so relative to the MoonLoader working directory.

Rust tests

cd rust_core
cargo test

Tests exist in rust_core/src/db.rs (7 tests covering batch DB operations).

Lua modules

No build step. Lua files are loaded at runtime by MoonLoader from the modules/ directory on the device.

Architecture

Rust ↔ Lua Bridge

The Rust server and Lua game thread communicate through a poll-based bridge (rust_core/src/bridge.rs):

  • Lua → Rust: rgl_push_event() sends game events (SAMP network events) to connected WebSocket clients via broadcast channel (capacity 256).
  • Rust → Lua: HTTP API requests become Lua code strings queued via bridge::request_lua_exec(). Lua's main() loop calls rgl_poll() each frame, executes the code, and returns results via rgl_respond(). API timeout is 2 seconds.
  • All strings crossing the FFI boundary may be Win-1251 encoded (game uses CP1251). The Rust side converts to UTF-8 via encoding_rs.

Module System

Modules live in http_framework/modules/<name>/init.lua (bundled with framework) or more_modules/<name>/init.lua (separate, gitignored). Both are loaded identically at runtime. Each module exports:

  • M.init(fw) — required. Receives the framework API table (namespaced per module).
  • M.render(ui, state) — optional. Provides a UI via the abstract widget builder (works both as web JSON and in-game imgui).
  • M.unload() — optional cleanup.

The framework wraps each module's DB calls with a <module_name>. key prefix for isolation. Modules register API handlers via fw.on_api(module, action, handler) and game event interceptors via fw.on_event(event_name, handler, priority).

See docs/MODULE_GUIDE.md for the full module authoring reference.

Dual UI Rendering

Modules write UI using an abstract ui API (ui.text(), ui.button(), ui.tab_bar(), etc.). Two implementations exist:

  • create_ui_builder() (rgl_framework.lua:129) — records widgets as JSON for the web frontend (/m/<module>/ served via ui_page.html).
  • create_ui_imgui() (rgl_framework.lua:267) — wraps mimgui (ImGui) for in-game overlay rendering.

Modules with render() but no static/ directory get auto-registered as __render__ modules, which are served the generic ui_page.html template.

HTTP Routes (server.rs)

  • GET / — dashboard listing all registered modules
  • GET /admin — admin panel (module management, DB browser, logs)
  • GET /m/<module>/ — module UI (static files or auto-rendered via ui_page.html)
  • POST /api/<module>/<action> — module API endpoint (routed to Lua via bridge)
  • GET /api/modules — JSON list of loaded modules
  • GET /api/commands — JSON list of registered commands
  • GET /ws — WebSocket upgrade for real-time game events

Framework API Quick Reference

Core fw.* methods available to modules:

Method Purpose
fw.log(level, tag, ...) Log message (also broadcasts to WS as __log)
fw.on_api(mod, action, fn) Register HTTP API handler
fw.on_event(name, fn, priority) Hook SA-MP event (return false to block)
fw.command(name, fn) Register /name chat command
fw.db_get(key) / fw.db_set(key, val) Single key DB ops (auto-prefixed)
fw.db_get_many(keys) / fw.db_set_many(kv) Batch DB ops
fw.db_get_prefix(prefix) Get all keys with prefix
fw.timer_once(ms, fn) One-shot timer, returns cancel function
fw.chat(text) Send chat message/command
fw.add_chat_message(text, color) Local-only chat message
fw.respond_dialog(id, btn, list, input) Respond to server dialog
fw.unlock_dialog() Send /mm + auto-close to unblock dialog
fw.json_encode(t) / fw.json_decode(s) JSON serialization (cjson)
fw.to_utf8(s) / fw.to_win1251(s) Encoding conversion
fw.register_module(name, dir) Register static file serving
fw.push_event(name, json) Broadcast custom event to WS

UI Widget Types

Available in render(ui, state):

Widget Signature Returns
text ui.text(str)
text_colored ui.text_colored(r,g,b,a, str)
separator ui.separator()
spacing ui.spacing()
sameline ui.sameline()
button ui.button(label) true if clicked
input ui.input(label, value) new string value
input_int ui.input_int(label, value) new int value
checkbox ui.checkbox(label, bool) new bool
slider_int ui.slider_int(label, val, min, max) new int
slider_float ui.slider_float(label, val, min, max) new float
combo ui.combo(label, idx, items) new index (0-based)
progress ui.progress(fraction, label)
collapsing ui.collapsing(label) true (call collapsing_end())
tab_bar ui.tab_bar(id) true
tab_item ui.tab_item(label) true (call tab_end())

Key Conventions

  • Game chat commands: short names without prefix for modules (/btc, /btcc), rgl_ prefix for core (/rgl_admin).
  • Dialog IDs are hardcoded SA-MP server dialog identifiers (e.g., 7238, 25244). Handlers return false to hide dialog from player.
  • Event interceptors sorted by priority (lower = earlier). Return false to block event propagation.
  • DB operations support async batching via rgl_db_submit()/rgl_db_poll() for bulk reads/writes.
  • Win-1251 Cyrillic matching in dialog text uses hex escapes (e.g. \xc2\xea\xeb\xfe\xf7 for "Включ").

Key Files

File Lines Purpose
http_framework/rgl_framework.lua ~1058 Main framework: init, module loading, UI builders, event hooks, admin panel
rust_core/src/lib.rs ~169 FFI exports (all rgl_* functions)
rust_core/src/server.rs ~306 Axum HTTP/WS server, routing, static files
rust_core/src/bridge.rs ~149 Lua↔Rust request/response bridge + event broadcast
rust_core/src/db.rs ~397 SQLite key-value store with async batch API
rust_core/src/events.rs ~77 Win-1251↔UTF-8 conversion, SA-MP color parsing
rust_core/src/logging.rs ~45 File + WS broadcast logging
rust_core/static/ui_page.html ~293 Generic auto-UI renderer for modules with render()
rust_core/static/index.html ~55 Dashboard page

Known Issues

  • .gitignore is minimal — missing target/, *.so, *.log, rgl_data.db
  • Broadcast channel (capacity 256) silently drops events when full
  • Fallback JSON encoder only handles flat {"key":"value"} — cjson must be present

Additional Documentation

  • docs/MODULE_GUIDE.md — complete module authoring reference with examples
  • docs/LIB_REFERENCE.md — analysis of available standard libraries (../lib)
  • docs/TASKS.md — prioritized improvement backlog