J3a: the journey on a server. A server per land (Lands: the hub starts land servers on demand over a private link), the cliff tunnel online, crossings with one-time passes, shared characters that remember their land, one clock from the hub; test_net_lands

This commit is contained in:
Paul Bailey
2026-11-06 23:14:11 +00:00
parent 6544862885
commit 85bbed249b
17 changed files with 1050 additions and 28 deletions

View File

@@ -79,7 +79,15 @@ func server_join(peer: int, id: String, display: String) -> Vector3:
p.load_data(d)
var t: Node3D = WorldNet.world.terrain
var pos := Vector3(d.pos[0], d.pos[1], d.pos[2]) if d.has("pos") else Vector3.INF
if pos.is_finite() and t.inside_boundary(pos.x, pos.z, 1.0) and pos.y > t.sample_height(pos.x, pos.z) - 1.0:
var here_ := String(d.get("land", Lands.here())) == Lands.here()
if here_ and d.has("arrive"):
# Just crossed over: where the way in comes out (J3).
pos = WorldNet.world.arrival_point(String(d.arrive))
elif not here_:
pos = Vector3.INF # (saved somewhere else: start at this land's start)
var ok_spot: bool = pos.is_finite() and (WorldNet.world.underground_at(pos) \
or (t.inside_boundary(pos.x, pos.z, 1.0) and pos.y > t.sample_height(pos.x, pos.z) - 1.0))
if ok_spot:
at = pos
print("[server] %s's character loaded" % display)
if at != Vector3.INF:
@@ -127,10 +135,54 @@ func _save(p: Node3D) -> void:
if f == null:
printerr("[server] can't save %s's character" % p.display)
return
f.store_string(JSON.stringify(p.to_data(), "\t"))
var d: Dictionary = p.to_data()
# Which land the character's in (J3: characters are shared by every land
# of the world); heading somewhere, it's there they'll turn up next.
d["land"] = String(p.get_meta("heading", Lands.here()))
if p.has_meta("arrive"):
d["arrive"] = String(p.get_meta("arrive"))
f.store_string(JSON.stringify(d, "\t"))
f.close()
DirAccess.rename_absolute(tmp, path)
## Which land player `id`'s character is in ("" = not saved yet, or here).
func land_of(id: String) -> String:
return String(_read(_file(id)).get("land", ""))
## The hub: player `id` (at `peer`, not let in yet) left their character in
## land `l`: start its server if need be and send them on there.
func send_on(peer: int, id: String, l: String) -> void:
print("[server] %s's character is in the %s: sending them on" % [id.substr(0, 8), l])
Lands.ensure(l, func(why: String):
if not why.is_empty():
Net._reject(peer, "The %s isn't open right now (%s)" % [l, why])
return
Net.send_travel(peer, l, Lands.pass_to(l, id)))
## A crossing, on the server (the tunnel's sea cave, the cave back up): the
## character is saved as heading for `to`, arriving at `arrive`, and their
## game is sent on (to a land server, or back to the hub).
func cross(peer: int, to: String, arrive: String) -> void:
var p: Node3D = proxies.get(peer)
if p == null or p.has_meta("heading"):
return
p.set_meta("heading", to)
p.set_meta("arrive", arrive)
_save(p)
var id: String = p.player_id
print("[server] %s crosses to the %s" % [p.display, to])
if to == Lands.home() and not Lands.land.is_empty():
Net.send_travel(peer, to, Lands.pass_home(id))
return
Lands.ensure(to, func(why: String):
if not why.is_empty():
p.remove_meta("heading")
p.remove_meta("arrive")
_save(p)
tell(peer, "The way's blocked for now (%s)" % why)
return
Net.send_travel(peer, to, Lands.pass_to(to, id)))
static func _read(path: String) -> Dictionary:
if not FileAccess.file_exists(path):
return {}
@@ -300,8 +352,42 @@ func _run(p: Node3D, what: String, a: Array) -> void:
"hit":
var n: Node3D = w.props.resource_nodes.get(_s(a, 0))
var at: Vector3 = _v(a, 1)
if n != null and is_instance_valid(n) and at.is_finite() and Actions.harvest_hit(p, n, at):
if n == null and w.tunnel != null:
var f: Node3D = w.tunnel.face_by_uid(_s(a, 0)) # (the cliff tunnel's seam or face)
if f != null and at.is_finite():
Actions.harvest_hit(p, f, at) # (the tunnel tells everyone: tunnel.gd)
elif n != null and is_instance_valid(n) and at.is_finite() and Actions.harvest_hit(p, n, at):
WorldNet.node_hit(n, at, p.global_position)
"tunnel_prop":
if w.tunnel != null:
w.tunnel.set_prop(p, _i(a, 0))
"tunnel_go":
# Down into the cliff tunnel, or back out at the top: a teleport
# the server makes (the tunnel's under the island), from nearby.
var t: Node3D = w.tunnel
if t == null or not t.opened:
return
var go_in := _s(a, 0) == "in"
var from: Vector3 = t.seam_out if go_in else t.top
if p.global_position.distance_to(from) > 4.5:
return
if go_in:
t.go_in(p)
else:
t.go_out(p)
Net.teleport(p.peer, p.global_position)
_s2c_respawn.rpc_id(p.peer, p.global_position, p.rotation.y, "")
"cross":
# The sea cave's mouth (the Valley), or the sea cave (the Cove).
var where := _s(a, 0)
if where == "tunnel" and w.tunnel != null and w.tunnel.dug >= w.tunnel.STRETCHES \
and p.global_position.distance_to(w.tunnel.cave_mouth) < 4.0:
cross(p.peer, w.tunnel.to, "sea_cave")
elif where == "sea_cave" and w.sea_cave != null and w.sea_cave.open() \
and p.global_position.distance_to(w.sea_cave.front) < 6.0:
cross(p.peer, w.sea_cave.to, "tunnel_bottom")
elif where == "sea_cave" and w.sea_cave != null:
tell(p.peer, w.sea_cave.interact_prompt(p))
"pickup":
var key := _s(a, 0)
var pk: Node3D = WorldNet.find_pickup(key)
@@ -456,6 +542,12 @@ func _run(p: Node3D, what: String, a: Array) -> void:
w.day_night.apply(true)
w.weather.snap()
Net.push_clock()
"debug_tunnel":
# (tests) dig the cliff tunnel to `n` stretches, all propped.
if cheats and w.tunnel != null:
var n := clampi(_i(a, 0), 0, w.tunnel.STRETCHES)
w.tunnel.load_data({"opened": true, "dug": n, "props": range(1, n + 1)})
w.tunnel._tell_everyone()
"debug_tp":
var to := _v(a, 0)
if cheats and to.is_finite():