diff --git a/README.md b/README.md index decaa64..c60ceca 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ callback, foot-body-orientation ("tactical twist"), scale-deformation walk cycle, and inheritance-decoupled foot sprites. End-to-end inventory pickup/drop demo: pick up a rock into a backpack container, drop it back into the world. -**Version:** 0.10.0 +**Version:** 0.11.0 **Module-ID:** vagrant-skeleton -**Requires:** lib-core.input >=0.4.0, lib-core.camera >=0.3.0, lib-core.render >=0.1.0, lib-core.maps >=0.1.2, lib-core.puppet >=0.4.4, lib-core.interaction >=0.1.0, lib-core.composition >=0.2.0, lib-core.actor >=0.1.0, lib-core.inventory-list >=0.1.0, lib-core.panel >=0.1.0, lib-core.inventory-list-display >=0.1.0, lib-core.notify >=0.1.0, lib-core.notify-display >=0.1.0, lib-core.world-overlay >=0.1.0, lib-asset.prototype-subterrain >=0.1.0 +**Requires:** lib-core.input >=0.4.0, lib-core.camera >=0.3.0, lib-core.render >=0.1.0, lib-core.maps >=0.1.2, lib-core.puppet >=0.4.4, lib-core.interaction >=0.1.0, lib-core.composition >=0.3.0, lib-core.actor >=0.1.0, lib-core.inventory-list >=0.1.0, lib-core.panel >=0.1.0, lib-core.inventory-list-display >=0.1.0, lib-core.notify >=0.1.0, lib-core.notify-display >=0.1.0, lib-core.world-overlay >=0.1.0, lib-core.crafting >=0.1.0, lib-core.crafting-display >=0.1.0, lib-asset.prototype-subterrain >=0.1.0 **Tags:** test-chamber, puppet, walking-sim, interaction ## Topology @@ -23,25 +23,31 @@ demo: pick up a rock into a backpack container, drop it back into the world. - **W A S D** — direct movement (4-way, normalized for diagonals) - **Shift + WASD** — sprint (2x player speed + 2x walk-anim cadence) - **Mouse** — body + head look-at targets (same position, different smoothing) -- **E** — interact: if within range of the rock, picks it up into the backpack +- **E** — interact: if within range of the rock or stick, picks it up into the backpack - **Q** — quick-drop (LIFO): places the most-recently-added backpack item at player position (+30px right) - **Tab** — toggle inventory panel (Backpack contents) -- **Right-Click on item row** (while panel open): context menu with **Drop** / **Inspect** options +- **C** — toggle Crafting panel (lists known recipes; availability dimmed by inventory) +- **Right-Click on item row** (inventory panel): context menu with **Drop** / **Inspect** +- **Right-Click on recipe row** (crafting panel): context menu with **Craft** / **Inspect** - **L** — toggle game-log (chronological list of all events) - **Right-Click on item row → Inspect** — opens a modal Item Details panel (instead of console-only output) - **Pickup (E)** — shows "+1 " floating above the player + toast at top-right + log entry - **Drop (right-click on item → Drop, or Q for LIFO quick-drop)** — toast at top-right + log entry +- **Craft (right-click on recipe → Craft)** — consumes inputs from backpack, adds output, posts a success/failure toast - **ESC** — return to launcher ## Demo Walkthrough -The world contains three notable entities at startup: +The world contains four notable entities at startup: 1. **Player** — spawns at (320, 240), controlled via WASD + mouse. 2. **Backpack** — a visible sprite at (350, 400). Acts as a container (kind="list"). Not directly interactable; it receives items via inventory.add. 3. **Rock** — a standalone stone tile at (270, 400), 80px to the left of the backpack. Tagged "renderable" + "item"; starts visible on the ground. +4. **Stick** — a placeholder pickup-item at (190, 400), 160px to the left of the backpack. + Re-uses the `slot_04_straight` blob_rect_stone tile for its visual (Phase C-Q10 + pragmatic resolution — echtes Stick-Sprite folgt mit dem ersten atlas-baker-decals-Slice). **Pickup flow:** - Walk toward the rock (it sits to the left of the backpack). @@ -115,6 +121,29 @@ Only one rock is spawned per session. ## CHANGELOG +### v0.11.0 — crafting integration + +- **New: stick pickup-item** neben rock am Boden (Position 190,400 — 160px links + vom Backpack). Re-uses `slot_04_straight` aus blob_rect_stone als Visual- + Placeholder (Phase C-Q10). +- **New: 2 Recipes** via `lib-core.crafting`: + - **Stone Pick** = 1 rock + 1 stick → `rock_pick` + - **Rock Pile** = 3 rocks → `rock_pile` +- **New: C-Taste** öffnet ein Crafting-Panel via `lib-core.crafting-display`. + Recipe-Zeilen sind dimmed, wenn inputs im Backpack fehlen. +- **New: Right-Click auf Recipe-Row** → Context-Menu mit **Craft** + **Inspect**. + Craft konsumiert inputs aus dem Backpack und legt das Output-Template als + neues item-Entity in den Backpack. Inspect routet recipe details (name, + description, inputs, output) zum notify-detail-channel. +- **New: 3 item-templates** — `stick`, `rock_pick`, `rock_pile` — jedes mit + player-facing properties (name, description, weight, volume, + composition.). Placeholder atlas-slots: stick=slot_04_straight, + rock_pick=slot_05_tee_open, rock_pile=slot_13_solid. +- **Deps**: +lib-core.crafting 0.1.0, +lib-core.crafting-display 0.1.0. + composition bleibt auf 0.3.0 (gebumpt in Phase C.0). +- **Notify events-Channel**: filter um `"craft"` erweitert, damit Craft- + Erfolgs- und Fehler-Toasts im events-channel landen. + ### v0.10.0 — notification system integration - **Inspect modal**: Inspect action replaced with `notify.post{tags={"inspect"}, ...}` routing to a modal detail-panel (`notify-display` detail-channel). Item name, description, weight, diff --git a/init.lua b/init.lua index beb2689..56be308 100644 --- a/init.lua +++ b/init.lua @@ -1,4 +1,4 @@ --- sporel-module-vagrant-skeleton v0.10.0 +-- sporel-module-vagrant-skeleton v0.11.0 -- Sprite-mode test-chamber: 13-bone humanoid puppet + sprite-tilemap + -- hardcoded furniture. Subterrain-style puppet control model: -- multi-look-target (aim/soft), slerp body rotation, hip-static legs, @@ -41,6 +41,14 @@ -- opens the game-log panel listing all events chronologically. -- engine.print mirror remains enabled — dev tools that parse the log -- continue to work. +-- +-- v0.11.0: Crafting system wired in (lib-core.crafting + +-- lib-core.crafting-display). Stick spawns as a second pickup-item +-- next to the rock. C-key opens a Crafting panel listing the two +-- known recipes (rock_pick = 1 rock + 1 stick, rock_pile = 3 rocks). +-- Right-click a recipe row → Craft + Inspect actions; Craft consumes +-- inputs from the backpack and adds the output; Inspect routes +-- recipe details to a notify-display detail-channel. local input = require("lib-core.input") local camera = require("lib-core.camera") @@ -56,6 +64,8 @@ local inv_display = require("lib-core.inventory-list-display") local notify = require("lib-core.notify") local notify_display = require("lib-core.notify-display") local world_overlay = require("lib-core.world-overlay") +local crafting = require("lib-core.crafting") +local crafting_display = require("lib-core.crafting-display") local M = {} @@ -116,6 +126,65 @@ composition.define_template{ tags = {"renderable", "item"}, } +-- Stick-Template: placeholder pickup-item that joins rock in the world. +-- Re-uses the blob_rect_stone atlas for visual placeholder (per Phase C-Q10 +-- pragmatic resolution — echtes Stick-Sprite kommt mit dem ersten +-- atlas-baker-decals-Slice). slot_04_straight reads as a long horizontal +-- bar — visually distinct from the rock's slot_00_isolated blob. +composition.define_template{ + id = "stick", + properties = { + stack_mode = "individual", + name = "Stick", + description = "A wooden stick. Useful for crafting tool handles.", + weight = 0.3, -- kg + volume = 0.0005, -- L + ["composition.wood"] = 1.0, + sprite_atlas = "", + sprite_uv = {x = 0, y = 0, w = 1, h = 1}, + position = {x = 0, y = 0}, + }, + tags = {"renderable", "item"}, +} + +-- Stone-Pick-Template: output of rock_pick recipe (1 rock + 1 stick). +-- Lives only in the backpack post-craft in v0.11 (no world-drop path yet +-- via the Crafting-Panel). slot_05_tee_open reads as a tool-shaped tile. +composition.define_template{ + id = "rock_pick", + properties = { + stack_mode = "individual", + name = "Stone Pick", + description = "A crude pick for mining stone.", + weight = 2.8, -- rock + stick (real units) + volume = 0.0015, + ["composition.stone"] = 0.9, + ["composition.wood"] = 0.1, + sprite_atlas = "", + sprite_uv = {x = 0, y = 0, w = 1, h = 1}, + position = {x = 0, y = 0}, + }, + tags = {"renderable", "item"}, +} + +-- Rock-Pile-Template: output of 3×rock recipe. slot_13_solid reads as +-- a dense cluster tile — visually distinct from a single rock. +composition.define_template{ + id = "rock_pile", + properties = { + stack_mode = "individual", + name = "Rock Pile", + description = "A stack of rocks bundled together for storage.", + weight = 7.5, -- 3x rock + volume = 0.003, + ["composition.stone"] = 1.0, + sprite_atlas = "", + sprite_uv = {x = 0, y = 0, w = 1, h = 1}, + position = {x = 0, y = 0}, + }, + tags = {"renderable", "item"}, +} + -- Player-Actor-Template (Phase A.6). Vagrant's player is an actor whose -- position drives the puppet (sync each frame). NOT tagged renderable — -- the puppet handles rendering, render.draw_entities doesn't see this @@ -157,6 +226,8 @@ local state = { furniture_uvs = nil, -- name -> { x, y, w, h } from atlas JSON backpack = nil, -- composition-entity, created in M.init after atlas load rock = nil, -- composition-entity (item), created in M.init after blob_rect_stone load + stick = nil, -- composition-entity (item), placeholder for crafting demo + crafting_widget = nil, -- crafting-display widget handle -- Subterrain-style controller state: walk_anim_current = nil, -- "walk_fwd_lower" / "walk_back_lower" / etc. (nil = idle_lower) lower_control_rot_deg = 0, @@ -443,6 +514,45 @@ function M.init(ctx) register_pickup(state.rock) if CI_FRAME_PERF then engine.print("vagrant: rock_spawned") end + -- Stick spawn 80px to the right of the rock (160px left of the backpack + -- on the same Y as rock + backpack). Placeholder visual: slot_04_straight + -- from blob_rect_stone reads as a long horizontal bar (Phase C-Q10 atlas + -- re-use; echtes Stick-Sprite folgt mit atlas-baker-decals). + local STICK_X, STICK_Y = BACKPACK_X - 160, BACKPACK_Y + local stick_uv = stone_uvs["slot_04_straight"] + state.stick = composition.create{ + template = "stick", + properties = { + sprite_atlas = stone_atlas_png, + sprite_uv = {x = stick_uv.x, y = stick_uv.y, + w = stick_uv.w, h = stick_uv.h}, + position = {x = STICK_X, y = STICK_Y}, + }, + } + register_pickup(state.stick) + if CI_FRAME_PERF then engine.print("vagrant: stick_spawned") end + + -- ====== Crafting Recipes ====== + crafting.define_recipe{ + id = "rock_pick", + inputs = { + {template = "rock", count = 1}, + {template = "stick", count = 1}, + }, + output = {template = "rock_pick", count = 1}, + name = "Stone Pick", + description = "A pick for mining stone.", + } + crafting.define_recipe{ + id = "rock_pile", + inputs = { + {template = "rock", count = 3}, + }, + output = {template = "rock_pile", count = 1}, + name = "Rock Pile", + description = "Stack rocks for storage.", + } + -- Inventory-UI Setup panel.set_theme{ panel_width_frac = 0.4, font_size_title = 20 } @@ -498,12 +608,82 @@ function M.init(ctx) panel.register("backpack", backpack_widget) panel.bind_default_trigger("tab", "backpack") + -- ====== Crafting Widget ====== + state.crafting_widget = crafting_display.create(state.backpack, { + title = "Crafting", + widget_id = "crafting", + pause_on_open = false, + ctx_factory = function() + return { actor = state.actor } + end, + }) + + -- Craft action: invokes crafting.craft on the backpack, posts a + -- notify-toast on success or a warn-toast on missing inputs. + crafting_display.register_action(state.crafting_widget, "Craft", + function(recipe_id, c) + local r = crafting.craft(recipe_id, c.container, + { actor = state.actor }) + if r.ok then + local recipe = crafting.get_recipe(recipe_id) + notify.post{ + tags = {"craft", "event"}, + text = "Crafted " .. (recipe.name or recipe_id), + severity = "info", + } + if CI_FRAME_PERF then + engine.print("vagrant: event=craft_ok recipe=" .. recipe_id) + end + else + local msg + if r.error == "missing_inputs" then + msg = "Missing inputs" + else + msg = "Cannot craft: " .. tostring(r.error) + end + notify.warn({"craft", "event"}, msg) + if CI_FRAME_PERF then + engine.print("vagrant: event=craft_fail recipe=" .. recipe_id + .. " error=" .. tostring(r.error)) + end + end + c.close_menu() + end) + + -- Inspect action: routes recipe details to the notify detail-channel. + crafting_display.register_action(state.crafting_widget, "Inspect", + function(recipe_id, c) + local recipe = crafting.get_recipe(recipe_id) + local lines = { + recipe.name or recipe.id, + "", + recipe.description or "", + "", + "Inputs:", + } + for _, inp in ipairs(recipe.inputs) do + lines[#lines + 1] = " " .. inp.template .. " \xc3\x97 " .. inp.count + end + lines[#lines + 1] = "" + lines[#lines + 1] = "Output: " .. recipe.output.template + .. " \xc3\x97 " .. recipe.output.count + notify.post{ + tags = {"craft", "inspect", "detail"}, + text = table.concat(lines, "\n"), + severity = "info", + } + c.close_menu() + end) + + panel.register("crafting", state.crafting_widget) + panel.bind_default_trigger("c", "crafting") + -- ====== Notification System Setup ====== -- Channels (created BEFORE display attach + widget register) notify.create_channel{ id = "inspect", filter = {"inspect"}, display_mode = "detail" } - notify.create_channel{ id = "events", filter = {"pickup", "drop", "crafting"}, + notify.create_channel{ id = "events", filter = {"pickup", "drop", "crafting", "craft"}, display_mode = "toast" } notify.create_channel{ id = "log", filter = {}, -- catch-all display_mode = "log" } diff --git a/manifest.module b/manifest.module index fa0e062..a8319d4 100644 --- a/manifest.module +++ b/manifest.module @@ -1,6 +1,6 @@ { "id": "vagrant-skeleton", - "version": "0.10.0", + "version": "0.11.0", "kind": "module", "api": "^0.1", "ci_frames": 60, @@ -22,10 +22,12 @@ {"id": "lib-core.notify", "version": "0.1.0"}, {"id": "lib-core.notify-display", "version": "0.1.0"}, {"id": "lib-core.world-overlay", "version": "0.1.0"}, - {"id": "lib-core.maps","version":"0.5.7"}, + {"id": "lib-core.maps", "version": "0.5.7"}, {"id": "lib-core.puppet", "version": "0.5.0"}, {"id": "lib-core.interaction", "version": "0.1.0"}, {"id": "lib-core.inventory-list", "version": "0.1.0"}, + {"id": "lib-core.crafting", "version": "0.1.0"}, + {"id": "lib-core.crafting-display", "version": "0.1.0"}, {"id": "lib-asset.prototype-subterrain", "version": "0.2.0"}, {"id": "lib-asset.prototype-blob-geom", "version": "0.1.0"} ]