Files
Emberwild/scripts/world/moorings.gd

159 lines
5.6 KiB
GDScript

extends Node3D
## The Cove's moorings (0.6.0, docs/WORLDLETS_DESIGN.md §5): berths along
## the end of the pier, and a mooring post on the deck. Build a rowing boat at
## a workbench, carry it down to the pier and press E at the post: it's tied
## up at a berth of its own, one boat for each player, and it stays there
## (saved with the Cove; online the server decides, and everyone sees
## everyone's boat). Your own boat's prompt is "Set sail", but nobody's
## charted the way to the Highlands yet: that's the next part of the journey,
## and the ship's compass from the pirate chest is what it'll need.
const ItemMeshes = preload("res://scripts/items/item_meshes.gd")
const BoatScript = preload("res://scripts/world/boat.gd")
const PostScript = preload("res://scripts/world/mooring_post.gd")
const BERTHS := 6 ## three each side (any further in, it shallows)
const BOAT_SCALE := 8.0 ## the item's model (0.55 m) up to a boat (4.4 m)
const BERTH_GAP := 5.0
const BOAT_ITEM := "rowing_boat"
var world: Node3D
var pier: Node3D
var moored: Dictionary = {} ## player id -> {"berth": i, "name": who}
var boats: Dictionary = {} ## player id -> boat.gd
var post: StaticBody3D
var _t := 0.0
func setup(w: Node3D) -> bool:
world = w
pier = w.pier
if pier == null:
return false
add_to_group("moorings")
post = StaticBody3D.new()
post.name = "MooringPost"
post.collision_layer = 1 | 2
post.set_script(PostScript)
post.moorings = self
var st := SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLES)
pier._box(st, Vector3(0, 0.45, 0), Vector3(0.24, 0.9, 0.24), pier.POST)
pier._box(st, Vector3(0, 0.8, 0), Vector3(0.32, 0.08, 0.32), pier.POST.darkened(0.2))
pier._box(st, Vector3(0, 0.62, 0), Vector3(0.27, 0.1, 0.27), Color(0.62, 0.55, 0.38)) # (a coil of rope)
st.generate_normals()
var mi := MeshInstance3D.new()
mi.mesh = st.commit()
var mat := StandardMaterial3D.new()
mat.vertex_color_use_as_albedo = true
mat.roughness = 0.9
mi.material_override = mat
post.add_child(mi)
var cs := CollisionShape3D.new()
var box := BoxShape3D.new()
box.size = Vector3(0.3, 0.9, 0.3)
cs.shape = box
cs.position = Vector3(0, 0.45, 0)
post.add_child(cs)
add_child(post)
post.global_position = pier.end - pier.dir * 1.2 + _right() * (pier.WIDTH * 0.5 - 0.3)
return true
func _right() -> Vector3:
return pier.dir.cross(Vector3.UP).normalized()
## Where berth `i` is: alongside the end of the pier, in pairs, bow out to sea.
func berth_xf(i: int) -> Transform3D:
var side := 1.0 if i % 2 == 0 else -1.0
var along: Vector3 = pier.end - pier.dir * (2.0 + (i / 2) * BERTH_GAP)
var p: Vector3 = along + _right() * side * (pier.WIDTH * 0.5 + 1.0)
p.y = world.terrain.sea_level - 0.08
return Transform3D(Basis.looking_at(pier.dir, Vector3.UP), p)
func has_boat(id: String) -> bool:
return moored.has(id)
func _free_berth() -> int:
var used := moored.values().map(func(m): return int(m.berth))
for i in range(BERTHS):
if not i in used:
return i
return -1
## Player `p` (id `id`) ties up the boat in their bag. Says why not and
## returns false when they can't.
func moor(p: Node, id: String) -> bool:
if has_boat(id):
Actions.tell(p, "Your boat's already tied up here")
return false
if p.inventory.count(BOAT_ITEM) <= 0:
Actions.tell(p, "Build a rowing boat at a workbench and bring it here to tie it up")
return false
var b := _free_berth()
if b < 0:
Actions.tell(p, "Every berth along the pier is taken")
return false
p.inventory.remove(BOAT_ITEM, 1)
var who := String(p.get("display")) if p.get("display") != null else ""
moored[id] = {"berth": b, "name": who}
_sync()
Actions.tell(p, "You tie your boat up at the end of the pier")
Sfx.at("creak", post.global_position)
if Net.is_server():
WorldNet.mark_changed()
return true
## Your boat: off to the Highlands. Not yet.
func set_sail(p: Node) -> void:
if p.inventory.count("ship_compass") <= 0:
Actions.tell(p, "Past the bay it's open sea: without a ship's compass you'd be lost within the hour")
return
Actions.tell(p, "The compass needle points north over the water, but nobody's charted the way to the Highlands yet. (That's the next part of the journey: coming in a later version.)")
func _sync() -> void:
for id in boats.keys():
if not moored.has(id):
boats[id].queue_free()
boats.erase(id)
for id in moored:
if boats.has(id):
continue
var b: StaticBody3D = StaticBody3D.new()
b.name = "Boat_%d" % int(moored[id].berth)
b.collision_layer = 1 | 2
b.set_script(BoatScript)
b.moorings = self
b.owner_id = String(id)
var mi := MeshInstance3D.new()
mi.name = "Hull"
mi.mesh = ItemMeshes.build(BOAT_ITEM)
mi.material_override = ItemDB.item_material()
mi.scale = Vector3.ONE * BOAT_SCALE
b.add_child(mi)
var cs := CollisionShape3D.new()
var box := BoxShape3D.new()
box.size = Vector3(1.4, 0.6, 4.2)
cs.shape = box
cs.position = Vector3(0, 0.3, 0)
b.add_child(cs)
add_child(b)
b.global_transform = berth_xf(int(moored[id].berth))
b.rest = b.global_transform
boats[id] = b
## A gentle bob on the swell (just the look).
func _process(delta: float) -> void:
_t += delta
for id in boats:
var b: Node3D = boats[id]
var k := float(int(moored[id].berth)) * 1.7
b.global_transform = b.rest.translated(Vector3(0, sin(_t * 1.1 + k) * 0.05, 0)) * Transform3D(Basis(Vector3(0, 0, 1), sin(_t * 0.8 + k) * 0.03), Vector3.ZERO)
func to_data() -> Dictionary:
return {"moored": moored.duplicate(true)}
func load_data(d: Dictionary) -> void:
moored = {}
var m: Dictionary = d.get("moored", {})
for id in m:
moored[String(id)] = {"berth": int(m[id].get("berth", 0)), "name": String(m[id].get("name", ""))}
_sync()