85 lines
4.2 KiB
Markdown
85 lines
4.2 KiB
Markdown
# Sprint 18 — Room Chests & Item Spawning Design Spec
|
||
|
||
## Goal
|
||
|
||
Implement a `RoomChest` system: tappable storage nodes in every room that spawn `HoldableItem`/`OutfitItem` instances with a fly-out tween. Items persist in the world until manually dragged back to their chest. All 12 existing rooms get populated with placeholder items (string IDs, no textures yet).
|
||
|
||
## Architecture
|
||
|
||
### Data Layer
|
||
|
||
**`ChestItemData`** — `Resource` subclass, one entry per item slot in a chest.
|
||
|
||
| Field | Type | Purpose |
|
||
|---|---|---|
|
||
| `item_id` | `String` | Canonical item identifier, e.g. `"stethoscope"` |
|
||
| `item_type` | `String` | `"holdable"` or `"outfit"` |
|
||
| `outfit_layer` | `int` | 1–3, only relevant when `item_type == "outfit"` |
|
||
| `spawn_offset` | `Vector2` | Relative target position for fly-out tween |
|
||
|
||
**`RoomChest`** — `Node2D` subclass.
|
||
|
||
| Field | Type | Purpose |
|
||
|---|---|---|
|
||
| `chest_id` | `String` | Unique identifier, e.g. `"pharmacy_medicine"` |
|
||
| `items` | `Array[ChestItemData]` | Configured items for this chest |
|
||
| `_spawned_items` | `Array` | Currently live item nodes in the world |
|
||
|
||
### Spawn Behaviour
|
||
|
||
1. Player taps `RoomChest`.
|
||
2. If `_spawned_items` is not empty → no-op (already spawned).
|
||
3. Otherwise: for each `ChestItemData`, instantiate a `HoldableItem` or `OutfitItem` node. Set `item.home_chest = self`. Reparent to the room scene (not the chest). Tween `global_position` from `chest.global_position` to `chest.global_position + item_data.spawn_offset`.
|
||
|
||
### Return Behaviour
|
||
|
||
`HoldableItem._on_drag_released` checks (in priority order):
|
||
|
||
1. **Chest return** — if `home_chest != null` and `global_position.distance_to(home_chest.global_position) < CHEST_RETURN_RADIUS (80.0)`: tween back to chest → `home_chest.receive_item(self)` → `queue_free()`.
|
||
2. **Outfit apply** — existing OutfitItem logic (80 px body proximity).
|
||
3. **Hand slot attach** — existing HoldableItem logic (60 px hand slot proximity).
|
||
|
||
`RoomChest.receive_item(item: HoldableItem)` removes the item from `_spawned_items`.
|
||
|
||
### GameState v3
|
||
|
||
Extend `GameState` with `_chest_states: Dictionary` mapping `chest_id → Array[String]` of currently spawned `item_id`s. Persist in save data as `"version": 3`. Item positions within the room are **not** persisted (deliberate simplification). On load: if a chest has entries in `_chest_states`, re-spawn those items via the normal fly-out tween from the chest — they land at their configured `spawn_offset`, not their last dragged position.
|
||
|
||
## Room Population
|
||
|
||
All 12 existing room `.tscn` files get `RoomChest` nodes. No textures in this sprint — `item_id` strings only.
|
||
|
||
| Room | chest_id | item_ids |
|
||
|---|---|---|
|
||
| Reception | `reception_desk` | `clipboard`, `pen`, `bandage` |
|
||
| GiftShop | `giftshop_shelf` | `gift_box`, `ribbon`, `balloon` |
|
||
| Restaurant | `restaurant_counter` | `teacup`, `plate`, `spoon` |
|
||
| Emergency | `emergency_cabinet` | `bandage_roll`, `syringe`, `ice_pack` |
|
||
| XRay | `xray_cabinet` | `xray_sheet`, `lead_apron`*, `marker` |
|
||
| Pharmacy | `pharmacy_medicine` | `pill_bottle`, `syrup` |
|
||
| Pharmacy | `pharmacy_tools` | `mortar`, `spatula` |
|
||
| Lab | `lab_bench` | `test_tube`, `pipette`, `microscope_slide` |
|
||
| PatientRooms | `patient_cabinet` | `thermometer`, `stethoscope`*, `pillow` |
|
||
| Ultrasound | `ultrasound_cart` | `gel_tube`, `probe`, `towel` |
|
||
| DeliveryRoom | `delivery_cabinet` | `swaddle`*, `scissors`, `cord_clamp` |
|
||
| Nursery | `nursery_shelf` | `bottle`, `rattle`, `blanket`* |
|
||
| GardenParty | `garden_table` | `teapot`, `cake` |
|
||
| GardenParty | `garden_storage` | `confetti`, `party_hat` |
|
||
|
||
`*` = `item_type: "outfit"` (layer: `lead_apron`→1, `stethoscope`→2, `swaddle`→1, `blanket`→1)
|
||
|
||
## Testing
|
||
|
||
- `test/unit/test_room_chest.gd` — unit tests for `RoomChest` and `ChestItemData`
|
||
- `test/unit/test_game_state.gd` — appended tests for GameState v3 chest state
|
||
- `test/unit/test_holdable_item.gd` — appended tests for chest-return priority
|
||
|
||
Tests follow GUT v9.6.0, `extends GutTest`, `add_child_autofree`.
|
||
|
||
## Out of Scope
|
||
|
||
- Item textures / sprites (Sprint 18 uses null textures throughout)
|
||
- Chest open/close sprite animation (visual polish, later sprint)
|
||
- Per-item sounds (AudioManager sprint)
|
||
- Chest state per save-slot (single save file, existing SaveManager)
|