From 2b4e923e5dcee95b1c809d07d960052e35d00557 Mon Sep 17 00:00:00 2001 From: Richard Ward Date: Tue, 30 Dec 2025 19:42:51 +0000 Subject: [PATCH] update: packages,lua,retro (4 files) --- .../seed/scripts/lua/retro_combos.lua | 95 ++++++++++ .../seed/scripts/lua/retro_input.lua | 169 ++++++++++++++++++ .../permissions/tests/check_access.test.lua | 125 +++++++++++++ .../workflow_editor/seed/scripts/editor.lua | 6 +- 4 files changed, 393 insertions(+), 2 deletions(-) create mode 100644 packages/media_center/seed/scripts/lua/retro_combos.lua create mode 100644 packages/media_center/seed/scripts/lua/retro_input.lua create mode 100644 packages/shared/seed/scripts/permissions/tests/check_access.test.lua diff --git a/packages/media_center/seed/scripts/lua/retro_combos.lua b/packages/media_center/seed/scripts/lua/retro_combos.lua new file mode 100644 index 000000000..4fb6eac2c --- /dev/null +++ b/packages/media_center/seed/scripts/lua/retro_combos.lua @@ -0,0 +1,95 @@ +--[[ + Libretro combo and motion inputs + Complex input sequences and fighting game motions +]] + +local input = require("lua.retro_input") + +---@class RetroCombosModule +local M = {} + +-- ============================================================================ +-- Combo/Macro System +-- ============================================================================ + +---Execute a sequence of inputs +---@param session_id string Session identifier +---@param inputs ComboInput[] Array of input specifications +---@param player? number Player number (default 0) +function M.combo(session_id, inputs, player) + for _, input_spec in ipairs(inputs) do + if input_spec.button then + input.tap(session_id, input_spec.button, player, input_spec.duration_ms) + elseif input_spec.analog then + input.flick_analog(session_id, input_spec.analog.stick, + input_spec.analog.x, input_spec.analog.y, input_spec.duration_ms, player) + end + if input_spec.wait_after_ms then + sleep(input_spec.wait_after_ms) + end + end +end + +-- ============================================================================ +-- Fighting Game Motions +-- ============================================================================ + +--- Common fighting game motions +M.MOTION = { + -- Quarter circle forward (↓↘→) + QCF = function(session_id, player) + input.tap(session_id, "down", player, 30) + input.diagonal(session_id, "right", "down", 30, player) + input.tap(session_id, "right", player, 30) + end, + + -- Quarter circle back (↓↙←) + QCB = function(session_id, player) + input.tap(session_id, "down", player, 30) + input.diagonal(session_id, "left", "down", 30, player) + input.tap(session_id, "left", player, 30) + end, + + -- Dragon punch / Shoryuken (→↓↘) + DP = function(session_id, player) + input.tap(session_id, "right", player, 30) + input.tap(session_id, "down", player, 30) + input.diagonal(session_id, "right", "down", 30, player) + end, + + -- Half circle forward (←↙↓↘→) + HCF = function(session_id, player) + input.tap(session_id, "left", player, 30) + input.diagonal(session_id, "left", "down", 30, player) + input.tap(session_id, "down", player, 30) + input.diagonal(session_id, "right", "down", 30, player) + input.tap(session_id, "right", player, 30) + end, + + -- Charge back then forward + CHARGE_BF = function(session_id, charge_ms, player) + charge_ms = charge_ms or 800 + input.hold_direction(session_id, "left", charge_ms, player) + input.tap(session_id, "right", player, 30) + end, + + -- 360 motion (for grapplers) + CIRCLE = function(session_id, player) + local dirs = {"right", "down", "left", "up"} + for _, dir in ipairs(dirs) do + input.tap(session_id, dir, player, 20) + end + end, +} + +---Execute a motion then press a button +---@param session_id string Session identifier +---@param motion function Motion from MOTION table +---@param button string Button to press after motion +---@param player? number Player number (default 0) +function M.special_move(session_id, motion, button, player) + motion(session_id, player) + input.tap(session_id, button, player) +end + +return M diff --git a/packages/media_center/seed/scripts/lua/retro_input.lua b/packages/media_center/seed/scripts/lua/retro_input.lua new file mode 100644 index 000000000..54325e9cb --- /dev/null +++ b/packages/media_center/seed/scripts/lua/retro_input.lua @@ -0,0 +1,169 @@ +--[[ + Libretro input handling + Button presses, analog sticks, combos, and fighting game motions +]] + +local constants = require("lua.retro_constants") + +---@class RetroInputModule +local M = {} + +-- Re-export constants for convenience +M.BUTTON = constants.BUTTON +M.AXIS = constants.AXIS + +-- ============================================================================ +-- Basic Input Functions +-- ============================================================================ + +---Press and release a button (tap) +---@param session_id string Session identifier +---@param button string Button from BUTTON table +---@param player? number Player number (default 0) +---@param duration_ms? number Hold duration in ms (default 50) +function M.tap(session_id, button, player, duration_ms) + player = player or 0 + duration_ms = duration_ms or 50 + + M.press(session_id, button, player) + sleep(duration_ms) + M.release(session_id, button, player) +end + +---Press a button (hold down) +---@param session_id string Session identifier +---@param button string Button from BUTTON table +---@param player? number Player number (default 0) +function M.press(session_id, button, player) + player = player or 0 + http.post("/api/v1/retro/sessions/" .. session_id .. "/input", { + player = player, + button = button, + pressed = true + }) +end + +---Release a button +---@param session_id string Session identifier +---@param button string Button from BUTTON table +---@param player? number Player number (default 0) +function M.release(session_id, button, player) + player = player or 0 + http.post("/api/v1/retro/sessions/" .. session_id .. "/input", { + player = player, + button = button, + pressed = false + }) +end + +---Release all buttons for a player +---@param session_id string Session identifier +---@param player? number Player number (default 0) +function M.release_all(session_id, player) + player = player or 0 + for _, btn in pairs(M.BUTTON) do + M.release(session_id, btn, player) + end +end + +-- ============================================================================ +-- Direction Shortcuts +-- ============================================================================ + +---Tap UP button +---@param session_id string Session identifier +---@param player? number Player number (default 0) +function M.up(session_id, player) + M.tap(session_id, M.BUTTON.UP, player) +end + +---Tap DOWN button +---@param session_id string Session identifier +---@param player? number Player number (default 0) +function M.down(session_id, player) + M.tap(session_id, M.BUTTON.DOWN, player) +end + +---Tap LEFT button +---@param session_id string Session identifier +---@param player? number Player number (default 0) +function M.left(session_id, player) + M.tap(session_id, M.BUTTON.LEFT, player) +end + +---Tap RIGHT button +---@param session_id string Session identifier +---@param player? number Player number (default 0) +function M.right(session_id, player) + M.tap(session_id, M.BUTTON.RIGHT, player) +end + +---Hold a direction +---@param session_id string Session identifier +---@param direction string "up", "down", "left", "right" +---@param duration_ms number How long to hold in milliseconds +---@param player? number Player number (default 0) +function M.hold_direction(session_id, direction, duration_ms, player) + M.press(session_id, direction, player) + sleep(duration_ms) + M.release(session_id, direction, player) +end + +---Move in a diagonal direction +---@param session_id string Session identifier +---@param horizontal string "left" or "right" +---@param vertical string "up" or "down" +---@param duration_ms? number Hold duration (default 50) +---@param player? number Player number (default 0) +function M.diagonal(session_id, horizontal, vertical, duration_ms, player) + duration_ms = duration_ms or 50 + M.press(session_id, horizontal, player) + M.press(session_id, vertical, player) + sleep(duration_ms) + M.release(session_id, horizontal, player) + M.release(session_id, vertical, player) +end + +-- ============================================================================ +-- Analog Stick Input +-- ============================================================================ + +---Set analog stick position +---@param session_id string Session identifier +---@param stick string "left" or "right" +---@param x number -1.0 to 1.0 (left to right) +---@param y number -1.0 to 1.0 (up to down) +---@param player? number Player number (default 0) +function M.set_analog(session_id, stick, x, y, player) + player = player or 0 + http.post("/api/v1/retro/sessions/" .. session_id .. "/input/analog", { + player = player, + stick = stick, + x = math.max(-1.0, math.min(1.0, x)), + y = math.max(-1.0, math.min(1.0, y)) + }) +end + +---Center (release) analog stick +---@param session_id string Session identifier +---@param stick string "left" or "right" +---@param player? number Player number (default 0) +function M.center_analog(session_id, stick, player) + M.set_analog(session_id, stick, 0, 0, player) +end + +---Move analog stick in a direction and return to center +---@param session_id string Session identifier +---@param stick string "left" or "right" +---@param x number -1.0 to 1.0 horizontal axis +---@param y number -1.0 to 1.0 vertical axis +---@param duration_ms? number How long to hold (default 100) +---@param player? number Player number (default 0) +function M.flick_analog(session_id, stick, x, y, duration_ms, player) + duration_ms = duration_ms or 100 + M.set_analog(session_id, stick, x, y, player) + sleep(duration_ms) + M.center_analog(session_id, stick, player) +end + +return M diff --git a/packages/shared/seed/scripts/permissions/tests/check_access.test.lua b/packages/shared/seed/scripts/permissions/tests/check_access.test.lua new file mode 100644 index 000000000..c1d710c0b --- /dev/null +++ b/packages/shared/seed/scripts/permissions/tests/check_access.test.lua @@ -0,0 +1,125 @@ +-- Tests for permission access checking +-- Tests check_access function with various scenarios + +local checkAccess = require("permissions.check_access") + +describe("Permission Access Checking", function() + + describe("Basic access checks", function() + it("should allow access when user level meets requirement", function() + local result = checkAccess.check_access(3, { + enabled = true, + minLevel = 2 + }) + assert.is_true(result.allowed) + assert.is_nil(result.reason) + end) + + it("should deny access when user level is too low", function() + local result = checkAccess.check_access(1, { + enabled = true, + minLevel = 3 + }) + assert.is_false(result.allowed) + assert.equals("Insufficient permission level", result.reason) + assert.equals(3, result.requiredLevel) + end) + + it("should deny access when resource is disabled", function() + local result = checkAccess.check_access(5, { + enabled = false, + minLevel = 1 + }) + assert.is_false(result.allowed) + assert.equals("Resource is currently disabled", result.reason) + end) + end) + + describe("Database requirements", function() + it("should allow access when database is enabled and required", function() + local result = checkAccess.check_access(3, { + enabled = true, + minLevel = 2, + databaseRequired = true + }, {}, true) + assert.is_true(result.allowed) + end) + + it("should deny access when database is disabled but required", function() + local result = checkAccess.check_access(3, { + enabled = true, + minLevel = 2, + databaseRequired = true + }, {}, false) + assert.is_false(result.allowed) + assert.equals("Database is required but not enabled", result.reason) + end) + + it("should allow access when database is disabled and not required", function() + local result = checkAccess.check_access(3, { + enabled = true, + minLevel = 2 + }, {}, false) + assert.is_true(result.allowed) + end) + end) + + describe("Feature flags", function() + it("should allow access when all required flags are enabled", function() + local result = checkAccess.check_access(3, { + enabled = true, + minLevel = 2, + featureFlags = {"flag1", "flag2"} + }, { + flag1 = true, + flag2 = true + }) + assert.is_true(result.allowed) + end) + + it("should deny access when required flag is missing", function() + local result = checkAccess.check_access(3, { + enabled = true, + minLevel = 2, + featureFlags = {"flag1", "flag2"} + }, { + flag1 = true, + flag2 = false + }) + assert.is_false(result.allowed) + assert.equals("Required feature flag 'flag2' is not enabled", result.reason) + end) + + it("should allow access when no flags are required", function() + local result = checkAccess.check_access(3, { + enabled = true, + minLevel = 2 + }, {}) + assert.is_true(result.allowed) + end) + end) + + describe("Combined checks", function() + it("should pass all checks when all requirements are met", function() + local result = checkAccess.check_access(5, { + enabled = true, + minLevel = 4, + databaseRequired = true, + featureFlags = {"advanced_mode"} + }, { + advanced_mode = true + }, true) + assert.is_true(result.allowed) + end) + + it("should fail on first check that doesn't pass", function() + local result = checkAccess.check_access(3, { + enabled = false, + minLevel = 4, + databaseRequired = true + }, {}, true) + assert.is_false(result.allowed) + assert.equals("Resource is currently disabled", result.reason) + end) + end) +end) diff --git a/packages/workflow_editor/seed/scripts/editor.lua b/packages/workflow_editor/seed/scripts/editor.lua index fd69ca015..6a958340e 100644 --- a/packages/workflow_editor/seed/scripts/editor.lua +++ b/packages/workflow_editor/seed/scripts/editor.lua @@ -1,4 +1,6 @@ --- This file has been split into smaller modules in editor/ --- Use require("editor") or the individual submodules instead +--- This file has been split into smaller modules in editor/ +--- Use require("editor") or the individual submodules instead +---@module editor +---@deprecated Use editor.init instead return require("editor.init")