4.2 KiB
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
- Player taps
RoomChest. - If
_spawned_itemsis not empty → no-op (already spawned). - Otherwise: for each
ChestItemData, instantiate aHoldableItemorOutfitItemnode. Setitem.home_chest = self. Reparent to the room scene (not the chest). Tweenglobal_positionfromchest.global_positiontochest.global_position + item_data.spawn_offset.
Return Behaviour
HoldableItem._on_drag_released checks (in priority order):
- Chest return — if
home_chest != nullandglobal_position.distance_to(home_chest.global_position) < CHEST_RETURN_RADIUS (80.0): tween back to chest →home_chest.receive_item(self)→queue_free(). - Outfit apply — existing OutfitItem logic (80 px body proximity).
- 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_ids. 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 forRoomChestandChestItemDatatest/unit/test_game_state.gd— appended tests for GameState v3 chest statetest/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)