plugin dev guide

Guide for plugin dev guide

Legends of Hastinapur - Plugin Development Guide

Version: 1.0
Last Updated: 2026-01-05
Plugin API Version: 1.0.0

Table of Contents

  1. Introduction
  2. Plugin Modes
  3. Getting Started
  4. Plugin API Reference
  5. Security Model
  6. Best Practices
  7. Examples

Introduction

The Legends of Hastinapur plugin system allows you to create gameplay enhancements using Lua scripts. Plugins can track player statistics, display custom UI, respond to game events, and (in development mode) automate testing scenarios.

What Can Plugins Do?

Production Mode (Release Builds):
  • Read player stats (position, health, skills)
  • Query world state (nearby entities, NPCs)
  • Display UI overlays (tables, progress bars, charts)
  • Subscribe to game events (combat, skills, quests)
  • Store persistent configuration
Development Mode (Debug Builds):
  • All production features PLUS:
  • Teleport player
  • Spawn entities
  • Modify inventory
  • Set skill levels
  • Automated testing capabilities

Plugin Modes

The plugin system operates in two distinct modes for security and safety:

Production Mode

When: Release builds (cargo build --release)
Purpose: Safe, read-only gameplay enhancements
Available APIs: player., world., ui., events., config.*

Development Mode

When: Debug builds (cargo build)
Purpose: Testing, automation, rapid development
Available APIs: All production APIs + automation.*
Mode Detection:
-- Plugins automatically receive the correct API surface
-- No code changes needed between modes

Getting Started

1. Create Plugin Directory

mkdir -p plugins/my_plugin
cd plugins/my_plugin

2. Create plugin.toml Manifest

[plugin]
name = "My Plugin"
version = "1.0.0"
author = "Your Name"
description = "A helpful gameplay enhancement"

[plugin.main]
script = "main.lua"

3. Create main.lua Script

-- Plugin lifecycle hooks
function on_load()
    ui.show_notification("My Plugin loaded!")
end

function on_tick(delta)
    -- Called every frame
    -- delta = time since last frame in seconds
end

function on_unload()
    -- Cleanup before plugin unloads
end

4. Test Your Plugin

cd ../../loh-game
cargo run
# Plugin will auto-load from plugins/ directory

Plugin API Reference

Player API

Access player state and statistics.
-- Get player position
local pos = player.position()
-- Returns: {x, y, z}
print("Player at", pos[1], pos[2], pos[3])

-- Get health
local hp = player.health()
local max_hp = player.max_health()

-- Get username
local name = player.username()

-- Get skill levels
local skills = player.skill_level
for skill_name, level in pairs(skills) do
    print(skill_name, level)
end
-- Example: Fishing=15, Smithing=10, etc.

World API

Query game world state.
-- Get nearby entities
local entities = world.get_nearby_entities()
for id, entity in pairs(entities) do
    -- entity has: name, position, type
    print(entity.name, "at", entity.position)
end

-- Get nearby NPCs
local npcs = world.get_nearby_npcs()
for id, npc in pairs(npcs) do
    print(npc.name, npc.examine)
end

UI API

Display custom UI overlays.
-- Show notification
ui.show_notification("Hello, World!")

-- Draw data table
local headers = {"Stat", "Value"}
local rows = {
    {"Health", tostring(player.health())},
    {"Position", "100, 50, 25"}
}
ui.draw_table(headers, rows)

-- Draw progress bar
ui.draw_progress_bar("XP Progress", 750, 1000)

-- Draw line chart
local data_points = {
    {0, 100},   -- {x, y}
    {1, 150},
    {2, 175}
}
ui.draw_line_chart("DPS Over Time", data_points)

Events API

Subscribe to game events.
-- Listen for combat events
events.on_combat(function(event)
    local event_type = event.event_type  -- "Attack", "Hit", "Death"
    local attacker = event.attacker_id
    local target = event.target_id
    local damage = event.damage or 0
    
    if event_type == "Hit" and attacker == "player" then
        ui.show_notification("Dealt " .. damage .. " damage!")
    end
end)

-- Listen for skill events
events.on_skill(function(event)
    local skill = event.skill         -- "Fishing", "Smithing", etc.
    local xp_gained = event.xp_gained
    local level_after = event.level_after
    
    if xp_gained > 0 then
        ui.show_notification("+" .. xp_gained .. " " .. skill .. " XP")
    end
end)

Quest API

Access quest data.
-- Get active quests
local active = quests.get_active()
for quest_id, progress in pairs(active) do
    print("Quest:", quest_id, "Stage:", progress.stage_id)
end

-- Get completed quests
local completed = quests.get_completed()

-- Get quest definition
local quest_def = quests.get_definition("tutorial_quest")
if quest_def then
    print(quest_def.name)        -- Quest title
    print(quest_def.description) -- Quest description
    -- quest_def.steps = array of objective descriptions
end

Config API

Store persistent plugin data.
-- Save configuration
config.save("my_setting", "value")
config.save("total_kills", tostring(100))

-- Load configuration
local setting = config.load("my_setting")
local kills = tonumber(config.load("total_kills")) or 0

-- Config persists between game sessions

Automation API (Development Mode Only)

⚠️ WARNING: Only available in debug builds. Will error in production.
-- Teleport player
automation.teleport(100.0, 50.0, 25.0)

-- Spawn entity
automation.spawn_entity("goblin", 100.0, 50.0, 30.0)

-- Modify inventory
automation.modify_inventory("bronze_sword", 1)  -- Add 1 bronze sword
automation.modify_inventory("coins", -100)      -- Remove 100 coins

-- Set skill level
automation.set_skill_level("Fishing", 50)

Security Model

Sandbox Restrictions

Lua plugins run in a sandboxed environment with restricted capabilities:
Disabled Lua Functions:
  • os.* - No OS access
  • io.* - No file I/O
  • loadfile, dofile - No arbitrary code loading
  • require - No module loading
  • debug.* - No debug manipulation
Allowed:
  • String manipulation
  • Table operations
  • Math functions
  • Basic Lua logic
  • Plugin API functions

Mode Enforcement

  • Compile-time: Automation APIs only compile in debug builds
  • Runtime: Additional permission checks prevent production access
  • API gating: PluginMode enum enforces capability separation

Best Practices

Performance

  1. Cache API calls:
    -- Bad: Calls API every frame
    function on_tick(delta)
      local pos = player.position()
      ui.draw_table(headers, {{pos[1], pos[2], pos[3]}})
    end
    
    -- Good: Only update when changed
    local last_pos = nil
    function on_tick(delta)
      local pos = player.position()
      if pos ~= last_pos then
          ui.draw_table(headers, {{pos[1], pos[2], pos[3]}})
          last_pos = pos
      end
    end
  2. Limit UI commands: Don't draw excessive UI elements every frame
  3. Use events over polling: Subscribe to events instead of checking state in on_tick

Error Handling

function on_tick(delta)
    local success, error = pcall(function()
        -- Your code here
        local pos = player.position()
    end)
    
    if not success then
        print("Error:", error)
    end
end

Data Persistence

-- Save state on unload
function on_unload()
    config.save("session_xp", tostring(total_xp))
    config.save("start_time", tostring(os.time()))
end

-- Load state on startup
function on_load()
    total_xp = tonumber(config.load("session_xp")) or 0
    start_time = tonumber(config.load("start_time")) or os.time()
end

Examples

See the loh-plugins/ directory for complete examples:

Basic Examples

  • xp_tracker.lua - Track total XP gains with notifications
  • player_info.lua - Display player stats in real-time
  • nearby_entities.lua - Show nearby NPCs and entities

Advanced Examples

  • skill_calculator.lua - Calculate XP/hour and time-to-level
  • combat_stats.lua - Track DPS, hits landed, damage taken
  • loot_tracker.lua - Monitor loot drops and GP/hour
  • quest_helper.lua - Display active quest objectives
  • farming_timer.lua - Track farming patch grow times
  • idle_notifier.lua - Alert when player inactive

Example: Simple XP Tracker

local total_xp = 0

function on_load()
   total_xp = tonumber(config.load("total_xp")) or 0
    ui.show_notification("XP Tracker loaded! Total: " .. total_xp)
    
    events.on_skill(function(event)
        if event.xp_gained > 0 then
            total_xp = total_xp + event.xp_gained
            ui.show_notification("+" .. event.xp_gained .. " XP!")
        end
    end)
end

function on_tick(delta)
    local headers = {"Total XP"}
    local rows = {{tostring(total_xp)}}
    ui.draw_table(headers, rows)
end

function on_unload()
    config.save("total_xp", tostring(total_xp))
end

Troubleshooting

Plugin Doesn't Load

  1. Check plugin.toml is valid TOML format
  2. Verify main.lua exists and has no syntax errors
  3. Check game console for error messages
  4. Ensure plugin directory is in plugins/

API Function Not Found

  • Verify you're calling the correct API (check spelling)
  • Ensure you're using current API (not old deprecated functions)
  • Development-only APIs require debug build

Performance Issues

  • Reduce UI draw calls (cache and update only when changed)
  • Use events instead of polling in on_tick
  • Profile with print() statements to find bottlenecks

Migration from Old API

If you have plugins using the old API format (api.ui.draw_text()), update them to use the current API:

Old API (Deprecated):

api.ui.draw_text(100, 100, "Hello", {1, 1, 1, 1})
api.ui.draw_rect(10, 10, 200, 50, {0, 0, 0, 0.5})
api.events.on("ExperienceGained", callback)

New API (Current):

ui.show_notification("Hello")
ui.draw_table(headers, rows)
events.on_skill(callback)
Benefits of new API:
  • Higher level abstractions
  • No coordinate management needed
  • Type-safe event subscriptions
  • Easier to use and maintain

Support

  • Examples: /loh-plugins/ directory
  • Issues: File bug reports on project repository
  • Community: Share plugins and get help

Happy Plugin Development! 🎮