extends Node3D ## The Cove's wreck (docs/WORLDLETS_DESIGN.md ยง5, 0.6.0): an old ship driven ## up on the sand at the far end of the bay from the sea cave, her bow in the ## shallows, one side stove in. Inside, the captain's sea chest: a torn ## treasure map and a few useful things, **once for each player** (single- ## player and online alike: who's had theirs is saved with the Cove). ## ## Placed from the terrain alone, like the pier, so it's the same for ## everyone; only `opened` (who's had their share) is saved. const Pier = preload("res://scripts/world/pier.gd") const OAK := Color(0.36, 0.25, 0.16) const DARK := Color(0.24, 0.17, 0.11) const WET := Color(0.2, 0.18, 0.15) const IRON := Color(0.22, 0.22, 0.24) ## What's in the sea chest, for each player once. const LOOT := {"treasure_map": 1, "nails": 4, "salt": 2} var world: Node3D var opened: Array = [] ## the player ids who've had their share var chest: StaticBody3D var front := Vector3.ZERO ## where you stand to reach into the chest func place(w: Node3D) -> bool: world = w var t: Node3D = w.terrain if not w.has_place("wreck") or not t._has_beach: return false # The far end of the beach from the sea cave, where the sand meets the water. var a := deg_to_rad(t.beach_angle_deg - t.beach_half_width_deg * 0.8) var dir := Vector2(cos(a), sin(a)) var r: float = t._shore_d() - t._rim_offset(dir.x, dir.y) var at2 := dir * (r - 1.0) var at := Vector3(at2.x, maxf(t.sample_height(at2.x, at2.y), t.water_level - 0.3), at2.y) # Bow out to sea (+Z local), a little askew, heeled over on her side. var out := Vector3(dir.x, 0.0, dir.y).rotated(Vector3.UP, 0.35) global_transform = Transform3D(Basis.looking_at(-out, Vector3.UP), at) _build() add_to_group("wreck") return true func _build() -> void: var st := SurfaceTool.new() st.begin(Mesh.PRIMITIVE_TRIANGLES) var body := StaticBody3D.new() body.name = "Hull" add_child(body) var heel := Basis(Vector3(0, 0, 1), deg_to_rad(-14.0)) var parts: Array = [ # [centre, size, colour] in the hull's frame (+Z the bow, +X starboard) [Vector3(0, 0.12, 0), Vector3(0.35, 0.25, 9.0), DARK], # keel [Vector3(-1.45, 0.75, 0), Vector3(0.14, 1.3, 8.2), OAK], # port side, whole [Vector3(1.45, 0.75, 2.6), Vector3(0.14, 1.3, 3.0), OAK], # starboard, forward [Vector3(1.45, 0.55, -3.0), Vector3(0.14, 0.9, 2.2), OAK], # starboard, aft (stove in between) [Vector3(0, 0.75, -4.1), Vector3(2.9, 1.3, 0.14), OAK], # transom [Vector3(0, 0.05, 0), Vector3(2.8, 0.1, 7.6), WET], # bilge boards [Vector3(0, 1.8, 1.4), Vector3(0.22, 2.6, 0.22), DARK], # mast stump ] for p in parts: var c: Vector3 = heel * p[0] Pier._box(st, c, p[1], p[2]) var cs := CollisionShape3D.new() var box := BoxShape3D.new() box.size = p[1] cs.shape = box cs.transform = Transform3D(heel, c) body.add_child(cs) # Ribs over the open hull, some broken off short. for i in range(6): var z := -3.4 + i * 1.3 for side in [-1.0, 1.0]: if side > 0.0 and z > -1.8 and z < 1.0: continue # (the stove-in side) var h := 1.9 if (i + int(side)) % 3 != 0 else 1.2 Pier._box(st, heel * Vector3(side * 1.3, h * 0.5 + 0.5, z), Vector3(0.12, h, 0.14), DARK) # Planks strewn on the sand on the open side. var rng := RandomNumberGenerator.new() rng.seed = 919 for i in range(5): var off := Vector3(2.6 + rng.randf() * 2.5, 0.05, -2.0 + rng.randf() * 4.0) Pier._box(st, off, Vector3(0.3, 0.06, 1.6 + rng.randf()), OAK.darkened(rng.randf() * 0.3)) st.generate_normals() var mi := MeshInstance3D.new() mi.name = "Ship" mi.mesh = st.commit() var mat := StandardMaterial3D.new() mat.vertex_color_use_as_albedo = true mat.roughness = 0.95 mi.material_override = mat add_child(mi) # The captain's sea chest, wedged inside by the break. chest = StaticBody3D.new() chest.name = "SeaChest" chest.collision_layer = 1 | 2 chest.set_script(preload("res://scripts/world/wreck_chest.gd")) chest.wreck = self var cst := SurfaceTool.new() cst.begin(Mesh.PRIMITIVE_TRIANGLES) Pier._box(cst, Vector3(0, 0.25, 0), Vector3(0.9, 0.5, 0.55), Color(0.3, 0.2, 0.12)) Pier._box(cst, Vector3(0, 0.55, 0), Vector3(0.92, 0.12, 0.57), Color(0.27, 0.18, 0.1)) for x in [-0.32, 0.32]: Pier._box(cst, Vector3(x, 0.32, 0), Vector3(0.06, 0.64, 0.6), IRON) cst.generate_normals() var cmi := MeshInstance3D.new() cmi.mesh = cst.commit() cmi.material_override = mat chest.add_child(cmi) var ccs := CollisionShape3D.new() var cb := BoxShape3D.new() cb.size = Vector3(0.9, 0.62, 0.55) ccs.shape = cb ccs.position = Vector3(0, 0.31, 0) chest.add_child(ccs) add_child(chest) chest.transform = Transform3D(heel * Basis(Vector3.UP, 0.4), heel * Vector3(0.5, 0.12, -0.4)) front = global_transform * (heel * Vector3(2.4, 0.0, -0.4)) front.y = world.terrain.sample_height(front.x, front.z) # --- the sea chest ------------------------------------------------------------- ## Whether player `id` has had their share of the chest. func had(id: String) -> bool: return id in opened ## Gives player `p` (id `id`) their share: the map and the rest, once. ## Returns false (and says why) when they've had it, or their bag's too full. func open_for(p: Node, id: String) -> bool: if had(id): Actions.tell(p, "The sea chest's empty: you've had what was in it") return false for item in LOOT: if p.inventory.space_for(item) < int(LOOT[item]): Actions.tell(p, "Make room in your bag first") return false for item in LOOT: p.inventory.add(item, int(LOOT[item])) opened.append(id) Actions.tell(p, "In the captain's sea chest: a torn treasure map, %d nails and %d salt. Right-click the map to read it" % [ int(LOOT.nails), int(LOOT.salt)]) Sfx.at("door_open", chest.global_position) if Net.is_server(): WorldNet.mark_changed() return true func to_data() -> Dictionary: return {"opened": opened.duplicate()} func load_data(d: Dictionary) -> void: opened = (d.get("opened", []) as Array).duplicate()