The Cove's wreck: driftwood and salt on the beach, the captain's sea chest (a treasure map, nails, salt; once each), each player's own X on the map; online too

This commit is contained in:
Paul Bailey
2026-11-07 02:00:31 +00:00
parent ec8caa9ef4
commit 419c71e638
17 changed files with 470 additions and 7 deletions

View File

@@ -55,6 +55,7 @@ var _pieces: Node3D
var hud: CanvasLayer
var pier: Node3D ## the Cove's pier (scripts/world/pier.gd), or null
var tunnel: Node3D ## the cliff tunnel (scripts/world/tunnel.gd, J1), or null
var wreck: Node3D ## the Cove's wreck (scripts/world/wreck.gd, 0.6.0), or null
var sea_cave: Node3D ## the Cove's end of it (scripts/world/sea_cave.gd), or null
var traveling := false ## on the way to another worldlet (the scene is about to change)
var _session_time := 0.0 ## seconds played since the last save
@@ -365,6 +366,15 @@ func _on_terrain_ready() -> void:
props.pier = pr
else:
pr.free()
# The wreck (0.6.0): on the Cove's beach, with the sea chest's treasure maps.
var wk := preload("res://scripts/world/wreck.gd").new()
wk.name = "Wreck"
add_child(wk)
if not menu_mode and wk.place(self):
wreck = wk
props.keep_clear.append([wk.global_position, 9.0])
else:
wk.free()
# The journey's ways on (J1): the Valley's cliff tunnel and the Cove's sea
# cave. On a server too (J3): the server decides the digging and the
# crossings, and every player's game has the same rock to see and walk.
@@ -690,6 +700,7 @@ func collect_save() -> Dictionary:
"barrow": barrow.save_data() if barrow != null else {},
"stones": stones.save_data() if stones != null else {},
"tunnel": tunnel.to_data() if tunnel != null else {},
"wreck": wreck.to_data() if wreck != null else {},
"map": map_data(),
}
@@ -706,6 +717,7 @@ func _shared_state(harvested: Dictionary, regrow: Dictionary, taken: Array, drop
"barrow": barrow.save_data() if barrow != null else {},
"stones": stones.save_data() if stones != null else {},
"tunnel": tunnel.to_data() if tunnel != null else {},
"wreck": wreck.to_data() if wreck != null else {},
}
## The live pieces (placed and not being removed).
@@ -839,6 +851,8 @@ func _apply_save(s: Dictionary, with_player: bool) -> void:
stones.load_data(s.get("stones", {}))
if tunnel != null:
tunnel.load_data(s.get("tunnel", {}))
if wreck != null:
wreck.load_data(s.get("wreck", {}))
if with_player:
load_map_data(String(s.get("map", "")))
# The bear (bear_range.gd): applied when it's set up, after loading.
@@ -971,6 +985,12 @@ func map_marks() -> Array:
add.call("pier", (pier.start + pier.end) * 0.5, "The pier")
if sea_cave != null:
add.call("cave", sea_cave.global_position, "Sea cave")
if wreck != null:
add.call("wreck", wreck.global_position, "The wreck")
# Your X: shown while you carry the map (even where you haven't been).
var x := treasure_spot(my_id())
if x != Vector3.INF and player.inventory.count("treasure_map") > 0:
out.append({"kind": "treasure", "pos": x, "label": "X"})
for piece in all_pieces():
var id: String = String(piece.get("item_id"))
if piece.is_in_group("bed"):
@@ -983,6 +1003,70 @@ func map_marks() -> Array:
add.call("grave", piece.global_position, "Your things")
return out
# --- the pirate treasure (0.6.0, docs/WORLDLETS_DESIGN.md §5) ----------------------
var _treasure_spots: Dictionary = {} ## player id -> Vector3 (worked out once)
## Where player `id`'s treasure is buried (each player's map has its own X):
## a dry, gentle spot in the Cove's hills, picked from the world's seed and
## the player's id, clear of the water, the stream and the other places.
## INF where there's no treasure (no wreck in this land).
func treasure_spot(id: String) -> Vector3:
if wreck == null:
return Vector3.INF
if _treasure_spots.has(id):
return _treasure_spots[id]
var spots := treasure_spots()
var at: Vector3 = Vector3.INF if spots.is_empty() else spots[posmod(hash(id), spots.size())]
_treasure_spots[id] = at
return at
## The world's hiding places for treasure: up to 12, at least 25 m apart,
## picked from its seed (so each player's X is one of these, and different
## players' are usually far apart).
var _hiding: Array = []
func treasure_spots() -> Array:
if not _hiding.is_empty() or wreck == null:
return _hiding
var t: Node3D = terrain
var rng := RandomNumberGenerator.new()
rng.seed = t.noise_seed * 7919 + 2024
var avoid: Array = [wreck.global_position, _spawn]
for n in [den, pier, sea_cave]:
if n != null and n.is_inside_tree():
avoid.append(n.global_position if n != pier else (pier.start + pier.end) * 0.5)
for _try in range(3000):
if _hiding.size() >= 12:
break
var a := rng.randf() * TAU
var r: float = sqrt(rng.randf()) * t.play_radius * 0.85
var x: float = cos(a) * r
var z: float = sin(a) * r
if not t.inside_boundary(x, z, 14.0) or t.beach_weight(x, z) > 0.3 or t.near_stream(x, z, 4.0):
continue
var h: float = t.sample_height(x, z)
if h < t.water_level + 2.0 or t.sample_normal(x, z).y < 0.92 or h > t.snow_height - 6.0:
continue
if avoid.any(func(q): return Vector2(q.x - x, q.z - z).length() < 22.0):
continue
if _hiding.any(func(q): return Vector2(q.x - x, q.z - z).length() < 25.0):
continue
_hiding.append(Vector3(x, h, z))
return _hiding
## The id of the player at this game (yours; a server's character's own).
func my_id() -> String:
return String(Net.get_identity().player_id)
## Right-click with the treasure map: your X goes on your map, and it opens.
func read_map(p: Node3D) -> void:
if wreck == null:
Actions.notify.emit("The X is in the hills above the Cove: read it there")
return
Actions.notify.emit("X marks the spot, in the hills above the bay: it's on your map now (N). Take a pickaxe")
if hud != null and hud.map != null and not hud.map.is_open():
hud.map.open()
## In the cliff tunnel (J1): no daylight, no weather, sheltered.
func underground_at(p: Vector3) -> bool:
return tunnel != null and tunnel.contains(p)