Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| df6df900c6 | |||
| cd3ce7bf6e | |||
| 9aded82dbb | |||
| a877d8f5fe | |||
| 87db92955a | |||
| 96ec053331 | |||
| 4f1766834a | |||
| b9c73b80ea | |||
| 4e4743f14f | |||
| b97b110876 | |||
| 9786cf5895 | |||
| 8aa9673154 | |||
| 2e0b961520 |
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,84 @@
|
||||
# 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)
|
||||
@@ -1,8 +1,9 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://cozypaw_emergency"]
|
||||
[gd_scene load_steps=5 format=3 uid="uid://cozypaw_emergency"]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://scenes/objects/InteractiveObject.tscn" id="1_iobj"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/objects/Ambulance.tscn" id="2_ambulance"]
|
||||
[ext_resource type="Script" path="res://scripts/objects/snap_point.gd" id="3_snap"]
|
||||
[ext_resource type="Script" path="res://scripts/objects/room_chest.gd" id="4_chest"]
|
||||
|
||||
[node name="EmergencyRoom" type="Node2D"]
|
||||
|
||||
@@ -79,3 +80,8 @@ trigger_room = 3
|
||||
position = Vector2(310, 480)
|
||||
script = ExtResource("3_snap")
|
||||
pose = "lying"
|
||||
|
||||
[node name="EmergencyCabinet" type="Node2D" parent="."]
|
||||
position = Vector2(150.0, 400.0)
|
||||
script = ExtResource("4_chest")
|
||||
chest_id = "emergency_cabinet"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://cozypaw_giftshop"]
|
||||
[gd_scene load_steps=4 format=3 uid="uid://cozypaw_giftshop"]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://scenes/objects/InteractiveObject.tscn" id="1_iobj"]
|
||||
[ext_resource type="Script" path="res://scripts/objects/snap_point.gd" id="2_snap"]
|
||||
[ext_resource type="Script" path="res://scripts/objects/room_chest.gd" id="3_chest"]
|
||||
|
||||
[node name="GiftShop" type="Node2D"]
|
||||
|
||||
@@ -68,3 +69,8 @@ position = Vector2(640, 510)
|
||||
[node name="SnapCounter" type="Node2D" parent="."]
|
||||
position = Vector2(640, 528)
|
||||
script = ExtResource("2_snap")
|
||||
|
||||
[node name="GiftShopShelf" type="Node2D" parent="."]
|
||||
position = Vector2(120.0, 300.0)
|
||||
script = ExtResource("3_chest")
|
||||
chest_id = "giftshop_shelf"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://cozypaw_reception"]
|
||||
[gd_scene load_steps=4 format=3 uid="uid://cozypaw_reception"]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://scenes/objects/InteractiveObject.tscn" id="1_iobj"]
|
||||
[ext_resource type="Script" path="res://scripts/objects/snap_point.gd" id="2_snap"]
|
||||
[ext_resource type="Script" path="res://scripts/objects/room_chest.gd" id="3_chest"]
|
||||
|
||||
[node name="Reception" type="Node2D"]
|
||||
|
||||
@@ -102,3 +103,8 @@ script = ExtResource("2_snap")
|
||||
[node name="SnapBenchRight2" type="Node2D" parent="."]
|
||||
position = Vector2(1080, 555)
|
||||
script = ExtResource("2_snap")
|
||||
|
||||
[node name="ReceptionDesk" type="Node2D" parent="."]
|
||||
position = Vector2(120.0, 555.0)
|
||||
script = ExtResource("3_chest")
|
||||
chest_id = "reception_desk"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://cozypaw_restaurant"]
|
||||
[gd_scene load_steps=4 format=3 uid="uid://cozypaw_restaurant"]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://scenes/objects/InteractiveObject.tscn" id="1_iobj"]
|
||||
[ext_resource type="Script" path="res://scripts/objects/snap_point.gd" id="2_snap"]
|
||||
[ext_resource type="Script" path="res://scripts/objects/room_chest.gd" id="3_chest"]
|
||||
|
||||
[node name="Restaurant" type="Node2D"]
|
||||
|
||||
@@ -118,3 +119,8 @@ script = ExtResource("2_snap")
|
||||
[node name="SnapTable3Right" type="Node2D" parent="."]
|
||||
position = Vector2(1120, 510)
|
||||
script = ExtResource("2_snap")
|
||||
|
||||
[node name="RestaurantCounter" type="Node2D" parent="."]
|
||||
position = Vector2(120.0, 555.0)
|
||||
script = ExtResource("3_chest")
|
||||
chest_id = "restaurant_counter"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://cozypaw_lab"]
|
||||
[gd_scene load_steps=4 format=3 uid="uid://cozypaw_lab"]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://scenes/objects/InteractiveObject.tscn" id="1_iobj"]
|
||||
[ext_resource type="Script" path="res://scripts/objects/snap_point.gd" id="2_snap"]
|
||||
[ext_resource type="Script" path="res://scripts/objects/room_chest.gd" id="3_chest"]
|
||||
|
||||
[node name="Lab" type="Node2D"]
|
||||
|
||||
@@ -25,7 +26,7 @@ color = Color(0.88, 0.92, 0.94, 1)
|
||||
size = Vector2(40, 620)
|
||||
position = Vector2(1240, 0)
|
||||
|
||||
[node name="LabBench" type="ColorRect" parent="."]
|
||||
[node name="LabBenchSurface" type="ColorRect" parent="."]
|
||||
color = Color(0.88, 0.88, 0.92, 1)
|
||||
size = Vector2(800, 40)
|
||||
position = Vector2(240, 480)
|
||||
@@ -77,3 +78,8 @@ script = ExtResource("2_snap")
|
||||
[node name="SnapLabBench2" type="Node2D" parent="."]
|
||||
position = Vector2(750, 470)
|
||||
script = ExtResource("2_snap")
|
||||
|
||||
[node name="LabBench" type="Node2D" parent="."]
|
||||
position = Vector2(150.0, 400.0)
|
||||
script = ExtResource("3_chest")
|
||||
chest_id = "lab_bench"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://cozypaw_patientroom"]
|
||||
[gd_scene load_steps=4 format=3 uid="uid://cozypaw_patientroom"]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://scenes/objects/InteractiveObject.tscn" id="1_iobj"]
|
||||
[ext_resource type="Script" path="res://scripts/objects/snap_point.gd" id="2_snap"]
|
||||
[ext_resource type="Script" path="res://scripts/objects/room_chest.gd" id="3_chest"]
|
||||
|
||||
[node name="PatientRoom" type="Node2D"]
|
||||
|
||||
@@ -93,3 +94,8 @@ pose = "lying"
|
||||
position = Vector2(810, 465)
|
||||
script = ExtResource("2_snap")
|
||||
pose = "lying"
|
||||
|
||||
[node name="PatientCabinet" type="Node2D" parent="."]
|
||||
position = Vector2(150.0, 400.0)
|
||||
script = ExtResource("3_chest")
|
||||
chest_id = "patient_cabinet"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://cozypaw_pharmacy"]
|
||||
[gd_scene load_steps=4 format=3 uid="uid://cozypaw_pharmacy"]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://scenes/objects/InteractiveObject.tscn" id="1_iobj"]
|
||||
[ext_resource type="Script" path="res://scripts/objects/snap_point.gd" id="2_snap"]
|
||||
[ext_resource type="Script" path="res://scripts/objects/room_chest.gd" id="3_chest"]
|
||||
|
||||
[node name="Pharmacy" type="Node2D"]
|
||||
|
||||
@@ -73,3 +74,13 @@ position = Vector2(640, 430)
|
||||
[node name="SnapCounter" type="Node2D" parent="."]
|
||||
position = Vector2(640, 520)
|
||||
script = ExtResource("2_snap")
|
||||
|
||||
[node name="PharmacyMedicine" type="Node2D" parent="."]
|
||||
position = Vector2(350.0, 320.0)
|
||||
script = ExtResource("3_chest")
|
||||
chest_id = "pharmacy_medicine"
|
||||
|
||||
[node name="PharmacyTools" type="Node2D" parent="."]
|
||||
position = Vector2(900.0, 320.0)
|
||||
script = ExtResource("3_chest")
|
||||
chest_id = "pharmacy_tools"
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://cozypaw_xray"]
|
||||
[gd_scene load_steps=5 format=3 uid="uid://cozypaw_xray"]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://scenes/objects/InteractiveObject.tscn" id="1_iobj"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/objects/XRayMachine.tscn" id="2_xraymachine"]
|
||||
[ext_resource type="Script" path="res://scripts/objects/snap_point.gd" id="3_snap"]
|
||||
[ext_resource type="Script" path="res://scripts/objects/room_chest.gd" id="4_chest"]
|
||||
|
||||
[node name="XRay" type="Node2D"]
|
||||
|
||||
@@ -59,3 +60,8 @@ position = Vector2(900, 560)
|
||||
position = Vector2(640, 480)
|
||||
script = ExtResource("3_snap")
|
||||
pose = "lying"
|
||||
|
||||
[node name="XRayCabinet" type="Node2D" parent="."]
|
||||
position = Vector2(150.0, 400.0)
|
||||
script = ExtResource("4_chest")
|
||||
chest_id = "xray_cabinet"
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://cozypaw_deliveryroom"]
|
||||
[gd_scene load_steps=5 format=3 uid="uid://cozypaw_deliveryroom"]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://scenes/objects/InteractiveObject.tscn" id="1_iobj"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/objects/DeliveryBed.tscn" id="2_deliverybed"]
|
||||
[ext_resource type="Script" path="res://scripts/objects/snap_point.gd" id="3_snap"]
|
||||
[ext_resource type="Script" path="res://scripts/objects/room_chest.gd" id="4_chest"]
|
||||
|
||||
[node name="DeliveryRoom" type="Node2D"]
|
||||
|
||||
@@ -42,3 +43,8 @@ position = Vector2(880, 540)
|
||||
position = Vector2(540, 480)
|
||||
script = ExtResource("3_snap")
|
||||
pose = "lying"
|
||||
|
||||
[node name="DeliveryCabinet" type="Node2D" parent="."]
|
||||
position = Vector2(150.0, 400.0)
|
||||
script = ExtResource("4_chest")
|
||||
chest_id = "delivery_cabinet"
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://cozypaw_nursery"]
|
||||
[gd_scene load_steps=5 format=3 uid="uid://cozypaw_nursery"]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://scenes/objects/InteractiveObject.tscn" id="1_iobj"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/objects/Cradle.tscn" id="2_cradle"]
|
||||
[ext_resource type="Script" path="res://scripts/objects/snap_point.gd" id="3_snap"]
|
||||
[ext_resource type="Script" path="res://scripts/objects/room_chest.gd" id="4_chest"]
|
||||
|
||||
[node name="Nursery" type="Node2D"]
|
||||
|
||||
@@ -66,3 +67,8 @@ position = Vector2(860, 240)
|
||||
script = ExtResource("3_snap")
|
||||
pose = "lying"
|
||||
baby_only = true
|
||||
|
||||
[node name="NurseryShelf" type="Node2D" parent="."]
|
||||
position = Vector2(640.0, 260.0)
|
||||
script = ExtResource("4_chest")
|
||||
chest_id = "nursery_shelf"
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://cozypaw_ultrasound"]
|
||||
[gd_scene load_steps=5 format=3 uid="uid://cozypaw_ultrasound"]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://scenes/objects/InteractiveObject.tscn" id="1_iobj"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/objects/UltrasoundMachine.tscn" id="2_ultrasound"]
|
||||
[ext_resource type="Script" path="res://scripts/objects/snap_point.gd" id="3_snap"]
|
||||
[ext_resource type="Script" path="res://scripts/objects/room_chest.gd" id="4_chest"]
|
||||
|
||||
[node name="Ultrasound" type="Node2D"]
|
||||
|
||||
@@ -57,3 +58,8 @@ position = Vector2(480, 450)
|
||||
position = Vector2(470, 480)
|
||||
script = ExtResource("3_snap")
|
||||
pose = "lying"
|
||||
|
||||
[node name="UltrasoundCart" type="Node2D" parent="."]
|
||||
position = Vector2(150.0, 400.0)
|
||||
script = ExtResource("4_chest")
|
||||
chest_id = "ultrasound_cart"
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://cozypaw_gardenparty"]
|
||||
[gd_scene load_steps=6 format=3 uid="uid://cozypaw_gardenparty"]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://scenes/objects/GiftBox.tscn" id="1_giftbox"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/objects/TeaPot.tscn" id="2_teapot"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/objects/HomeButton.tscn" id="3_homebtn"]
|
||||
[ext_resource type="Script" path="res://scripts/objects/snap_point.gd" id="4_snap"]
|
||||
[ext_resource type="Script" path="res://scripts/objects/room_chest.gd" id="5_chest"]
|
||||
|
||||
[node name="GardenParty" type="Node2D"]
|
||||
|
||||
@@ -93,3 +94,13 @@ script = ExtResource("4_snap")
|
||||
[node name="SnapTableRight" type="Node2D" parent="."]
|
||||
position = Vector2(750, 455)
|
||||
script = ExtResource("4_snap")
|
||||
|
||||
[node name="GardenTable" type="Node2D" parent="."]
|
||||
position = Vector2(200.0, 400.0)
|
||||
script = ExtResource("5_chest")
|
||||
chest_id = "garden_table"
|
||||
|
||||
[node name="GardenStorage" type="Node2D" parent="."]
|
||||
position = Vector2(900.0, 400.0)
|
||||
script = ExtResource("5_chest")
|
||||
chest_id = "garden_storage"
|
||||
|
||||
@@ -8,6 +8,7 @@ var _character_positions: Dictionary = {}
|
||||
var _character_outfits: Dictionary = {}
|
||||
var _character_held_items: Dictionary = {}
|
||||
var _object_states: Dictionary = {}
|
||||
var _chest_states: Dictionary = {}
|
||||
var current_room: String = "reception"
|
||||
var music_volume: float = 0.6
|
||||
var sfx_volume: float = 1.0
|
||||
@@ -58,17 +59,32 @@ func set_object_state(id: String, state: String) -> void:
|
||||
state_changed.emit()
|
||||
|
||||
|
||||
func get_chest_state(chest_id: String) -> Array:
|
||||
return _chest_states.get(chest_id, [])
|
||||
|
||||
|
||||
func set_chest_state(chest_id: String, spawned_item_ids: Array[String]) -> void:
|
||||
_chest_states[chest_id] = spawned_item_ids
|
||||
state_changed.emit()
|
||||
|
||||
|
||||
func clear_chest_state(chest_id: String) -> void:
|
||||
_chest_states.erase(chest_id)
|
||||
state_changed.emit()
|
||||
|
||||
|
||||
func get_save_data() -> Dictionary:
|
||||
var positions: Dictionary = {}
|
||||
for key: String in _character_positions:
|
||||
var pos: Vector2 = _character_positions[key]
|
||||
positions[key] = [pos.x, pos.y]
|
||||
return {
|
||||
"version": 2,
|
||||
"version": 3,
|
||||
"character_positions": positions,
|
||||
"character_outfits": _character_outfits.duplicate(true),
|
||||
"character_held_items": _character_held_items.duplicate(true),
|
||||
"object_states": _object_states,
|
||||
"chest_states": _chest_states.duplicate(true),
|
||||
"current_room": current_room,
|
||||
"music_volume": music_volume,
|
||||
"sfx_volume": sfx_volume,
|
||||
@@ -92,9 +108,15 @@ func apply_save_data(data: Dictionary) -> void:
|
||||
_character_held_items = {}
|
||||
if data.has("object_states"):
|
||||
_object_states = data["object_states"]
|
||||
else:
|
||||
_object_states = {}
|
||||
if data.has("current_room"):
|
||||
current_room = data["current_room"]
|
||||
if data.has("music_volume"):
|
||||
music_volume = data["music_volume"]
|
||||
if data.has("sfx_volume"):
|
||||
sfx_volume = data["sfx_volume"]
|
||||
if data.has("chest_states"):
|
||||
_chest_states = data["chest_states"].duplicate(true)
|
||||
else:
|
||||
_chest_states = {}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
## ChestItemData — configuration for a single item slot inside a RoomChest.
|
||||
class_name ChestItemData extends Resource
|
||||
|
||||
enum ItemType { HOLDABLE, OUTFIT }
|
||||
|
||||
@export var item_id: String = ""
|
||||
@export var item_type: ItemType = ItemType.HOLDABLE
|
||||
@export_range(1, 3) var outfit_layer: int = 1
|
||||
@export var spawn_offset: Vector2 = Vector2.ZERO
|
||||
@@ -7,9 +7,12 @@ signal item_picked_up(item: HoldableItem)
|
||||
signal item_placed(item: HoldableItem)
|
||||
|
||||
const HAND_SLOT_RADIUS: float = 60.0
|
||||
const CHEST_RETURN_RADIUS: float = 80.0
|
||||
|
||||
@export var item_id: String = ""
|
||||
|
||||
var home_chest: Node2D = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var drag: DragDropComponent = get_node_or_null("DragDropComponent") as DragDropComponent
|
||||
@@ -25,6 +28,8 @@ func _on_drag_picked_up(_pos: Vector2) -> void:
|
||||
|
||||
|
||||
func _on_drag_released(_pos: Vector2) -> void:
|
||||
if _try_return_to_chest():
|
||||
return
|
||||
var result: Array = _find_nearest_free_hand_slot()
|
||||
if not result.is_empty():
|
||||
var character: Character = result[0] as Character
|
||||
@@ -33,6 +38,18 @@ func _on_drag_released(_pos: Vector2) -> void:
|
||||
item_placed.emit(self)
|
||||
|
||||
|
||||
func _try_return_to_chest() -> bool:
|
||||
if home_chest == null:
|
||||
return false
|
||||
if global_position.distance_to(home_chest.global_position) >= CHEST_RETURN_RADIUS:
|
||||
return false
|
||||
var chest: RoomChest = home_chest as RoomChest
|
||||
if chest == null:
|
||||
return false
|
||||
chest.receive_item(self)
|
||||
return true
|
||||
|
||||
|
||||
func is_in_hand_slot() -> bool:
|
||||
var p: Node = get_parent()
|
||||
if p == null:
|
||||
|
||||
@@ -10,6 +10,8 @@ const OUTFIT_APPLY_RADIUS: float = 80.0
|
||||
|
||||
|
||||
func _on_drag_released(_pos: Vector2) -> void:
|
||||
if _try_return_to_chest():
|
||||
return
|
||||
var character: Character = _find_nearest_character()
|
||||
if character != null:
|
||||
character.apply_outfit_item(outfit_layer, item_id, outfit_sprite, self)
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
## RoomChest — tappable storage node. Spawns HoldableItem/OutfitItem instances on demand.
|
||||
## Items fly out with a tween. Receives items back via receive_item().
|
||||
class_name RoomChest extends Node2D
|
||||
|
||||
signal items_spawned(chest: RoomChest)
|
||||
signal item_received(chest: RoomChest, item_id: String)
|
||||
|
||||
const SPAWN_TWEEN_DURATION: float = 0.3
|
||||
|
||||
@export var chest_id: String = ""
|
||||
|
||||
var _spawned_items: Array[HoldableItem] = []
|
||||
var _item_configs: Array[ChestItemData] = []
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("room_chests")
|
||||
_item_configs = RoomChestConfig.get_items(chest_id)
|
||||
if not chest_id.is_empty() and GameState.has_method("get_chest_state"):
|
||||
if not GameState.get_chest_state(chest_id).is_empty():
|
||||
call_deferred("spawn_items")
|
||||
|
||||
|
||||
func spawn_items() -> void:
|
||||
if not _spawned_items.is_empty():
|
||||
return
|
||||
var parent: Node = get_parent()
|
||||
for config: ChestItemData in _item_configs:
|
||||
var item: HoldableItem = _create_item(config)
|
||||
item.home_chest = self
|
||||
if parent != null:
|
||||
parent.add_child(item)
|
||||
else:
|
||||
add_child(item)
|
||||
item.global_position = global_position
|
||||
_spawned_items.append(item)
|
||||
_tween_item_out(item, config.spawn_offset)
|
||||
if GameState.has_method("set_chest_state"):
|
||||
GameState.set_chest_state(chest_id, _get_spawned_ids())
|
||||
items_spawned.emit(self)
|
||||
|
||||
|
||||
func receive_item(item: HoldableItem) -> void:
|
||||
if not _spawned_items.has(item):
|
||||
return
|
||||
_spawned_items.erase(item)
|
||||
if GameState.has_method("set_chest_state"):
|
||||
if _spawned_items.is_empty():
|
||||
GameState.clear_chest_state(chest_id)
|
||||
else:
|
||||
GameState.set_chest_state(chest_id, _get_spawned_ids())
|
||||
item_received.emit(self, item.item_id)
|
||||
_tween_item_in(item)
|
||||
|
||||
|
||||
func are_items_spawned() -> bool:
|
||||
return not _spawned_items.is_empty()
|
||||
|
||||
|
||||
func get_spawned_count() -> int:
|
||||
return _spawned_items.size()
|
||||
|
||||
|
||||
func get_item_config_count() -> int:
|
||||
return _item_configs.size()
|
||||
|
||||
|
||||
func get_spawned_item(index: int) -> HoldableItem:
|
||||
if index < 0 or index >= _spawned_items.size():
|
||||
return null
|
||||
return _spawned_items[index]
|
||||
|
||||
|
||||
func _create_item(config: ChestItemData) -> HoldableItem:
|
||||
var item: HoldableItem
|
||||
if config.item_type == ChestItemData.ItemType.OUTFIT:
|
||||
var outfit: OutfitItem = OutfitItem.new()
|
||||
outfit.outfit_layer = config.outfit_layer
|
||||
item = outfit
|
||||
else:
|
||||
item = HoldableItem.new()
|
||||
item.item_id = config.item_id
|
||||
return item
|
||||
|
||||
|
||||
func _get_spawned_ids() -> Array[String]:
|
||||
var ids: Array[String] = []
|
||||
for item: HoldableItem in _spawned_items:
|
||||
ids.append(item.item_id)
|
||||
return ids
|
||||
|
||||
|
||||
func _tween_item_out(item: HoldableItem, offset: Vector2) -> void:
|
||||
var tween: Tween = create_tween()
|
||||
tween.tween_property(item, "global_position", global_position + offset, SPAWN_TWEEN_DURATION)
|
||||
|
||||
|
||||
func _tween_item_in(item: HoldableItem) -> void:
|
||||
var tween: Tween = create_tween()
|
||||
tween.tween_property(item, "global_position", global_position, SPAWN_TWEEN_DURATION)
|
||||
tween.tween_callback(item.queue_free)
|
||||
@@ -0,0 +1,107 @@
|
||||
## RoomChestConfig — static item configuration for all room chests.
|
||||
## Maps chest_id strings to ChestItemData arrays. No assets needed: item_id strings only.
|
||||
class_name RoomChestConfig
|
||||
|
||||
const _OFFSET_LEFT: Vector2 = Vector2(-70.0, -60.0)
|
||||
const _OFFSET_CENTER: Vector2 = Vector2(0.0, -80.0)
|
||||
const _OFFSET_RIGHT: Vector2 = Vector2(70.0, -60.0)
|
||||
const _OFFSET_LEFT_2: Vector2 = Vector2(-50.0, -60.0)
|
||||
const _OFFSET_RIGHT_2: Vector2 = Vector2(50.0, -60.0)
|
||||
|
||||
|
||||
static func get_items(chest_id: String) -> Array[ChestItemData]:
|
||||
match chest_id:
|
||||
"reception_desk":
|
||||
return _make([
|
||||
["clipboard", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_LEFT],
|
||||
["pen", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_CENTER],
|
||||
["bandage", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_RIGHT],
|
||||
])
|
||||
"giftshop_shelf":
|
||||
return _make([
|
||||
["gift_box", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_LEFT],
|
||||
["ribbon", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_CENTER],
|
||||
["balloon", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_RIGHT],
|
||||
])
|
||||
"restaurant_counter":
|
||||
return _make([
|
||||
["teacup", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_LEFT],
|
||||
["plate", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_CENTER],
|
||||
["spoon", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_RIGHT],
|
||||
])
|
||||
"emergency_cabinet":
|
||||
return _make([
|
||||
["bandage_roll", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_LEFT],
|
||||
["syringe", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_CENTER],
|
||||
["ice_pack", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_RIGHT],
|
||||
])
|
||||
"xray_cabinet":
|
||||
return _make([
|
||||
["xray_sheet", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_LEFT],
|
||||
["lead_apron", ChestItemData.ItemType.OUTFIT, 1, _OFFSET_CENTER],
|
||||
["marker", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_RIGHT],
|
||||
])
|
||||
"pharmacy_medicine":
|
||||
return _make([
|
||||
["pill_bottle", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_LEFT_2],
|
||||
["syrup", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_RIGHT_2],
|
||||
])
|
||||
"pharmacy_tools":
|
||||
return _make([
|
||||
["mortar", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_LEFT_2],
|
||||
["spatula", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_RIGHT_2],
|
||||
])
|
||||
"lab_bench":
|
||||
return _make([
|
||||
["test_tube", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_LEFT],
|
||||
["pipette", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_CENTER],
|
||||
["microscope_slide", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_RIGHT],
|
||||
])
|
||||
"patient_cabinet":
|
||||
return _make([
|
||||
["thermometer", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_LEFT],
|
||||
["stethoscope", ChestItemData.ItemType.OUTFIT, 2, _OFFSET_CENTER],
|
||||
["pillow", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_RIGHT],
|
||||
])
|
||||
"ultrasound_cart":
|
||||
return _make([
|
||||
["gel_tube", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_LEFT],
|
||||
["probe", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_CENTER],
|
||||
["towel", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_RIGHT],
|
||||
])
|
||||
"delivery_cabinet":
|
||||
return _make([
|
||||
["swaddle", ChestItemData.ItemType.OUTFIT, 1, _OFFSET_LEFT],
|
||||
["scissors", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_CENTER],
|
||||
["cord_clamp", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_RIGHT],
|
||||
])
|
||||
"nursery_shelf":
|
||||
return _make([
|
||||
["bottle", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_LEFT],
|
||||
["rattle", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_CENTER],
|
||||
["blanket", ChestItemData.ItemType.OUTFIT, 1, _OFFSET_RIGHT],
|
||||
])
|
||||
"garden_table":
|
||||
return _make([
|
||||
["teapot", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_LEFT_2],
|
||||
["cake", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_RIGHT_2],
|
||||
])
|
||||
"garden_storage":
|
||||
return _make([
|
||||
["confetti", ChestItemData.ItemType.HOLDABLE, 1, _OFFSET_LEFT_2],
|
||||
["party_hat", ChestItemData.ItemType.OUTFIT, 1, _OFFSET_RIGHT_2],
|
||||
])
|
||||
return []
|
||||
|
||||
|
||||
static func _make(data: Array) -> Array[ChestItemData]:
|
||||
var result: Array[ChestItemData] = []
|
||||
for entry: Array in data:
|
||||
assert(entry.size() == 4, "ChestItemData entry must have 4 elements: " + str(entry))
|
||||
var d: ChestItemData = ChestItemData.new()
|
||||
d.item_id = entry[0]
|
||||
d.item_type = entry[1]
|
||||
d.outfit_layer = entry[2]
|
||||
d.spawn_offset = entry[3]
|
||||
result.append(d)
|
||||
return result
|
||||
@@ -142,6 +142,42 @@ func test_apply_save_data_restores_held_items() -> void:
|
||||
assert_eq(_state.get_character_held_item("kitten_f", "left"), "gel_tube")
|
||||
|
||||
|
||||
func test_save_data_has_version_two() -> void:
|
||||
func test_save_data_has_version_three() -> void:
|
||||
var data: Dictionary = _state.get_save_data()
|
||||
assert_eq(data.get("version", 0), 2)
|
||||
assert_eq(data.get("version", 0), 3)
|
||||
|
||||
|
||||
func test_get_chest_state_returns_empty_for_unknown_id() -> void:
|
||||
assert_eq(GameState.get_chest_state("nonexistent_chest_xyz"), [])
|
||||
|
||||
|
||||
func test_set_and_get_chest_state() -> void:
|
||||
GameState.set_chest_state("pharmacy_medicine_test", ["pill_bottle", "syrup"])
|
||||
assert_eq(GameState.get_chest_state("pharmacy_medicine_test"), ["pill_bottle", "syrup"])
|
||||
|
||||
|
||||
func test_clear_chest_state_removes_entry() -> void:
|
||||
GameState.set_chest_state("lab_bench_test", ["test_tube"])
|
||||
GameState.clear_chest_state("lab_bench_test")
|
||||
assert_eq(GameState.get_chest_state("lab_bench_test"), [])
|
||||
|
||||
|
||||
func test_chest_state_included_in_save_data() -> void:
|
||||
GameState.set_chest_state("xray_cabinet_test", ["xray_sheet"])
|
||||
var data: Dictionary = GameState.get_save_data()
|
||||
assert_true(data.has("chest_states"))
|
||||
assert_eq(data["chest_states"]["xray_cabinet_test"], ["xray_sheet"])
|
||||
|
||||
|
||||
func test_save_data_version_is_three() -> void:
|
||||
var data: Dictionary = GameState.get_save_data()
|
||||
assert_eq(data["version"], 3)
|
||||
|
||||
|
||||
func test_apply_save_data_restores_chest_state() -> void:
|
||||
var data: Dictionary = {
|
||||
"version": 3,
|
||||
"chest_states": {"reception_desk_test": ["clipboard", "pen"]},
|
||||
}
|
||||
GameState.apply_save_data(data)
|
||||
assert_eq(GameState.get_chest_state("reception_desk_test"), ["clipboard", "pen"])
|
||||
|
||||
@@ -69,3 +69,33 @@ func test_holdable_item_detach_preserves_global_position() -> void:
|
||||
var hand_pos: Vector2 = character.get_node("HandLeft").global_position
|
||||
item._on_drag_picked_up(hand_pos)
|
||||
assert_eq(item.global_position, hand_pos)
|
||||
|
||||
|
||||
func test_try_return_to_chest_false_when_no_home_chest() -> void:
|
||||
var item: HoldableItem = HoldableItem.new()
|
||||
add_child_autofree(item)
|
||||
assert_false(item._try_return_to_chest())
|
||||
|
||||
|
||||
func test_try_return_to_chest_false_when_beyond_radius() -> void:
|
||||
var chest: RoomChest = RoomChest.new()
|
||||
chest.chest_id = "reception_desk"
|
||||
add_child_autofree(chest)
|
||||
chest.global_position = Vector2.ZERO
|
||||
var item: HoldableItem = HoldableItem.new()
|
||||
add_child_autofree(item)
|
||||
item.home_chest = chest
|
||||
item.global_position = Vector2(200.0, 0.0)
|
||||
assert_false(item._try_return_to_chest())
|
||||
|
||||
|
||||
func test_try_return_to_chest_true_when_within_radius() -> void:
|
||||
var chest: RoomChest = RoomChest.new()
|
||||
chest.chest_id = "reception_desk"
|
||||
add_child_autofree(chest)
|
||||
chest.global_position = Vector2.ZERO
|
||||
var item: HoldableItem = HoldableItem.new()
|
||||
add_child_autofree(item)
|
||||
item.home_chest = chest
|
||||
item.global_position = Vector2(40.0, 0.0)
|
||||
assert_true(item._try_return_to_chest())
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
## Tests for ChestItemData resource and RoomChestConfig static config.
|
||||
extends GutTest
|
||||
|
||||
|
||||
func test_chest_item_data_default_item_type_is_holdable() -> void:
|
||||
var d: ChestItemData = ChestItemData.new()
|
||||
assert_eq(d.item_type, ChestItemData.ItemType.HOLDABLE)
|
||||
|
||||
|
||||
func test_chest_item_data_default_outfit_layer_is_one() -> void:
|
||||
var d: ChestItemData = ChestItemData.new()
|
||||
assert_eq(d.outfit_layer, 1)
|
||||
|
||||
|
||||
func test_room_chest_config_reception_desk_has_three_items() -> void:
|
||||
var items: Array[ChestItemData] = RoomChestConfig.get_items("reception_desk")
|
||||
assert_eq(items.size(), 3)
|
||||
|
||||
|
||||
func test_room_chest_config_unknown_id_returns_empty() -> void:
|
||||
var items: Array[ChestItemData] = RoomChestConfig.get_items("does_not_exist")
|
||||
assert_eq(items.size(), 0)
|
||||
|
||||
|
||||
func test_room_chest_config_reception_desk_first_item_fields() -> void:
|
||||
var items: Array[ChestItemData] = RoomChestConfig.get_items("reception_desk")
|
||||
assert_eq(items[0].item_id, "clipboard")
|
||||
assert_eq(items[0].item_type, ChestItemData.ItemType.HOLDABLE)
|
||||
assert_eq(items[0].spawn_offset, Vector2(-70.0, -60.0))
|
||||
|
||||
|
||||
func test_room_chest_config_patient_cabinet_stethoscope_outfit_layer_two() -> void:
|
||||
var items: Array[ChestItemData] = RoomChestConfig.get_items("patient_cabinet")
|
||||
var stethoscope: ChestItemData = items[1]
|
||||
assert_eq(stethoscope.item_id, "stethoscope")
|
||||
assert_eq(stethoscope.item_type, ChestItemData.ItemType.OUTFIT)
|
||||
assert_eq(stethoscope.outfit_layer, 2)
|
||||
|
||||
|
||||
func test_are_items_spawned_false_initially() -> void:
|
||||
var chest: RoomChest = RoomChest.new()
|
||||
chest.chest_id = "reception_desk"
|
||||
add_child_autofree(chest)
|
||||
assert_false(chest.are_items_spawned())
|
||||
|
||||
|
||||
func test_get_spawned_count_zero_initially() -> void:
|
||||
var chest: RoomChest = RoomChest.new()
|
||||
chest.chest_id = "reception_desk"
|
||||
add_child_autofree(chest)
|
||||
assert_eq(chest.get_spawned_count(), 0)
|
||||
|
||||
|
||||
func test_get_item_config_count_matches_config() -> void:
|
||||
var chest: RoomChest = RoomChest.new()
|
||||
chest.chest_id = "reception_desk"
|
||||
add_child_autofree(chest)
|
||||
assert_eq(chest.get_item_config_count(), 3)
|
||||
|
||||
|
||||
func test_spawn_items_creates_correct_count() -> void:
|
||||
var chest: RoomChest = RoomChest.new()
|
||||
chest.chest_id = "reception_desk"
|
||||
add_child_autofree(chest)
|
||||
chest.spawn_items()
|
||||
assert_eq(chest.get_spawned_count(), 3)
|
||||
|
||||
|
||||
func test_double_spawn_is_noop() -> void:
|
||||
var chest: RoomChest = RoomChest.new()
|
||||
chest.chest_id = "reception_desk"
|
||||
add_child_autofree(chest)
|
||||
chest.spawn_items()
|
||||
chest.spawn_items()
|
||||
assert_eq(chest.get_spawned_count(), 3)
|
||||
|
||||
|
||||
func test_receive_item_decrements_spawned_count() -> void:
|
||||
var chest: RoomChest = RoomChest.new()
|
||||
chest.chest_id = "reception_desk"
|
||||
add_child_autofree(chest)
|
||||
chest.spawn_items()
|
||||
var item: HoldableItem = chest.get_spawned_item(0)
|
||||
chest.receive_item(item)
|
||||
assert_eq(chest.get_spawned_count(), 2)
|
||||
@@ -0,0 +1,54 @@
|
||||
## Tests for RoomChest presence and item counts in Floor 0 rooms.
|
||||
extends GutTest
|
||||
|
||||
|
||||
func test_reception_has_chest_reception_desk() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/floor0/Reception.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
assert_not_null(room.get_node_or_null("ReceptionDesk"))
|
||||
|
||||
|
||||
func test_reception_desk_has_three_items() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/floor0/Reception.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
var chest: RoomChest = room.get_node_or_null("ReceptionDesk") as RoomChest
|
||||
assert_eq(chest.get_item_config_count(), 3)
|
||||
|
||||
|
||||
func test_giftshop_has_chest_giftshop_shelf() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/floor0/GiftShop.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
assert_not_null(room.get_node_or_null("GiftShopShelf"))
|
||||
|
||||
|
||||
func test_giftshop_shelf_has_three_items() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/floor0/GiftShop.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
var chest: RoomChest = room.get_node_or_null("GiftShopShelf") as RoomChest
|
||||
assert_eq(chest.get_item_config_count(), 3)
|
||||
|
||||
|
||||
func test_restaurant_has_chest_restaurant_counter() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/floor0/Restaurant.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
assert_not_null(room.get_node_or_null("RestaurantCounter"))
|
||||
|
||||
|
||||
func test_restaurant_counter_has_three_items() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/floor0/Restaurant.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
var chest: RoomChest = room.get_node_or_null("RestaurantCounter") as RoomChest
|
||||
assert_eq(chest.get_item_config_count(), 3)
|
||||
|
||||
|
||||
func test_emergency_has_chest_emergency_cabinet() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/floor0/EmergencyRoom.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
assert_not_null(room.get_node_or_null("EmergencyCabinet"))
|
||||
|
||||
|
||||
func test_emergency_cabinet_has_three_items() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/floor0/EmergencyRoom.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
var chest: RoomChest = room.get_node_or_null("EmergencyCabinet") as RoomChest
|
||||
assert_eq(chest.get_item_config_count(), 3)
|
||||
@@ -0,0 +1,67 @@
|
||||
## Tests for RoomChest presence and item counts in Floor 1 rooms.
|
||||
extends GutTest
|
||||
|
||||
|
||||
func test_xray_has_chest_xray_cabinet() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/floor1/XRay.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
assert_not_null(room.get_node_or_null("XRayCabinet"))
|
||||
|
||||
|
||||
func test_xray_cabinet_has_three_items() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/floor1/XRay.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
var chest: RoomChest = room.get_node_or_null("XRayCabinet") as RoomChest
|
||||
assert_eq(chest.get_item_config_count(), 3)
|
||||
|
||||
|
||||
func test_pharmacy_has_chest_pharmacy_medicine() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/floor1/Pharmacy.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
assert_not_null(room.get_node_or_null("PharmacyMedicine"))
|
||||
|
||||
|
||||
func test_pharmacy_medicine_has_two_items() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/floor1/Pharmacy.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
var chest: RoomChest = room.get_node_or_null("PharmacyMedicine") as RoomChest
|
||||
assert_eq(chest.get_item_config_count(), 2)
|
||||
|
||||
|
||||
func test_pharmacy_has_chest_pharmacy_tools() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/floor1/Pharmacy.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
assert_not_null(room.get_node_or_null("PharmacyTools"))
|
||||
|
||||
|
||||
func test_pharmacy_tools_has_two_items() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/floor1/Pharmacy.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
var chest: RoomChest = room.get_node_or_null("PharmacyTools") as RoomChest
|
||||
assert_eq(chest.get_item_config_count(), 2)
|
||||
|
||||
|
||||
func test_lab_has_chest_lab_bench() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/floor1/Lab.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
assert_not_null(room.get_node_or_null("LabBench"))
|
||||
|
||||
|
||||
func test_lab_bench_has_three_items() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/floor1/Lab.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
var chest: RoomChest = room.get_node_or_null("LabBench") as RoomChest
|
||||
assert_eq(chest.get_item_config_count(), 3)
|
||||
|
||||
|
||||
func test_patient_room_has_chest_patient_cabinet() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/floor1/PatientRoom.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
assert_not_null(room.get_node_or_null("PatientCabinet"))
|
||||
|
||||
|
||||
func test_patient_cabinet_has_three_items() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/floor1/PatientRoom.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
var chest: RoomChest = room.get_node_or_null("PatientCabinet") as RoomChest
|
||||
assert_eq(chest.get_item_config_count(), 3)
|
||||
@@ -0,0 +1,67 @@
|
||||
## Tests for RoomChest presence and item counts in Floor 2 and Home rooms.
|
||||
extends GutTest
|
||||
|
||||
|
||||
func test_ultrasound_has_chest_ultrasound_cart() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/floor2/Ultrasound.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
assert_not_null(room.get_node_or_null("UltrasoundCart"))
|
||||
|
||||
|
||||
func test_ultrasound_cart_has_three_items() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/floor2/Ultrasound.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
var chest: RoomChest = room.get_node_or_null("UltrasoundCart") as RoomChest
|
||||
assert_eq(chest.get_item_config_count(), 3)
|
||||
|
||||
|
||||
func test_delivery_room_has_chest_delivery_cabinet() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/floor2/DeliveryRoom.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
assert_not_null(room.get_node_or_null("DeliveryCabinet"))
|
||||
|
||||
|
||||
func test_delivery_cabinet_has_three_items() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/floor2/DeliveryRoom.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
var chest: RoomChest = room.get_node_or_null("DeliveryCabinet") as RoomChest
|
||||
assert_eq(chest.get_item_config_count(), 3)
|
||||
|
||||
|
||||
func test_nursery_has_chest_nursery_shelf() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/floor2/Nursery.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
assert_not_null(room.get_node_or_null("NurseryShelf"))
|
||||
|
||||
|
||||
func test_nursery_shelf_has_three_items() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/floor2/Nursery.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
var chest: RoomChest = room.get_node_or_null("NurseryShelf") as RoomChest
|
||||
assert_eq(chest.get_item_config_count(), 3)
|
||||
|
||||
|
||||
func test_garden_party_has_chest_garden_table() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/home/GardenParty.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
assert_not_null(room.get_node_or_null("GardenTable"))
|
||||
|
||||
|
||||
func test_garden_table_has_two_items() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/home/GardenParty.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
var chest: RoomChest = room.get_node_or_null("GardenTable") as RoomChest
|
||||
assert_eq(chest.get_item_config_count(), 2)
|
||||
|
||||
|
||||
func test_garden_party_has_chest_garden_storage() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/home/GardenParty.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
assert_not_null(room.get_node_or_null("GardenStorage"))
|
||||
|
||||
|
||||
func test_garden_storage_has_two_items() -> void:
|
||||
var room: Node = preload("res://scenes/rooms/home/GardenParty.tscn").instantiate()
|
||||
add_child_autofree(room)
|
||||
var chest: RoomChest = room.get_node_or_null("GardenStorage") as RoomChest
|
||||
assert_eq(chest.get_item_config_count(), 2)
|
||||
Reference in New Issue
Block a user