W3: the Cove (bay, pond, pier for sea fishing), mackerel and cod (cod only in deep water), non-start worldlets offered for testing when run from source
This commit is contained in:
114
scripts/world/pier.gd
Normal file
114
scripts/world/pier.gd
Normal file
@@ -0,0 +1,114 @@
|
||||
extends Node3D
|
||||
## The Cove's pier (W3, docs/WORLDLETS_DESIGN.md §5): a wooden walkway from
|
||||
## the sand straight out over the bay, so a cast from its end reaches deep
|
||||
## sea water (mackerel, and cod further out). Part of the world, like the
|
||||
## barrow: worked out from the terrain alone, so every player and the server
|
||||
## get the same one, and it's never saved, built or dismantled.
|
||||
##
|
||||
## It starts where the beach has risen to deck height (so you walk straight
|
||||
## on) and runs `LENGTH_OUT` metres past the waterline.
|
||||
|
||||
const WIDTH := 2.4
|
||||
const DECK_ABOVE := 1.0 ## deck height over the water
|
||||
const LENGTH_OUT := 24.0 ## how far past the waterline it reaches
|
||||
const POST_EVERY := 3.0
|
||||
const WOOD := Color(0.42, 0.30, 0.19)
|
||||
const POST := Color(0.30, 0.22, 0.15)
|
||||
|
||||
var start := Vector3.ZERO ## the landward end, on the sand
|
||||
var end := Vector3.ZERO ## the seaward end (fish from here)
|
||||
var dir := Vector3.ZERO ## out to sea along the deck
|
||||
|
||||
## Lays the pier off the terrain's beach. Returns false (and adds nothing)
|
||||
## when the worldlet has no beach.
|
||||
func place(terrain: Node3D) -> bool:
|
||||
if not terrain._has_beach or not terrain.worldlet.get("places", []).any(func(p): return p is Dictionary and p.get("type", "") == "pier"):
|
||||
return false
|
||||
var deck_y: float = terrain.water_level + DECK_ABOVE
|
||||
var out2: Vector2 = terrain.shore_point(LENGTH_OUT)
|
||||
var root2: Vector2 = terrain.shore_point(-2.0)
|
||||
var d2 := (out2 - root2).normalized()
|
||||
# Walk inland from the waterline to where the sand is at deck height.
|
||||
var s2 := root2
|
||||
for _i in range(40):
|
||||
if terrain.sample_height(s2.x, s2.y) >= deck_y - 0.15:
|
||||
break
|
||||
s2 -= d2 * 0.5
|
||||
start = Vector3(s2.x, deck_y, s2.y)
|
||||
end = Vector3(out2.x, deck_y, out2.y)
|
||||
dir = (end - start).normalized()
|
||||
_build(terrain)
|
||||
return true
|
||||
|
||||
## Whether (x, z) is under or right beside the pier (scenery keeps clear).
|
||||
func covers(x: float, z: float, margin: float = 1.0) -> bool:
|
||||
var a := Vector2(start.x, start.z)
|
||||
var b := Vector2(end.x, end.z)
|
||||
var p := Vector2(x, z)
|
||||
var t := clampf((p - a).dot(b - a) / maxf((b - a).length_squared(), 0.001), 0.0, 1.0)
|
||||
return p.distance_to(a.lerp(b, t)) < WIDTH * 0.5 + margin
|
||||
|
||||
func _build(terrain: Node3D) -> void:
|
||||
var length := start.distance_to(end)
|
||||
var yaw := atan2(dir.x, dir.z)
|
||||
var basis := Basis(Vector3.UP, yaw)
|
||||
var mid := (start + end) * 0.5
|
||||
# The deck: planks across it, with a little gap and wobble between them.
|
||||
var st := SurfaceTool.new()
|
||||
st.begin(Mesh.PRIMITIVE_TRIANGLES)
|
||||
var n := int(length / 0.32)
|
||||
var rng := RandomNumberGenerator.new()
|
||||
rng.seed = 7171
|
||||
for i in range(n):
|
||||
var z := -length * 0.5 + (i + 0.5) * length / n
|
||||
var h := 0.08 + rng.randf_range(-0.01, 0.01)
|
||||
_box(st, Vector3(rng.randf_range(-0.04, 0.04), -h * 0.5, z), Vector3(WIDTH, h, length / n - 0.03),
|
||||
WOOD.darkened(rng.randf_range(0.0, 0.18)))
|
||||
# Posts down to the bed, in pairs, and a rail beam along each side.
|
||||
var k := 0.0
|
||||
while k <= length + 0.01:
|
||||
var along := -length * 0.5 + k
|
||||
for side in [-1.0, 1.0]:
|
||||
var local := Vector3(side * (WIDTH * 0.5 - 0.1), 0.0, along)
|
||||
var world_p: Vector3 = mid + basis * local
|
||||
var bed: float = terrain.sample_height(world_p.x, world_p.z)
|
||||
var tall := start.y - bed + 0.3
|
||||
if tall > 0.2:
|
||||
_box(st, local + Vector3(0, -tall * 0.5 - 0.05, 0), Vector3(0.22, tall, 0.22), POST)
|
||||
k += POST_EVERY
|
||||
for side in [-1.0, 1.0]:
|
||||
_box(st, Vector3(side * (WIDTH * 0.5 - 0.06), -0.16, 0.0), Vector3(0.12, 0.14, length), POST)
|
||||
st.generate_normals()
|
||||
var mi := MeshInstance3D.new()
|
||||
mi.name = "Deck"
|
||||
mi.mesh = st.commit()
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.vertex_color_use_as_albedo = true
|
||||
mat.roughness = 0.9
|
||||
mi.material_override = mat
|
||||
add_child(mi)
|
||||
# Walkable deck (on the ground layer, like the terrain).
|
||||
var body := StaticBody3D.new()
|
||||
body.name = "DeckBody"
|
||||
var cs := CollisionShape3D.new()
|
||||
var box := BoxShape3D.new()
|
||||
box.size = Vector3(WIDTH, 0.12, length)
|
||||
cs.shape = box
|
||||
cs.position = Vector3(0, -0.06, 0)
|
||||
body.add_child(cs)
|
||||
add_child(body)
|
||||
transform = Transform3D(basis, mid)
|
||||
add_to_group("pier")
|
||||
|
||||
static func _box(st: SurfaceTool, c: Vector3, s: Vector3, col: Color) -> void:
|
||||
var h := s * 0.5
|
||||
var v := [
|
||||
c + Vector3(-h.x, -h.y, -h.z), c + Vector3(h.x, -h.y, -h.z), c + Vector3(h.x, h.y, -h.z), c + Vector3(-h.x, h.y, -h.z),
|
||||
c + Vector3(-h.x, -h.y, h.z), c + Vector3(h.x, -h.y, h.z), c + Vector3(h.x, h.y, h.z), c + Vector3(-h.x, h.y, h.z),
|
||||
]
|
||||
var faces := [[0, 3, 2, 1], [4, 5, 6, 7], [0, 4, 7, 3], [1, 2, 6, 5], [3, 7, 6, 2], [0, 1, 5, 4]]
|
||||
st.set_color(col)
|
||||
for f in faces:
|
||||
# (listed anticlockwise from outside; Godot's front faces are clockwise)
|
||||
st.add_vertex(v[f[0]]); st.add_vertex(v[f[2]]); st.add_vertex(v[f[1]])
|
||||
st.add_vertex(v[f[0]]); st.add_vertex(v[f[3]]); st.add_vertex(v[f[2]])
|
||||
1
scripts/world/pier.gd.uid
Normal file
1
scripts/world/pier.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ps0h2qmbn5ms
|
||||
@@ -31,6 +31,7 @@ const FLOWER_COLORS := [
|
||||
@export var grass_tiles_per_update: int = 6
|
||||
|
||||
var terrain: Node3D
|
||||
var pier: Node3D ## the Cove's pier, kept clear of scenery (set by the world)
|
||||
var _player: Node3D
|
||||
var _spawn := Vector3.ZERO
|
||||
var _forest_noise := FastNoiseLite.new()
|
||||
@@ -417,7 +418,10 @@ func _scatter_trees() -> void:
|
||||
|
||||
## Open grassy strip along the sea-cliff edge: no trees, bushes or boulders, so
|
||||
## the view out to sea is clear and you can walk right up to the edge.
|
||||
## (Also kept clear: the Cove's pier and the sand by its foot.)
|
||||
func _on_clifftop(x: float, z: float) -> bool:
|
||||
if pier != null and pier.covers(x, z, 2.0):
|
||||
return true
|
||||
return terrain.sea_weight(x, z) > 0.5 and not terrain.inside_boundary(x, z, 10.0) \
|
||||
and terrain.inside_boundary(x, z, -8.0)
|
||||
|
||||
@@ -486,7 +490,7 @@ func _scatter_rocks() -> void:
|
||||
# Sea stacks: tall pillars standing off the cliff (no collision, unreachable).
|
||||
var placed := 0
|
||||
var tries := 0
|
||||
while placed < 9 and tries < 200:
|
||||
while placed < 9 and tries < 200 and terrain._has_sea: # (only off a sea cliff)
|
||||
tries += 1
|
||||
var ang: float = terrain.sea_angle_deg + rng.randf_range(-1.0, 1.0) * (terrain.sea_half_width_deg + 8.0)
|
||||
var p: Vector2 = terrain.coast_point(ang, rng.randf_range(10.0, 48.0))
|
||||
@@ -672,7 +676,7 @@ func _scatter_pickups() -> void:
|
||||
continue
|
||||
var h: float = terrain.sample_height(x, z)
|
||||
var n: Vector3 = terrain.sample_normal(x, z)
|
||||
if h < terrain.water_level + 0.3 or n.y < 0.8:
|
||||
if h < terrain.water_level + 0.3 or n.y < 0.8 or (pier != null and pier.covers(x, z, 1.0)):
|
||||
continue
|
||||
var chance := 1.0
|
||||
match id:
|
||||
|
||||
@@ -53,6 +53,7 @@ var _spawn := Vector3.ZERO
|
||||
var _spawn_yaw := 0.0
|
||||
var _pieces: Node3D
|
||||
var hud: CanvasLayer
|
||||
var pier: Node3D ## the Cove's pier (scripts/world/pier.gd), or null
|
||||
var _session_time := 0.0 ## seconds played since the last save
|
||||
var _autosave_left := AUTOSAVE_SECONDS
|
||||
var _menu_cam: Camera3D
|
||||
@@ -352,6 +353,15 @@ func _on_terrain_ready() -> void:
|
||||
var to_lake := lake - Vector2(_spawn.x, _spawn.z)
|
||||
_spawn_yaw = atan2(-to_lake.x, -to_lake.y)
|
||||
player.water_level = terrain.water_level
|
||||
# The pier (W3): off a beach, laid before the scenery so it keeps clear.
|
||||
var pr := preload("res://scripts/world/pier.gd").new()
|
||||
pr.name = "Pier"
|
||||
add_child(pr)
|
||||
if pr.place(terrain):
|
||||
pier = pr
|
||||
props.pier = pr
|
||||
else:
|
||||
pr.free()
|
||||
props.place_on(terrain, player if not menu_mode else _menu_cam, _spawn)
|
||||
# The wolf den: part of the scenery, so it's there from day 1 (empty).
|
||||
den = preload("res://scripts/creatures/den.gd").new()
|
||||
|
||||
Reference in New Issue
Block a user