diff --git a/data/worldlets/cove.json b/data/worldlets/cove.json index 4be5492..4b70f45 100644 --- a/data/worldlets/cove.json +++ b/data/worldlets/cove.json @@ -15,7 +15,7 @@ {"type": "ocean_edge"} ], "climate": {"snowline": 42.0}, - "places": [{"type": "pier", "at": "beach"}], + "places": [{"type": "den"}, {"type": "pier", "at": "beach"}], "next": [ {"to": "valley", "by": "tunnel", "where": "sea_cave"}, {"to": "highlands", "by": "sail", "where": "pier"} diff --git a/data/worldlets/valley.json b/data/worldlets/valley.json index a951f03..71c7482 100644 --- a/data/worldlets/valley.json +++ b/data/worldlets/valley.json @@ -14,5 +14,6 @@ {"type": "ocean_edge"} ], "climate": {"snowline": 40.0}, + "places": [{"type": "den"}, {"type": "barrow"}, {"type": "stones"}, {"type": "bear"}], "next": [{"to": "cove", "by": "tunnel", "where": "sea_cliff"}] } diff --git a/docs/WORLDLETS_DESIGN.md b/docs/WORLDLETS_DESIGN.md index 63c4021..55b02f9 100644 --- a/docs/WORLDLETS_DESIGN.md +++ b/docs/WORLDLETS_DESIGN.md @@ -283,6 +283,46 @@ The steps: - Later: tides (sea level shifting slowly), a raft or rowing boat, and driftwood on the beach. +### The Cove's own dangers and the pirate treasure (Paul, Nov 2026, for 0.6.0) +Paused at the Cove for a playtest before the Highlands. The Cove gets its +own set pieces instead of copies of the Valley's: +- **Set pieces per land**, listed in each worldlet's data (`"places"`). The + Valley keeps the wolf den, the barrow, the standing stones (the Old Stag) + and the bear; the Cove keeps the wolves only, and gets the wreck and the + treasure. +- **The wreck:** a beached ship on the rocks at the far end of the bay, its + timbers along the sand (driftwood). Its captain's sea chest: some nails, + rope, coins and a **torn treasure map**. +- **The map:** reading it puts an X on your N map: a mound of loose earth + by a marker stone in the Cove's hills (your own spot: see below). +- **The dig:** a pickaxe, many blows, turns up the **pirate chest**: a + **ship's compass** (what the boat will need to sail to the Highlands: the + next crossing's gate, J2), a **spyglass** (zooms your view while held), + **pirate gold**, and a **cutlass** (quick, harder-hitting than the iron + spear). +- **One treasure each, each in its own place** (online too): the wreck's + chest gives each player their own map, once, and each map's X is + somewhere different: picked from the world's seed and the player's id + (so it's fixed for them), among dry, gentle spots in the Cove's hills + away from the other places. You only see your own mound; the server knows + every player's spot and what each has found, and checks the right one + when you dig. +- **Gold** is a **trophy you can place** in your hut (a heap of coins) for + now; what else it's for waits until there's a trader. +- **The drowned crew:** the treasure's guards (the Cove's barrow guards). + Two or three rise from the shallows round the dig at night, or as the + chest comes up, for whoever's digging. Slow, hit hard, unafraid of fire, + they drag at you in the water. +- **Crabs** on the beach at dusk (pinch if trodden on; food), and **gulls**, + thieves like the crows (fish off drying racks, things dropped on sand). +- **The boat** (J2's first part): driftwood, seagrass rope and pitch (pine + resin and salt), at a workbench, placed at the pier. One each, moored, + yours and your friends'. Setting sail says the Highlands are uncharted + for now. +- **Report a bug** in the pause menu, and a short playtest guide. +- Order: set pieces per land, the wreck and map, the dig and the drowned + crew, crabs and gulls, the boat, report a bug; then 0.6.0 (protocol 18). + ## 6. Done already (groundwork) - `world.water_at(x, z)` returns `{type, surface, depth}` for the lake and the sea, and fishing uses it instead of `terrain.water_level`. diff --git a/scripts/world/world.gd b/scripts/world/world.gd index 1a1abee..733b131 100644 --- a/scripts/world/world.gd +++ b/scripts/world/world.gd @@ -394,18 +394,21 @@ func _on_terrain_ready() -> void: props.static_cuts.append([den.global_transform, 2.2, 3.4]) _update_grass_cuts() # The barrow: scenery too (P3), out on open ground. Its guards only run - # where creatures do (_setup_bear). - barrow = preload("res://scripts/world/barrow.gd").new() - barrow.name = "Barrow" - add_child(barrow) - if barrow.place(self): - _update_grass_cuts() + # where creatures do (_setup_bear). (Each land has its own set pieces: + # the barrow and the stones are the Valley's: has_place().) + if has_place("barrow"): + barrow = preload("res://scripts/world/barrow.gd").new() + barrow.name = "Barrow" + add_child(barrow) + if barrow.place(self): + _update_grass_cuts() # The standing stones (P5): where the Old Stag is called. - stones = preload("res://scripts/world/standing_stones.gd").new() - stones.name = "StandingStones" - add_child(stones) - if stones.place(self): - _update_grass_cuts() + if has_place("stones"): + stones = preload("res://scripts/world/standing_stones.gd").new() + stones.name = "StandingStones" + add_child(stones) + if stones.place(self): + _update_grass_cuts() if DisplayServer.get_name() != "headless": var fish := preload("res://scripts/fishing/lake_fish.gd").new() fish.name = "LakeFish" @@ -420,7 +423,8 @@ func _on_terrain_ready() -> void: hazards.name = "Hazards" add_child(hazards) hazards.setup(self, not is_online()) - stones.deciding = not is_online() + if stones != null: + stones.deciding = not is_online() if server_mode: if Saves.has_world(): var saved: Dictionary = Saves.load_state(Saves.current_id) @@ -491,6 +495,8 @@ func _setup_bear() -> void: thieves.enabled = not "--no-creatures" in OS.get_cmdline_user_args() if barrow != null and barrow.found and thieves.enabled: barrow.start() + if not has_place("bear"): + return # (bears only in the lands that list one: the Valley) bears = preload("res://scripts/creatures/bear_range.gd").new() bears.name = "Bear" add_child(bears) @@ -1202,6 +1208,16 @@ func water_at(x: float, z: float) -> Dictionary: # and its ways out. (W0 adds these; systems move over to them as each worldlet # needs them to be different.) +## Whether this land has set piece `kind` ("den", "barrow", "stones", +## "bear", "pier", "wreck"...): its worldlet's "places". A worldlet that +## doesn't list them has the Valley's set (den, barrow, stones, bear). +const DEFAULT_PLACES := ["den", "barrow", "stones", "bear"] +func has_place(kind: String) -> bool: + var places: Array = terrain.worldlet.get("places", []) + if places.is_empty(): + return kind in DEFAULT_PLACES + return places.any(func(p): return p is Dictionary and String(p.get("type", "")) == kind) + ## This worldlet's id ("valley") and its whole definition. func worldlet_id() -> String: return terrain.worldlet_id diff --git a/tests/test_cove.gd b/tests/test_cove.gd index 03b73bc..e265cd0 100644 --- a/tests/test_cove.gd +++ b/tests/test_cove.gd @@ -59,7 +59,9 @@ func _body() -> void: check(world.crossings().any(func(c): return c.to == "valley") and world.crossings().any(func(c): return c.by == "sail"), "its ways on: back to the Valley, and on by sail") check(world._spawn.y > t.water_level + 1.0, "you start on dry land") - check(world.den != null and world.barrow != null and world.stones != null, "the den, the barrow and the stones found places") + check(world.den != null and world.den.found, "the Cove's wolves have a den") + check(world.barrow == null and world.stones == null and world.bears == null, + "but the barrow, the standing stones and the bear are the Valley's alone") check(t.get_node_or_null("Water") == null, "one water surface (the bay is the sea)") print("the stream") @@ -77,9 +79,9 @@ func _body() -> void: "its banks are dry, and above the water") check(DB.water_fishing("stream").get("fish", []).any(func(f): return f.id == "trout"), "trout in the stream") var away := true - for p in [world.den.global_position, world.barrow.global_position, world.stones.global_position, world._spawn]: + for p in [world.den.global_position, world._spawn]: away = away and not t.near_stream(p.x, p.z, 3.0) - check(away, "the den, the barrow, the stones and the start are all clear of it") + check(away, "the den and the start are clear of it") var in_bed := 0 for n in world.props.resource_nodes.values(): if is_instance_valid(n) and t.near_stream(n.global_position.x, n.global_position.z, 0.0):