2019 - Present2.0 Rewrite Active

Tabletop RPG Toolset

Custom Lua/XML Engine & Game Automation Suite for Tabletop Simulator

Source Repositories
Tabletop Simulator Scripts
github.com/Zavian/Tabletop-Simulator-Scripts-2.0
LuaXMLTabletop Simulator APIState MachinesEvent Listeners

Digital tabletop simulators provide physics and 3D space, but lack native mechanics for complex roleplaying systems. In 2019, I started building a dedicated Lua/XML automation system to eliminate manual record-keeping and speed up gameplay.

Over five years of continuous weekly playtesting, the toolset has evolved from basic dice and modifier scripts into an event-driven framework featuring condition tracking, dynamic turn queues, and combat resolvers.

I am currently developing a complete '2.0' architectural rewrite. The new iteration decouples game rules from Tabletop Simulator's proprietary UI runtime via a modular state-machine pattern, making it straightforward to adapt from D&D 5e to Daggerheart and custom rulesets.

Event-Driven Bus Architecture

Centralized event dispatcher managing decoupled subsystem communications, global hooks registration via Global.call, and isolated module execution.

Multi-Ruleset Abstraction

Decoupled rule evaluation engine allowing support for both traditional D&D 5e mechanics and modern dual-dice narrative systems like Daggerheart without rewriting UI layers.

--- @class MonsterData
--- @field name string
--- @field hp string
--- @field ac string
--- @field mov string
--- @field size string
--- @field side string
--- @field image string (optional)

local Events = {}

-- Dictionary for possible events
Events.EVENT_NAMES = {
    parse_monster_data = "parse_monster_data",
    create_json_note = "create_json_note",

    -- in development
    player_hp_update = "player_hp_update",
    monster_hp_update = "monster_hp_update",
    monster_stress_update = "monster_stress_update",
    ruin_update = "ruin_update",
    on_roll = "on_roll",
    create_monster = "create_monster"
}



--- Subscribes to an event.
--- @param eventName string The name of the event.
--- @param func string The callback function name.
--- @overload fun(eventName: "parse_monster_data", func: fun(data: MonsterData))
--- @overload fun(eventName: "create_json_note", func: fun(data: MonsterData))
function Events.subscribe(eventName, func)
    Global.call("event_subscribe", {eventName = eventName, guid = self.getGUID(), functionName = func})
end

--- Broadcasts an event.
--- @param eventName string The name of the event.
--- @param ... any The event data.
--- @overload fun(eventName: "parse_monster_data", data: MonsterData)
--- @overload fun(eventName: "create_json_note", data: MonsterData)
function Events.broadcast(eventName, ...)
    Global.call("event_broadcast", {eventName = eventName, args = {...}})
end

return Events

Technical Challenges & Solutions

Network Race Conditions: Resolving simultaneous player actions on shared game objects without crashing the host Lua sandbox.
Memory & Garbage Collection: Optimizing recurring update loops and event listeners to prevent frame drops in physics-heavy 3D scenes.
System Flexibility: Abstracting core combat and inventory mechanics so new rulebooks (like Daggerheart's Hope/Fear dice mechanics) can be plugged in without rewriting the UI.