120 lines
5.0 KiB
GDScript
120 lines
5.0 KiB
GDScript
extends Node3D
|
|
## The Cove's sea cave (J1): the bottom of the Valley's cliff tunnel, seen
|
|
## from the shore. A dark opening in the rocks at the end of the beach,
|
|
## where the headland meets the sand. [E] goes in and up the tunnel, back to
|
|
## the Valley, once that tunnel has been dug through from the top. (Until
|
|
## then the cave runs back into solid rock.)
|
|
##
|
|
## Placed from the terrain alone, like the pier, so it's the same for
|
|
## everyone; nothing about it is saved here (the tunnel is the Valley's).
|
|
|
|
const RockPile = preload("res://scripts/world/rock_pile.gd")
|
|
|
|
var world: Node3D
|
|
var to := "valley"
|
|
var front := Vector3.ZERO ## where you stand in front of it (arriving from the tunnel)
|
|
|
|
## Places the cave if this worldlet has a way back by tunnel from a sea cave.
|
|
func place(w: Node3D) -> bool:
|
|
world = w
|
|
var t: Node3D = w.terrain
|
|
var way: Dictionary = {}
|
|
for c in w.crossings():
|
|
if c is Dictionary and c.get("by", "") == "tunnel" and c.get("where", "") == "sea_cave":
|
|
way = c
|
|
if way.is_empty() or not t._has_beach:
|
|
return false
|
|
to = String(way.to)
|
|
# At the end of the beach, where the headland starts: the first dry,
|
|
# gentle spot coming in from the waterline.
|
|
var a := deg_to_rad(t.beach_angle_deg + t.beach_half_width_deg + t.beach_fade_deg * 0.35)
|
|
var dir := Vector2(cos(a), sin(a))
|
|
var at := Vector3.INF
|
|
for i in range(60):
|
|
var r: float = t._shore_d() - 2.0 - i * 0.5 - t._rim_offset(dir.x, dir.y)
|
|
var p := dir * r
|
|
var h: float = t.sample_height(p.x, p.y)
|
|
if h > t.water_level + 0.5 and t.sample_normal(p.x, p.y).y > 0.86 and t.inside_boundary(p.x, p.y, 3.0):
|
|
at = Vector3(p.x, h, p.y)
|
|
break
|
|
if at == Vector3.INF:
|
|
return false
|
|
# Facing back along the beach (towards its middle), so you see it from the sand.
|
|
var mid: Vector2 = t.shore_point(-6.0)
|
|
var face := Vector3(mid.x - at.x, 0.0, mid.y - at.z).normalized()
|
|
global_transform = Transform3D(Basis.looking_at(face, Vector3.UP), at)
|
|
front = at + face * 2.4
|
|
front.y = t.sample_height(front.x, front.z)
|
|
_build(w)
|
|
add_to_group("sea_cave")
|
|
return true
|
|
|
|
## Whether the Valley's tunnel has been dug right down to here.
|
|
func open() -> bool:
|
|
var full: int = preload("res://scripts/world/tunnel.gd").STRETCHES
|
|
if not Lands.land.is_empty():
|
|
return Lands.tunnel_dug >= full # (a land server: the hub says how far it's dug)
|
|
if world.is_online():
|
|
return int(Net.world_info.get("tunnel_dug", 0)) >= full # (as the server said when we came in)
|
|
if not Saves.has_world():
|
|
return false
|
|
var s: Dictionary = Saves.load_state(Saves.current_id, to)
|
|
return int(s.get("tunnel", {}).get("dug", 0)) >= preload("res://scripts/world/tunnel.gd").STRETCHES
|
|
|
|
func interact_prompt(_player: Node) -> String:
|
|
if open():
|
|
return "[E] Go into the sea cave and up the tunnel to the %s" % preload("res://scripts/world/terrain.gd").worldlet_name(to)
|
|
return "A sea cave. It runs back into solid rock: the way up hasn't been dug"
|
|
|
|
func interact(_player: Node) -> void:
|
|
if open():
|
|
world.travel(to, "tunnel_bottom")
|
|
else:
|
|
Actions.notify.emit(interact_prompt(null))
|
|
|
|
func _build(w: Node3D) -> void:
|
|
var rock: ShaderMaterial = RockPile.material(w.terrain)
|
|
var body := StaticBody3D.new()
|
|
add_child(body)
|
|
# A heap of big boulders at the foot of the headland, with a dark mouth
|
|
# between them (local -Z faces the beach; the heap is behind, +Z), and
|
|
# seaweed-dark stones scattered on the sand in front.
|
|
var s: int = w.terrain.noise_seed + 5100
|
|
for part in [[Vector3(-2.4, 1.3, 1.0), Vector3(1.3, 2.1, 1.7), 0.5, true],
|
|
[Vector3(2.4, 1.15, 1.0), Vector3(1.25, 1.9, 1.6), 2.0, true],
|
|
[Vector3(0.0, 3.75, 1.2), Vector3(2.9, 1.0, 1.9), 0.3, false],
|
|
[Vector3(0.0, 1.8, 3.2), Vector3(2.4, 2.4, 1.4), 1.1, false],
|
|
[Vector3(-4.3, 0.8, 2.0), Vector3(1.8, 1.5, 1.8), 2.6, false],
|
|
[Vector3(4.1, 0.6, 2.2), Vector3(1.6, 1.3, 1.7), 0.7, false],
|
|
[Vector3(-2.0, 4.4, 2.8), Vector3(1.8, 1.2, 1.6), 1.7, false],
|
|
[Vector3(2.3, 4.1, 3.0), Vector3(1.6, 1.1, 1.5), 2.9, false]]:
|
|
RockPile.add(self, body, s, part[0], part[1], part[2], rock, part[3])
|
|
s += 1
|
|
for stone in [[Vector3(-1.4, 0.08, -1.6), 0.4], [Vector3(1.6, 0.06, -2.2), 0.3],
|
|
[Vector3(-3.2, 0.06, -1.0), 0.35], [Vector3(0.4, 0.05, -2.9), 0.22], [Vector3(3.4, 0.05, -1.2), 0.28]]:
|
|
RockPile.add(self, null, s, stone[0], Vector3.ONE * float(stone[1]), float(s), rock)
|
|
s += 1
|
|
var dark := MeshInstance3D.new()
|
|
var bm := BoxMesh.new()
|
|
bm.size = Vector3(2.0, 2.6, 0.1)
|
|
dark.mesh = bm
|
|
var m := StandardMaterial3D.new()
|
|
m.albedo_color = Color(0.015, 0.014, 0.018)
|
|
m.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
|
dark.material_override = m
|
|
dark.position = Vector3(0, 1.3, 1.6)
|
|
add_child(dark)
|
|
# What you press E on: the opening (interactables layer: you walk into it).
|
|
var spot := StaticBody3D.new()
|
|
spot.collision_layer = 2
|
|
spot.collision_mask = 0
|
|
var cs2 := CollisionShape3D.new()
|
|
var b2 := BoxShape3D.new()
|
|
b2.size = Vector3(2.2, 2.8, 1.6)
|
|
cs2.shape = b2
|
|
spot.add_child(cs2)
|
|
spot.position = Vector3(0, 1.4, 1.2)
|
|
spot.set_script(preload("res://scripts/world/sea_cave_spot.gd"))
|
|
spot.cave = self
|
|
add_child(spot)
|