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:
@@ -191,6 +191,10 @@ func start_server(config_path: String, overrides: Dictionary = {}) -> String:
|
||||
if overrides.has(flag):
|
||||
var v: Variant = overrides[flag]
|
||||
config.overrides[map[flag]] = int(v) if map[flag] in ["port", "max_players", "seed"] else String(v)
|
||||
# A land server (J3, lands.gd) runs another land of the hub's world.
|
||||
Saves.land = String(overrides.get("land", ""))
|
||||
if not Saves.land.is_empty():
|
||||
config.overrides["port"] = int(overrides.get("port", config.v("port")))
|
||||
# The shared island (world_net.gd): the same terrain and scenery every
|
||||
# client generates from the seed, plus everything players changed.
|
||||
_world = WorldNet.open_server_world(config)
|
||||
@@ -211,8 +215,10 @@ func start_server(config_path: String, overrides: Dictionary = {}) -> String:
|
||||
players.clear()
|
||||
_pending.clear()
|
||||
_set_state("server")
|
||||
print("[server] '%s' listening on UDP %d (max %d players%s). Config: %s" % [config.v("server_name"), port,
|
||||
Lands.server_started(overrides)
|
||||
print("[server] '%s' listening on UDP %d (max %d players%s%s). Config: %s" % [config.v("server_name"), port,
|
||||
int(config.v("max_players")), ", password set" if not String(config.v("password")).is_empty() else "",
|
||||
", the %s (a land server)" % Saves.land if not Saves.land.is_empty() else "",
|
||||
ProjectSettings.globalize_path(config.path)])
|
||||
return ""
|
||||
|
||||
@@ -231,6 +237,8 @@ func stop() -> void:
|
||||
local_peer = 0
|
||||
if _world != null:
|
||||
WorldNet.close_server_world() # saves it first
|
||||
Lands.server_stopped() # (and the hub's land servers with it)
|
||||
Saves.land = ""
|
||||
_world = null
|
||||
_terrain = null
|
||||
if state != "offline":
|
||||
@@ -252,7 +260,8 @@ func _on_peer_disconnected(id: int) -> void:
|
||||
_s2c_player_left.rpc(id)
|
||||
PlayerNet.server_leave(id) # saves their character
|
||||
WorldNet.peer_left(id)
|
||||
if _hosted_by(String(p.id)):
|
||||
# (The host crossing to another land hasn't left: only leaving the game does.)
|
||||
if _hosted_by(String(p.id)) and PlayerNet.land_of(String(p.id)) in ["", Lands.here()]:
|
||||
_host_left.call_deferred()
|
||||
players_changed.emit()
|
||||
|
||||
@@ -282,10 +291,12 @@ func _process(delta: float) -> void:
|
||||
## it every few seconds.
|
||||
func _tick_clock(delta: float) -> void:
|
||||
_clock_left -= delta
|
||||
if _clock_left <= 0.0 and not players.is_empty():
|
||||
if _clock_left <= 0.0 and (not players.is_empty() or not Lands.servers.is_empty()):
|
||||
_clock_left = CLOCK_INTERVAL
|
||||
for peer in players:
|
||||
_s2c_clock.rpc_id(peer, _world.day_night.day, _world.day_night.time)
|
||||
if Lands.hub:
|
||||
Lands.push_clock(_world.day_night.day, _world.day_night.time) # (one clock for every land)
|
||||
|
||||
# --- hosting from the title screen (M2.7) ------------------------------------------
|
||||
# "Host Game" runs a dedicated server as a separate, windowless copy of the
|
||||
@@ -414,7 +425,9 @@ func _hosted_by(id: String) -> bool:
|
||||
|
||||
func _host_left() -> void:
|
||||
print("[server] the host left: saving and stopping")
|
||||
stop() # (saves the world and everyone's characters)
|
||||
if not Lands.land.is_empty():
|
||||
Lands.send_hub({"t": "host_left"}) # (they left from another land: the hub stops too)
|
||||
stop() # (saves the world and everyone's characters, and stops the other lands)
|
||||
get_tree().quit()
|
||||
|
||||
# --- admin (chat.gd) ---------------------------------------------------------------
|
||||
@@ -456,11 +469,58 @@ func _s2c_weather(state: String) -> void:
|
||||
|
||||
signal weather_changed(state: String)
|
||||
|
||||
# --- crossing to another land (J3, lands.gd) ------------------------------------------
|
||||
|
||||
signal travelling(land: String) ## client: on our way to another land's server
|
||||
signal arrived(land: String) ## client: in, at the other land (the world reloads)
|
||||
|
||||
var _travel: Dictionary = {} ## client: {"land", "address", "pass"} while crossing
|
||||
var _hub_address := "" ## client: the address we joined (the hub's), for the host part
|
||||
|
||||
## Server: sends player `peer`'s game on to land `l` (its server's port, and
|
||||
## the pass that lets them in), then lets them go.
|
||||
func send_travel(peer: int, l: String, pass_: String) -> void:
|
||||
_s2c_travel.rpc_id(peer, {"land": l, "port": Lands.port_for(l), "pass": pass_})
|
||||
# (Their game goes on its own; this only tidies up one that doesn't.)
|
||||
get_tree().create_timer(3.0).timeout.connect(func():
|
||||
if _peer != null and is_server() and (players.has(peer) or _pending.has(peer)):
|
||||
_peer.disconnect_peer(peer))
|
||||
|
||||
@rpc("authority", "call_remote", "reliable")
|
||||
func _s2c_travel(d: Dictionary) -> void:
|
||||
var host := String(parse_address(_hub_address if not _hub_address.is_empty() else get_identity().last_server)[0])
|
||||
if host.is_empty():
|
||||
host = "127.0.0.1"
|
||||
_travel = {"land": String(d.land), "address": "%s:%d" % [host, int(d.port)], "pass": String(d.pass)}
|
||||
print("[net] on to the %s (%s)" % [d.land, _travel.address])
|
||||
travelling.emit(String(d.land))
|
||||
var pw := _pending_password
|
||||
_quiet_stop()
|
||||
join(String(_travel.address), pw, String(_travel.pass))
|
||||
|
||||
## Closes the connection without telling anyone we left (a crossing).
|
||||
func _quiet_stop() -> void:
|
||||
if _peer != null:
|
||||
_peer.close()
|
||||
_peer = null
|
||||
multiplayer.multiplayer_peer = null
|
||||
players.clear()
|
||||
_states.clear()
|
||||
_out_q.clear()
|
||||
_in_q.clear()
|
||||
local_peer = 0
|
||||
_set_state("offline")
|
||||
|
||||
func is_travelling() -> bool:
|
||||
return not _travel.is_empty()
|
||||
|
||||
## Everyone gets the clock now (after a night's sleep, an admin's /time).
|
||||
func push_clock() -> void:
|
||||
_clock_left = CLOCK_INTERVAL
|
||||
for peer in players:
|
||||
_s2c_clock.rpc_id(peer, _world.day_night.day, _world.day_night.time)
|
||||
if Lands.hub:
|
||||
Lands.push_clock(_world.day_night.day, _world.day_night.time)
|
||||
|
||||
## Where a player is, as far as the server knows.
|
||||
func player_pos(peer: int) -> Vector3:
|
||||
@@ -541,6 +601,12 @@ func move_problem(from: Vector3, to: Vector3, dt: float, teleported: bool) -> St
|
||||
return "too fast (%.0f m in %.2f s)" % [flat, dt]
|
||||
elif to.y - from.y > MAX_RISE * dt + MOVE_SLACK:
|
||||
return "rising too fast"
|
||||
# (Down the cliff tunnel, J1: its own space under the island, where its
|
||||
# rock is the ground. The way in and out is a server teleport.)
|
||||
if _world != null and _world.underground_at(to):
|
||||
return "" if teleported or _world.underground_at(from) else "into the tunnel without going in"
|
||||
if _world != null and _world.underground_at(from):
|
||||
return "out of the tunnel without climbing out"
|
||||
if not _terrain.inside_boundary(to.x, to.z, -3.0):
|
||||
return "outside the island"
|
||||
if to.y < _terrain.sample_height(to.x, to.z) - 1.5:
|
||||
@@ -594,8 +660,10 @@ func _c2s_hello(d: Dictionary) -> void:
|
||||
_reject(peer, "Different game version: this server runs Emberwild %s (protocol %d), you have protocol %d. %s" % [
|
||||
Updates.version, PROTOCOL, theirs, "Update the game to join." if theirs < PROTOCOL else "The server needs updating."])
|
||||
return
|
||||
# (A crossing to another land comes with a one-time pass instead, checked
|
||||
# once they've proved who they are: _c2s_proof.)
|
||||
var pw := String(config.v("password"))
|
||||
if not pw.is_empty() and String(d.get("password", "")) != pw:
|
||||
if not pw.is_empty() and String(d.get("password", "")) != pw and String(d.get("pass", "")).is_empty():
|
||||
_reject(peer, "Wrong password")
|
||||
return
|
||||
var nonce := Crypto.new().generate_random_bytes(32)
|
||||
@@ -619,6 +687,23 @@ func _c2s_proof(sig: PackedByteArray) -> void:
|
||||
if not why.is_empty():
|
||||
_reject(peer, why)
|
||||
return
|
||||
# Lands (J3): a land server only lets in someone crossing with a pass the
|
||||
# hub gave them; a pass anywhere must be theirs and fresh. And on the
|
||||
# hub, a character who's in another land is sent straight on there.
|
||||
var pass_ := String(hello.get("pass", ""))
|
||||
if not pass_.is_empty():
|
||||
if not Lands.take_pass(pass_, id):
|
||||
_reject(peer, "That way in has expired: join the server again")
|
||||
return
|
||||
elif not Lands.land.is_empty():
|
||||
_reject(peer, "Join this server's main address (this is one of its other lands)")
|
||||
return
|
||||
elif Lands.hub:
|
||||
var away: String = PlayerNet.land_of(id)
|
||||
if not away.is_empty() and away != Lands.home():
|
||||
_pending.erase(peer)
|
||||
PlayerNet.send_on(peer, id, away)
|
||||
return
|
||||
_pending.erase(peer)
|
||||
config.reserve_name(name, id)
|
||||
if config.data.admins.is_empty():
|
||||
@@ -641,7 +726,8 @@ func _c2s_proof(sig: PackedByteArray) -> void:
|
||||
# (The world's own seed: an existing world keeps the seed it was made with.)
|
||||
var world := {"seed": int(_terrain.noise_seed), "worldlet": String(_terrain.worldlet_id), "spawn": start, "yaw": yaw,
|
||||
"island_spawn": _spawn, "day": _world.day_night.day, "time": _world.day_night.time, "server_time": server_time,
|
||||
"weather": _world.weather.override}
|
||||
"weather": _world.weather.override,
|
||||
"tunnel_dug": Lands.tunnel_dug if not Lands.land.is_empty() else Lands._tunnel_dug_here()}
|
||||
_s2c_welcome.rpc_id(peer, {"you": peer, "server": config.public_info(), "players": players, "world": world})
|
||||
for other in players:
|
||||
if other != peer:
|
||||
@@ -683,7 +769,12 @@ static func parse_address(a: String) -> Array:
|
||||
return [a, DEFAULT_PORT]
|
||||
|
||||
## Connects to a server. The outcome arrives as `joined` or `join_failed`.
|
||||
func join(address: String, password: String = "") -> void:
|
||||
## `pass_` is a one-time pass from a crossing to another land (J3).
|
||||
func join(address: String, password: String = "", pass_: String = "") -> void:
|
||||
_pending_pass = pass_
|
||||
if pass_.is_empty():
|
||||
_hub_address = address.strip_edges()
|
||||
_travel = {}
|
||||
if state != "offline":
|
||||
stop()
|
||||
var me := get_identity()
|
||||
@@ -705,11 +796,13 @@ func join(address: String, password: String = "") -> void:
|
||||
_reject_reason = ""
|
||||
_pending_password = password
|
||||
_join_left = JOIN_TIMEOUT
|
||||
me.last_server = address.strip_edges()
|
||||
me.save_profile()
|
||||
if pass_.is_empty():
|
||||
me.last_server = address.strip_edges() # (always the hub's: rejoining sends you on)
|
||||
me.save_profile()
|
||||
_set_state("connecting")
|
||||
|
||||
var _pending_password := ""
|
||||
var _pending_pass := ""
|
||||
|
||||
func leave() -> void:
|
||||
var was := state
|
||||
@@ -722,7 +815,7 @@ func leave() -> void:
|
||||
func _on_connected() -> void:
|
||||
var me := get_identity()
|
||||
_c2s_hello.rpc_id(1, {"protocol": debug_protocol if debug_protocol != 0 else PROTOCOL, "name": me.name,
|
||||
"appearance": me.appearance, "public_key": me.public_pem, "password": _pending_password})
|
||||
"appearance": me.appearance, "public_key": me.public_pem, "password": _pending_password, "pass": _pending_pass})
|
||||
|
||||
@rpc("authority", "call_remote", "reliable")
|
||||
func _s2c_challenge(nonce: PackedByteArray) -> void:
|
||||
@@ -745,6 +838,10 @@ func _s2c_welcome(d: Dictionary) -> void:
|
||||
_set_state("connected")
|
||||
joined.emit()
|
||||
players_changed.emit()
|
||||
if not _travel.is_empty():
|
||||
var l := String(_travel.land)
|
||||
_travel = {}
|
||||
arrived.emit(l)
|
||||
|
||||
@rpc("authority", "call_remote", "reliable")
|
||||
func _s2c_rejected(reason: String) -> void:
|
||||
@@ -807,10 +904,17 @@ func _run_queue(q: Array) -> void:
|
||||
f.call()
|
||||
|
||||
func _fail(reason: String) -> void:
|
||||
stop_hosting() # (hosting and couldn't get in: don't leave the server running)
|
||||
var crossing := not _travel.is_empty()
|
||||
_travel = {}
|
||||
if not crossing:
|
||||
stop_hosting() # (hosting and couldn't get in: don't leave the server running)
|
||||
stop()
|
||||
last_reason = reason
|
||||
join_failed.emit(reason)
|
||||
if crossing:
|
||||
# (The world we were in is still on screen: it goes back to the title.)
|
||||
last_reason = "Couldn't get through to the next land (%s). Join again to carry on." % reason
|
||||
left.emit(last_reason)
|
||||
|
||||
func _on_connection_failed() -> void:
|
||||
_fail("Couldn't reach the server")
|
||||
@@ -818,6 +922,10 @@ func _on_connection_failed() -> void:
|
||||
func _on_server_disconnected() -> void:
|
||||
var was := state
|
||||
var reason := _reject_reason
|
||||
if not _travel.is_empty():
|
||||
if was == "connecting":
|
||||
_fail(reason if not reason.is_empty() else "the server hung up") # (the next land wouldn't have us)
|
||||
return # (the land we're leaving letting us go)
|
||||
stop_hosting()
|
||||
stop()
|
||||
if was == "connecting":
|
||||
|
||||
Reference in New Issue
Block a user