Files
Emberwild/scripts/ui/slot.gd
Paul Bailey 4640f5e577 M1a: survival core, pause menu, title screen and save/load
Design: docs/SURVIVAL_DESIGN.md (core loop, systems, architecture, phases).

Survival core (M1a):
- Items/recipes as data (data/*.json) in the addon SDK's define_item /
  define_recipe shape; procedural item meshes and offscreen-rendered icons
- Inventory class (40 slots, hotbar = row 1) shared by player and chests;
  click / right-click split / shift-click quick-move
- Actions autoload: the intent layer (craft, pickup, harvest_hit, place,
  transfer) that validates every change; becomes the RPC surface in M2
- Gathering: ground pickups (sticks, stones, flint); trees chopped with an
  axe fall and leave stumps; boulders mined with a pickaxe shrink per hit
- Crafting screen with station checks (workbench); placement mode with
  green/red ghost, rotation and overlap/slope validation
- Pieces: campfire (light, flames, embers), workbench, chest (16 slots)
- HUD: hotbar, held tool view model with swing, prompts, toasts

Menus and saving:
- ESC pause menu: resume, controls, fullscreen, mouse sensitivity, quit
- Title screen over a flyover of the island: Continue / New World (name +
  seed) / Load World (with delete) / Quit
- Saves autoload: one folder per world under user://worlds, atomic writes
  with a .bak; stores only player changes (player, pieces, chest contents,
  harvested resources, taken pickups) and regenerates the rest from the seed
- Autosave every 2 min, Save & Quit to title/desktop, save on window close

Fixes: UI screens now fill the viewport (were pinned top-left); title
backdrop no longer captures the mouse.

Tests: test_survival_core (31 checks), test_save_load (round trip + slots),
test_terrain_edges now checks 8 seeds for deep pits / unbanked water.
2026-11-04 00:34:46 +00:00

67 lines
2.4 KiB
GDScript

extends Panel
## One inventory slot: icon, stack count, optional hotbar number. Renders a
## stack and reports clicks; it never changes the inventory itself.
signal clicked(index: int, right: bool, shift: bool)
const UITheme = preload("res://scripts/ui/theme.gd")
var index := 0
var _icon: TextureRect
var _count: Label
var _key: Label
var _style: StyleBoxFlat
var _selected_style: StyleBoxFlat
var _stack = null
func _init(i: int = 0, size_px: int = 64, key_label: String = "") -> void:
index = i
custom_minimum_size = Vector2(size_px, size_px)
mouse_filter = Control.MOUSE_FILTER_STOP
_style = UITheme.box(UITheme.SLOT_BG, UITheme.SLOT_BORDER, 2, 5, 0)
_selected_style = UITheme.box(UITheme.SLOT_BG.lightened(0.15), UITheme.SELECTED, 3, 5, 0)
add_theme_stylebox_override("panel", _style)
_icon = TextureRect.new()
_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
_icon.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
_icon.offset_left = 5; _icon.offset_top = 5; _icon.offset_right = -5; _icon.offset_bottom = -5
_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(_icon)
_count = UITheme.label("", 15)
_count.set_anchors_and_offsets_preset(Control.PRESET_BOTTOM_RIGHT)
_count.grow_horizontal = Control.GROW_DIRECTION_BEGIN
_count.grow_vertical = Control.GROW_DIRECTION_BEGIN
_count.offset_right = -5
_count.offset_bottom = -2
_count.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(_count)
_key = UITheme.label(key_label, 12, UITheme.TEXT_DIM)
_key.position = Vector2(5, 2)
_key.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(_key)
func set_stack(stack) -> void:
_stack = stack
if stack == null:
_icon.texture = null
_count.text = ""
tooltip_text = ""
return
_icon.texture = ItemDB.icon(stack.id)
_count.text = str(stack.count) if stack.count > 1 else ""
var def := ItemDB.get_item(stack.id)
tooltip_text = "%s\n%s" % [def.get("name", stack.id), def.get("description", "")]
func set_selected(on: bool) -> void:
add_theme_stylebox_override("panel", _selected_style if on else _style)
func _gui_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.pressed:
if event.button_index == MOUSE_BUTTON_LEFT or event.button_index == MOUSE_BUTTON_RIGHT:
clicked.emit(index, event.button_index == MOUSE_BUTTON_RIGHT, event.shift_pressed)
accept_event()