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:
@@ -51,6 +51,11 @@ func place(w: Node3D) -> bool:
|
||||
|
||||
## 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)
|
||||
|
||||
@@ -88,7 +88,7 @@ func _ready() -> void:
|
||||
elif saves != null and saves.has_world():
|
||||
noise_seed = saves.world_seed()
|
||||
# (worlds from before worldlets have none: they're the Valley)
|
||||
worldlet_id = String(saves.read_meta(saves.current_id).get("worldlet", "valley"))
|
||||
worldlet_id = String(saves.worldlet_of(saves.current_id)) # (a land server's own land, or the world's)
|
||||
var why := use_worldlet(worldlet_id)
|
||||
if not why.is_empty():
|
||||
push_error("[terrain] %s: using the Valley" % why)
|
||||
|
||||
@@ -143,8 +143,20 @@ func dig_refusal(stage: int) -> String:
|
||||
return "It's loose: prop the last stretch first ([E] at its mouth, %d wood)" % PROP_WOOD
|
||||
return ""
|
||||
|
||||
## The face called `uid` (what a player's game asks the server to hit).
|
||||
func face_by_uid(uid: String) -> Node3D:
|
||||
return _face if _face != null and is_instance_valid(_face) and String(_face.uid) == uid and not _face.depleted else null
|
||||
|
||||
## On a server, everyone playing (and the other lands) hears how the tunnel
|
||||
## stands after each change.
|
||||
func _tell_everyone() -> void:
|
||||
if Net.is_server():
|
||||
WorldNet.tunnel_state(to_data())
|
||||
|
||||
func face_hit(f: Node) -> void:
|
||||
face_hp = f.hp
|
||||
if not f.depleted:
|
||||
_tell_everyone()
|
||||
|
||||
func face_broken(f: Node) -> void:
|
||||
face_hp = FACE_HP
|
||||
@@ -158,9 +170,14 @@ func face_broken(f: Node) -> void:
|
||||
else:
|
||||
Actions.notify.emit("The rock gives. Prop this stretch before you dig on")
|
||||
_rebuild.call_deferred()
|
||||
_tell_everyone()
|
||||
|
||||
## [E] at the mouth of stretch `k`: sets a timber prop there.
|
||||
## [E] at the mouth of stretch `k`: sets a timber prop there. (Online, your
|
||||
## game asks the server, which sets it and tells everyone.)
|
||||
func set_prop(player: Node, k: int) -> bool:
|
||||
if world.is_online():
|
||||
PlayerNet.ask("tunnel_prop", [k])
|
||||
return true
|
||||
if k < 1 or k > dug or props[k]:
|
||||
return false
|
||||
if player.inventory.count("wood") < PROP_WOOD:
|
||||
@@ -170,14 +187,22 @@ func set_prop(player: Node, k: int) -> bool:
|
||||
props[k] = true
|
||||
Actions.notify.emit("Propped. The next face is safe to dig")
|
||||
_rebuild.call_deferred()
|
||||
_tell_everyone()
|
||||
return true
|
||||
|
||||
# --- going in and out ----------------------------------------------------------
|
||||
|
||||
## (Online, your game asks the server, which moves you: player_net.gd.)
|
||||
func go_in(player: Node3D) -> void:
|
||||
if world.is_online() and player == world.player:
|
||||
PlayerNet.ask("tunnel_go", ["in"])
|
||||
return
|
||||
_move(player, top + Vector3(0, 0.1, 0), Vector3(1, 0, 0))
|
||||
|
||||
func go_out(player: Node3D) -> void:
|
||||
if world.is_online() and player == world.player:
|
||||
PlayerNet.ask("tunnel_go", ["out"])
|
||||
return
|
||||
_move(player, seam_out + Vector3(0, 0.2, 0), -Vector3(seam_at.x, 0.0, seam_at.z).normalized())
|
||||
|
||||
func _move(player: Node3D, at: Vector3, facing: Vector3) -> void:
|
||||
@@ -195,6 +220,7 @@ func to_data() -> Dictionary:
|
||||
return {"opened": opened, "dug": dug, "face_hp": face_hp, "props": p}
|
||||
|
||||
func load_data(d: Dictionary) -> void:
|
||||
var was := [opened, dug, props.duplicate()]
|
||||
opened = bool(d.get("opened", false))
|
||||
dug = clampi(int(d.get("dug", 0)), 0, STRETCHES)
|
||||
face_hp = float(d.get("face_hp", FACE_HP))
|
||||
@@ -202,6 +228,11 @@ func load_data(d: Dictionary) -> void:
|
||||
for k in d.get("props", []):
|
||||
if int(k) >= 1 and int(k) <= STRETCHES:
|
||||
props[int(k)] = true
|
||||
# (Online, a blow on the face only changes how much is left of it: no
|
||||
# need to rebuild the rock for that.)
|
||||
if was == [opened, dug, props] and _face != null and is_instance_valid(_face):
|
||||
_face.hp = clampf(face_hp, 0.5, _face.max_hp)
|
||||
return
|
||||
_rebuild()
|
||||
|
||||
# --- building it ---------------------------------------------------------------
|
||||
|
||||
@@ -366,8 +366,9 @@ func _on_terrain_ready() -> void:
|
||||
else:
|
||||
pr.free()
|
||||
# The journey's ways on (J1): the Valley's cliff tunnel and the Cove's sea
|
||||
# cave. Single-player for now (J3 brings travel online).
|
||||
if not server_mode and not menu_mode and not is_online():
|
||||
# cave. On a server too (J3): the server decides the digging and the
|
||||
# crossings, and every player's game has the same rock to see and walk.
|
||||
if not menu_mode:
|
||||
var tn := preload("res://scripts/world/tunnel.gd").new()
|
||||
tn.name = "Tunnel"
|
||||
add_child(tn)
|
||||
@@ -698,6 +699,7 @@ func _shared_state(harvested: Dictionary, regrow: Dictionary, taken: Array, drop
|
||||
"bear": bears.save_data() if bears != null else _saved_bear,
|
||||
"barrow": barrow.save_data() if barrow != null else {},
|
||||
"stones": stones.save_data() if stones != null else {},
|
||||
"tunnel": tunnel.to_data() if tunnel != null else {},
|
||||
}
|
||||
|
||||
## The live pieces (placed and not being removed).
|
||||
@@ -986,7 +988,15 @@ func underground_at(p: Vector3) -> bool:
|
||||
## at `arrive` ("sea_cave", "tunnel_bottom"). Each worldlet keeps its own
|
||||
## state file (Saves.state_path), so what you built here waits for you.
|
||||
func travel(to: String, arrive: String) -> void:
|
||||
if traveling or not Saves.has_world() or is_online() or server_mode:
|
||||
if server_mode:
|
||||
return # (on a server, the crossing is the player's game asking: player_net.gd "cross")
|
||||
if is_online():
|
||||
# The server checks, saves your character and sends you on (J3).
|
||||
if not traveling and Time.get_ticks_msec() > _cross_asked + 3000:
|
||||
_cross_asked = Time.get_ticks_msec()
|
||||
PlayerNet.ask("cross", ["tunnel" if arrive == "sea_cave" else "sea_cave"])
|
||||
return
|
||||
if traveling or not Saves.has_world():
|
||||
return
|
||||
traveling = true
|
||||
var id := Saves.current_id
|
||||
@@ -1011,6 +1021,25 @@ func travel(to: String, arrive: String) -> void:
|
||||
get_tree().change_scene_to_file.call_deferred(scene_file_path if not scene_file_path.is_empty() else "res://scenes/world.tscn")
|
||||
|
||||
signal travelled(to: String) ## saved and on the way to another worldlet
|
||||
var _cross_asked := -100000 ## (online: when we last asked the server to cross)
|
||||
|
||||
## Online (J3): the server's sending us on to another land's server.
|
||||
func _on_net_travelling(to: String) -> void:
|
||||
traveling = true
|
||||
player.set_physics_process(false)
|
||||
if net_sync != null:
|
||||
net_sync.set_process(false) # (nothing more to tell the land we're leaving,
|
||||
net_sync.set_physics_process(false) # nor the next one about this one)
|
||||
if hud != null:
|
||||
hud.toast("On to the %s..." % Terrain.worldlet_name(to))
|
||||
hud.fade(func(): pass)
|
||||
|
||||
## Online: in at the other land: the world reloads as that land (its terrain
|
||||
## comes from what that server says, Net.world_info).
|
||||
func _on_net_arrived(_to: String) -> void:
|
||||
travelled.emit(_to)
|
||||
if get_tree().current_scene == self:
|
||||
get_tree().change_scene_to_file.call_deferred(scene_file_path if not scene_file_path.is_empty() else "res://scenes/world.tscn")
|
||||
|
||||
## Back in a land you've been to before: its clock comes forward to `to`
|
||||
## ({day, time}, the clock where you've come from), and everything in it
|
||||
@@ -1029,20 +1058,30 @@ func catch_up(to: Dictionary) -> float:
|
||||
return secs
|
||||
|
||||
## Just arrived from another worldlet: put you where you came in.
|
||||
## Where the way in called `at` puts you ("sea_cave", "tunnel_bottom"), or
|
||||
## INF if this land has no such way in. (A server places crossing players
|
||||
## here too: player_net.gd.)
|
||||
func arrival_point(at: String) -> Vector3:
|
||||
match at:
|
||||
"sea_cave":
|
||||
if sea_cave != null:
|
||||
return sea_cave.front
|
||||
"tunnel_bottom":
|
||||
if tunnel != null and tunnel.dug >= tunnel.STRETCHES:
|
||||
return tunnel.bottom + Vector3(0.8, 0.0, 0.0)
|
||||
return Vector3.INF
|
||||
|
||||
func _arrive(at: String) -> void:
|
||||
if at.is_empty():
|
||||
return
|
||||
var pos := Vector3.INF
|
||||
var pos := arrival_point(at)
|
||||
var facing := Vector3.FORWARD
|
||||
match at:
|
||||
"sea_cave":
|
||||
if sea_cave != null:
|
||||
pos = sea_cave.front
|
||||
facing = (sea_cave.front - sea_cave.global_position).normalized()
|
||||
"tunnel_bottom":
|
||||
if tunnel != null and tunnel.dug >= tunnel.STRETCHES:
|
||||
pos = tunnel.bottom + Vector3(0.8, 0.0, 0.0)
|
||||
facing = Vector3(1, 0, 0)
|
||||
facing = Vector3(1, 0, 0)
|
||||
if pos == Vector3.INF:
|
||||
push_warning("[world] nowhere to arrive at '%s': the start instead" % at)
|
||||
_respawn()
|
||||
@@ -1228,6 +1267,8 @@ func _go_online() -> void:
|
||||
load_online_map() # (your map of this server, from your own computer)
|
||||
Net.left.connect(func(_why = ""): save_online_map(), CONNECT_ONE_SHOT)
|
||||
Net.left.connect(_on_net_left)
|
||||
Net.travelling.connect(_on_net_travelling)
|
||||
Net.arrived.connect(_on_net_arrived)
|
||||
get_tree().auto_accept_quit = false # leave the server cleanly on window close
|
||||
# The island's changes come from the server from now on.
|
||||
props.regrow_enabled = false
|
||||
|
||||
Reference in New Issue
Block a user