J3b: chat, the player list (with each player's land) and admin commands across lands; sleep needs everyone in every land, with a 'waiting for everyone else' message
This commit is contained in:
@@ -57,6 +57,11 @@ func _c2s_say(text: String) -> void:
|
||||
var who: String = Net.players[peer].name
|
||||
if text.begins_with("/"):
|
||||
print("[server] %s: %s" % [who, text])
|
||||
if not Lands.land.is_empty():
|
||||
# (Another land's server: commands run on the hub, which keeps
|
||||
# server.json, and answers back here.)
|
||||
Lands.send_hub({"t": "cmd", "line": text.substr(1), "id": String(Net.players[peer].id), "name": who, "peer": peer})
|
||||
return
|
||||
run(text.substr(1), peer)
|
||||
return
|
||||
# Plain chat only: a line every GAP seconds, so nobody can flood it.
|
||||
@@ -68,22 +73,44 @@ func _c2s_say(text: String) -> void:
|
||||
print("[chat] %s: %s" % [who, text])
|
||||
_all("%s: %s" % [who, text], "chat")
|
||||
|
||||
func _all(text: String, kind: String) -> void:
|
||||
## A line for everyone here, and (`relay`) in every other land too (J3b).
|
||||
func _all(text: String, kind: String, relay: bool = true) -> void:
|
||||
if kind == "server":
|
||||
print("[server] ", text)
|
||||
for peer in Net.players:
|
||||
_s2c_line.rpc_id(peer, text, kind)
|
||||
if relay:
|
||||
Lands.relay_line(text, kind)
|
||||
|
||||
## A command typed by someone in another land (J3b): who they are, and where
|
||||
## to send the answer. `run()` sees them as REMOTE.
|
||||
const Terrain = preload("res://scripts/world/terrain.gd")
|
||||
const REMOTE := -1
|
||||
var _caller: Dictionary = {}
|
||||
|
||||
func run_remote(line: String, caller: Dictionary) -> void:
|
||||
print("[server] %s (in the %s): /%s" % [caller.name, caller.land, line])
|
||||
_caller = caller
|
||||
run(line, REMOTE)
|
||||
_caller = {}
|
||||
|
||||
## An answer to whoever gave the command (peer 0 = the console).
|
||||
func _reply(peer: int, text: String) -> void:
|
||||
if peer == 0:
|
||||
print(text)
|
||||
elif peer == REMOTE:
|
||||
Lands.tell_land(String(_caller.land), {"t": "reply", "peer": int(_caller.peer), "text": text})
|
||||
else:
|
||||
_s2c_line.rpc_id(peer, text, "reply")
|
||||
|
||||
func _is_admin(peer: int) -> bool:
|
||||
if peer == REMOTE:
|
||||
return Net.config.is_admin(String(_caller.id))
|
||||
return peer == 0 or (Net.players.has(peer) and Net.config.is_admin(String(Net.players[peer].id)))
|
||||
|
||||
## Another land's server should know: this command changed server.json.
|
||||
const CHANGES_CONFIG := ["friend", "unfriend", "ban", "unban", "admin", "set", "release-name"]
|
||||
|
||||
## The player id a name belongs to (online or reserved), or "".
|
||||
func _id_of(n: String) -> String:
|
||||
var peer := Net.peer_by_name(n)
|
||||
@@ -102,16 +129,24 @@ func run(line: String, peer: int) -> void:
|
||||
if cmd in ADMIN_ONLY and not _is_admin(peer):
|
||||
_reply(peer, "Only admins can use /%s" % cmd)
|
||||
return
|
||||
var me: String = String(Net.players[peer].id) if Net.players.has(peer) else ""
|
||||
var by: String = String(Net.players[peer].name) if Net.players.has(peer) else "The server"
|
||||
var me: String = String(Net.players[peer].id) if Net.players.has(peer) else String(_caller.get("id", ""))
|
||||
var by: String = String(Net.players[peer].name) if Net.players.has(peer) else String(_caller.get("name", "The server"))
|
||||
_run_cmd(cmd, parts, rest, arg, peer, me, by)
|
||||
if cmd in CHANGES_CONFIG:
|
||||
Lands.tell_lands({"t": "config"})
|
||||
|
||||
func _run_cmd(cmd: String, parts: PackedStringArray, rest: String, arg: String, peer: int, me: String, by: String) -> void:
|
||||
match cmd:
|
||||
"help":
|
||||
_reply(peer, "Commands: /players /rules /friend <name> /unfriend <name> /friends" +
|
||||
(" · admin: /kick /ban /unban /admin /save /backup /backups /time 08:00 /weather <state|auto> /set <rule> <value> /say /release-name" if _is_admin(peer) else ""))
|
||||
"players":
|
||||
# Everyone, in every land (J3b: the hub's roster), with where they are.
|
||||
var names: Array[String] = []
|
||||
for p in Net.players.values():
|
||||
names.append(String(p.name) + (" (admin)" if Net.config.is_admin(String(p.id)) else ""))
|
||||
var many := Lands.roster.any(func(e): return String(e.land) != Lands.here())
|
||||
for p in (Lands.roster if not Lands.roster.is_empty() else Lands._here_list()):
|
||||
names.append(String(p.name) + (" (admin)" if Net.config.is_admin(String(p.id)) else "")
|
||||
+ (" in the %s" % Terrain.worldlet_name(String(p.land)) if many else ""))
|
||||
_reply(peer, "%d online: %s" % [names.size(), ", ".join(names)])
|
||||
"rules":
|
||||
var c: RefCounted = Net.config
|
||||
@@ -142,10 +177,14 @@ func run(line: String, peer: int) -> void:
|
||||
_reply(peer, "Your friends: %s" % (", ".join(names) if not names.is_empty() else "nobody yet (/friend <name>)"))
|
||||
"kick":
|
||||
var kp := Net.peer_by_name(arg)
|
||||
if kp == 0:
|
||||
var away := Lands.land_of_name(arg) if kp == 0 else ""
|
||||
if kp == 0 and (away.is_empty() or away == Lands.here()):
|
||||
_reply(peer, "Nobody called '%s' is online" % arg)
|
||||
return
|
||||
Net.kick(kp, "You were kicked by %s" % by)
|
||||
if kp != 0:
|
||||
Net.kick(kp, "You were kicked by %s" % by)
|
||||
else:
|
||||
Lands.tell_land(away, {"t": "kick", "name": arg, "reason": "You were kicked by %s" % by})
|
||||
_all("%s was kicked" % arg, "server")
|
||||
"ban":
|
||||
var bid := _id_of(arg)
|
||||
@@ -156,6 +195,8 @@ func run(line: String, peer: int) -> void:
|
||||
var bp := Net.peer_by_name(arg)
|
||||
if bp != 0:
|
||||
Net.kick(bp, "You were banned by %s" % by)
|
||||
elif not Lands.land_of_name(arg).is_empty():
|
||||
Lands.tell_land(Lands.land_of_name(arg), {"t": "kick", "name": arg, "reason": "You were banned by %s" % by})
|
||||
_all("%s was banned" % arg, "server")
|
||||
"unban":
|
||||
var uid: String = Net.config.name_owner(arg)
|
||||
@@ -173,9 +214,11 @@ func run(line: String, peer: int) -> void:
|
||||
_reply(ap, "%s made you an admin (/help for the commands)" % by)
|
||||
"save":
|
||||
WorldNet.save()
|
||||
Lands.tell_lands({"t": "save"})
|
||||
_reply(peer, "Saved")
|
||||
"backup":
|
||||
WorldNet.save()
|
||||
Lands.tell_lands({"t": "save"})
|
||||
var b: String = WorldNet.backup("by %s" % by)
|
||||
_reply(peer, "Backed up as %s (%d kept)" % [b, WorldNet.list_backups().size()] if not b.is_empty() else "Nothing to back up yet")
|
||||
"backups":
|
||||
@@ -198,6 +241,7 @@ func run(line: String, peer: int) -> void:
|
||||
_reply(peer, "Use /weather %s or auto" % "|".join(PackedStringArray(states)))
|
||||
return
|
||||
Net.set_weather("" if arg == "auto" else arg)
|
||||
Lands.tell_lands({"t": "weather", "state": "" if arg == "auto" else arg})
|
||||
_all("%s set the weather to %s" % [by, arg], "server")
|
||||
"set":
|
||||
if parts.size() < 3:
|
||||
|
||||
@@ -50,6 +50,9 @@ var _hub_link: Dictionary = {} ## {"conn": _hub, "buf": the partial line
|
||||
## Called once a server is listening. `args` are the command-line flags.
|
||||
func server_started(args: Dictionary) -> void:
|
||||
land = String(args.get("land", ""))
|
||||
roster = []
|
||||
if not Net.players_changed.is_connected(rebuild_roster):
|
||||
Net.players_changed.connect(rebuild_roster)
|
||||
if not land.is_empty():
|
||||
hub_port = int(args.get("hub-port", Net.DEFAULT_PORT))
|
||||
_hub = StreamPeerTCP.new()
|
||||
@@ -215,6 +218,8 @@ func _hub_tick(delta: float) -> void:
|
||||
print("[lands] the %s's server stopped" % l)
|
||||
_drain_output(s)
|
||||
servers.erase(l)
|
||||
rebuild_roster()
|
||||
PlayerNet.beds_changed()
|
||||
continue
|
||||
if s.conn != null:
|
||||
for msg in _read(s):
|
||||
@@ -253,6 +258,23 @@ func _from_land(l: String, msg: Dictionary) -> void:
|
||||
_send_land(l, {"t": "tunnel", "dug": _tunnel_dug_here()})
|
||||
"host_left":
|
||||
Net._host_left.call_deferred()
|
||||
"roster":
|
||||
servers[l].list = msg.get("list", [])
|
||||
rebuild_roster()
|
||||
"line":
|
||||
# Chat from that land: everyone here, and every other land.
|
||||
Chat._all(String(msg.text), String(msg.kind), false)
|
||||
for other in servers:
|
||||
if other != l:
|
||||
_send_land(other, msg)
|
||||
"cmd":
|
||||
Chat.run_remote(String(msg.line), {"id": String(msg.id), "name": String(msg.name), "land": l, "peer": int(msg.peer)})
|
||||
"beds":
|
||||
servers[l].beds = [int(msg.alive), int(msg.asleep)]
|
||||
# Tell the lands the running total (minus their own), so each can
|
||||
# say "N of M asleep", then see if the night passes.
|
||||
_push_bed_totals()
|
||||
PlayerNet.beds_changed()
|
||||
|
||||
## Sends `msg` to land `l`'s server.
|
||||
func _send_land(l: String, msg: Dictionary) -> void:
|
||||
@@ -302,9 +324,11 @@ func _land_tick() -> void:
|
||||
send_hub({"t": "hello", "land": land})
|
||||
send_hub({"t": "ready"})
|
||||
var n := Net.players.size()
|
||||
if n != _last_players:
|
||||
if n != _last_players or _roster_dirty:
|
||||
_last_players = n
|
||||
_roster_dirty = false
|
||||
send_hub({"t": "players", "n": n})
|
||||
send_hub({"t": "roster", "list": _here_list()})
|
||||
for msg in _read(_hub_link):
|
||||
_from_hub(msg)
|
||||
elif st == StreamPeerTCP.STATUS_ERROR or (st == StreamPeerTCP.STATUS_NONE and _said_hello):
|
||||
@@ -336,12 +360,116 @@ func _from_hub(msg: Dictionary) -> void:
|
||||
Net.push_clock()
|
||||
"tunnel":
|
||||
tunnel_dug = int(msg.dug)
|
||||
"roster":
|
||||
roster = msg.get("list", [])
|
||||
Net.push_roster()
|
||||
"line":
|
||||
Chat._all(String(msg.text), String(msg.kind), false)
|
||||
"reply":
|
||||
if Net.players.has(int(msg.peer)):
|
||||
Chat._s2c_line.rpc_id(int(msg.peer), String(msg.text), "reply")
|
||||
"kick":
|
||||
var kp: int = Net.peer_by_name(String(msg.name))
|
||||
if kp != 0:
|
||||
Net.kick(kp, String(msg.reason))
|
||||
"config":
|
||||
Net.reload_config()
|
||||
Net.broadcast_rules()
|
||||
"weather":
|
||||
Net.set_weather(String(msg.state))
|
||||
"save":
|
||||
WorldNet.save()
|
||||
"night":
|
||||
PlayerNet.pass_night_here(float(msg.secs))
|
||||
"beds_total":
|
||||
PlayerNet._hub_beds = [int(msg.alive), int(msg.asleep)]
|
||||
hub_message.emit(msg)
|
||||
|
||||
func send_hub(msg: Dictionary) -> void:
|
||||
if _hub != null and _hub.get_status() == StreamPeerTCP.STATUS_CONNECTED:
|
||||
_hub.put_data((JSON.stringify(msg) + "\n").to_utf8_buffer())
|
||||
|
||||
# --- everyone, everywhere (J3b) ------------------------------------------------------------
|
||||
|
||||
## Everyone on every land: [{"name", "id", "land", "admin"}]. The hub builds
|
||||
## it from its own players and what each land reports; every land and every
|
||||
## player's game gets a copy (Net.roster), for the player list and /players.
|
||||
var roster: Array = []
|
||||
var _roster_dirty := false
|
||||
|
||||
## The players on this server, as the roster lists them.
|
||||
func _here_list() -> Array:
|
||||
var out: Array = []
|
||||
for p in Net.players.values():
|
||||
out.append({"name": String(p.name), "id": String(p.id), "land": here(), "admin": bool(p.get("admin", false))})
|
||||
return out
|
||||
|
||||
## Hub: the roster again (its players changed, or a land's did), sent out.
|
||||
func rebuild_roster() -> void:
|
||||
if not hub:
|
||||
if not land.is_empty():
|
||||
_roster_dirty = true # (a land server: tell the hub, next tick)
|
||||
return
|
||||
var out: Array = _here_list()
|
||||
for l in servers:
|
||||
for e in servers[l].get("list", []):
|
||||
var e2: Dictionary = (e as Dictionary).duplicate()
|
||||
e2.land = l
|
||||
out.append(e2)
|
||||
roster = out
|
||||
Net.push_roster()
|
||||
for l in servers:
|
||||
_send_land(l, {"t": "roster", "list": out})
|
||||
|
||||
## The land a player called `n` is in ("" = not on anywhere).
|
||||
func land_of_name(n: String) -> String:
|
||||
for e in roster:
|
||||
if String(e.name).to_lower() == n.to_lower():
|
||||
return String(e.land)
|
||||
return ""
|
||||
|
||||
## Chat or a server message, from here to every other land.
|
||||
func relay_line(text: String, kind: String) -> void:
|
||||
if hub:
|
||||
for l in servers:
|
||||
_send_land(l, {"t": "line", "text": text, "kind": kind})
|
||||
elif not land.is_empty():
|
||||
send_hub({"t": "line", "text": text, "kind": kind})
|
||||
|
||||
## Hub: sends `msg` to every land.
|
||||
func tell_lands(msg: Dictionary) -> void:
|
||||
for l in servers:
|
||||
_send_land(l, msg)
|
||||
|
||||
## Hub: sends `msg` to land `l`.
|
||||
func tell_land(l: String, msg: Dictionary) -> void:
|
||||
_send_land(l, msg)
|
||||
|
||||
## Hub: tells each land the alive/asleep everywhere BUT that land, so a land
|
||||
## server can show the right "N of M" and know when all are abed.
|
||||
func _push_bed_totals() -> void:
|
||||
var here_alive: int = PlayerNet._alive().size()
|
||||
var here_asleep: int = PlayerNet._alive().filter(func(q): return q.in_bed).size()
|
||||
for l in servers:
|
||||
var a := here_alive
|
||||
var s := here_asleep
|
||||
for other in servers:
|
||||
if other != l:
|
||||
var b: Array = servers[other].get("beds", [0, 0])
|
||||
a += int(b[0])
|
||||
s += int(b[1])
|
||||
_send_land(l, {"t": "beds_total", "alive": a, "asleep": s})
|
||||
|
||||
## Hub: how many are alive, and how many asleep, in every other land.
|
||||
func beds_elsewhere() -> Array:
|
||||
var alive := 0
|
||||
var asleep := 0
|
||||
for l in servers:
|
||||
var b: Array = servers[l].get("beds", [0, 0])
|
||||
alive += int(b[0])
|
||||
asleep += int(b[1])
|
||||
return [alive, asleep]
|
||||
|
||||
## Land server: a pass for player `id` to go back to the hub.
|
||||
func pass_home(id: String) -> String:
|
||||
var p := Crypto.new().generate_random_bytes(16).hex_encode()
|
||||
|
||||
@@ -232,6 +232,7 @@ func stop() -> void:
|
||||
_states.clear()
|
||||
server_info.clear()
|
||||
world_info.clear()
|
||||
roster.clear()
|
||||
_out_q.clear()
|
||||
_in_q.clear()
|
||||
local_peer = 0
|
||||
@@ -514,6 +515,42 @@ func _quiet_stop() -> void:
|
||||
func is_travelling() -> bool:
|
||||
return not _travel.is_empty()
|
||||
|
||||
# --- everyone on every land (J3b, lands.gd) -------------------------------------------
|
||||
|
||||
## Client: everyone on the server, whichever land they're in: [{"name",
|
||||
## "id", "land", "admin"}] (the hub's roster). Empty on a server with only
|
||||
## one land running: then `players` is everyone.
|
||||
var roster: Array = []
|
||||
|
||||
## Server: everyone here gets the roster.
|
||||
func push_roster() -> void:
|
||||
for peer in players:
|
||||
_s2c_roster.rpc_id(peer, Lands.roster)
|
||||
|
||||
@rpc("authority", "call_remote", "reliable")
|
||||
func _s2c_roster(list: Array) -> void:
|
||||
roster = list
|
||||
players_changed.emit()
|
||||
|
||||
## Everyone on the server to show in a list: the roster, or (one land) just
|
||||
## those here. [{"name", "admin", "land"}]
|
||||
func everyone() -> Array:
|
||||
if not roster.is_empty():
|
||||
return roster
|
||||
var out: Array = []
|
||||
for p in players.values():
|
||||
out.append({"name": String(p.name), "admin": bool(p.get("admin", false)), "land": String(world_info.get("worldlet", ""))})
|
||||
return out
|
||||
|
||||
## Server: server.json read again (the hub changed it: a /set, a ban, a
|
||||
## new admin, a friend), keeping this run's command-line overrides.
|
||||
func reload_config() -> void:
|
||||
if config == null:
|
||||
return
|
||||
var ov: Dictionary = config.overrides
|
||||
config = ServerConfig.open(config.path)
|
||||
config.overrides = ov
|
||||
|
||||
## Everyone gets the clock now (after a night's sleep, an admin's /time).
|
||||
func push_clock() -> void:
|
||||
_clock_left = CLOCK_INTERVAL
|
||||
|
||||
@@ -92,6 +92,7 @@ func server_join(peer: int, id: String, display: String) -> Vector3:
|
||||
print("[server] %s's character loaded" % display)
|
||||
if at != Vector3.INF:
|
||||
p.global_position = at
|
||||
beds_changed.call_deferred() # (one more awake: everyone's count changes)
|
||||
return at
|
||||
|
||||
func place_at(peer: int, pos: Vector3) -> void:
|
||||
@@ -212,6 +213,7 @@ func server_moved(peer: int, from: Vector3, to: Vector3, yaw: float, anim: int,
|
||||
if p.in_bed and to.distance_to(p.bed_at) > 1.5:
|
||||
p.in_bed = false
|
||||
tell(p.peer, "You got out of bed")
|
||||
beds_changed()
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if not Net.is_server() or proxies.is_empty():
|
||||
@@ -607,28 +609,66 @@ func go_to_bed(p: Node3D) -> void:
|
||||
p.in_bed = true
|
||||
p.bed_at = p.global_position
|
||||
if not _maybe_pass_night():
|
||||
var n: int = _alive().size()
|
||||
var asleep: int = _alive().filter(func(q): return q.in_bed).size()
|
||||
tell(p.peer, "You're in bed (%d of %d asleep). The night passes when %s." % [asleep, n,
|
||||
"everyone's in bed" if _beds_needed(n) >= n else "%d are" % _beds_needed(n)])
|
||||
for q in _alive():
|
||||
if q != p:
|
||||
tell(q.peer, "%s is in bed (%d of %d asleep)" % [p.display, asleep, n])
|
||||
# (Counting everyone, in every land: on another land's server, the
|
||||
# hub's last count, with this bed in it.)
|
||||
var counts := sleep_counts()
|
||||
if not Lands.land.is_empty():
|
||||
counts = [_alive().size() + _hub_beds[0], _alive().filter(func(q): return q.in_bed).size() + _hub_beds[1]]
|
||||
var n: int = counts[0]
|
||||
var asleep: int = counts[1]
|
||||
var need: int = _beds_needed(n)
|
||||
tell(p.peer, waiting_text(asleep, n, need))
|
||||
# Everyone else, in every land, hears it in chat (so whoever's still
|
||||
# up knows they're being waited for).
|
||||
Chat._all("%s is in bed (%d of %d asleep%s)" % [p.display, asleep, n,
|
||||
"" if need >= n else ", %d needed" % need], "server")
|
||||
|
||||
## What a player who's just got into bed is told while the others are up.
|
||||
static func waiting_text(asleep: int, n: int, need: int) -> String:
|
||||
var left := need - asleep
|
||||
var who := "everyone else" if need >= n else ("%d more" % left)
|
||||
return "You're in bed: waiting for %s to go to bed (%d of %d asleep)" % [who, asleep, n]
|
||||
|
||||
func _alive() -> Array:
|
||||
return proxies.values().filter(func(q): return not q.survival.dead)
|
||||
|
||||
## Everyone in bed (by the rule)? Then until 06:00 for all: the clock, fires,
|
||||
## regrowth and weather move on; sleepers wake rested.
|
||||
## One clock for every land (J3b): the night passes when enough of everyone,
|
||||
## in every land, is in bed. Another land's server reports its beds to the
|
||||
## hub, which decides, and tells every land to pass the night too.
|
||||
func _maybe_pass_night() -> bool:
|
||||
var w: Node3D = WorldNet.world
|
||||
var live := _alive()
|
||||
if w == null or live.is_empty():
|
||||
if w == null:
|
||||
return false
|
||||
var asleep := live.filter(func(q): return q.in_bed)
|
||||
if asleep.size() < _beds_needed(live.size()):
|
||||
if not Lands.land.is_empty():
|
||||
_report_beds()
|
||||
return false
|
||||
Lands._push_bed_totals()
|
||||
var counts := sleep_counts()
|
||||
if counts[0] == 0 or counts[1] < _beds_needed(counts[0]):
|
||||
return false
|
||||
var secs: float = fposmod(6.0 - w.day_night.hours(), 24.0) / 24.0 * w.day_night.day_length
|
||||
Lands.tell_lands({"t": "night", "secs": secs})
|
||||
pass_night_here(secs)
|
||||
return true
|
||||
|
||||
## [alive, asleep] on this server and (the hub) every other land.
|
||||
func sleep_counts() -> Array:
|
||||
var live := _alive()
|
||||
var counts := [live.size(), live.filter(func(q): return q.in_bed).size()]
|
||||
if Lands.hub:
|
||||
var e: Array = Lands.beds_elsewhere()
|
||||
counts = [counts[0] + int(e[0]), counts[1] + int(e[1])]
|
||||
return counts
|
||||
|
||||
## The night passes here: until 06:00 for everyone on this server.
|
||||
func pass_night_here(secs: float) -> void:
|
||||
var w: Node3D = WorldNet.world
|
||||
if w == null:
|
||||
return
|
||||
var live := _alive()
|
||||
var asleep := live.filter(func(q): return q.in_bed)
|
||||
w.skip_time(secs)
|
||||
for q in live:
|
||||
if q.in_bed:
|
||||
@@ -637,7 +677,20 @@ func _maybe_pass_night() -> bool:
|
||||
_s2c_slept.rpc_id(q.peer, asleep.has(q))
|
||||
Net.push_clock()
|
||||
WorldNet.save()
|
||||
return true
|
||||
if not Lands.land.is_empty():
|
||||
_report_beds()
|
||||
|
||||
## Someone got into or out of bed, or came or went: (a land server) the hub
|
||||
## hears the new count; (the hub) the night may pass.
|
||||
func beds_changed() -> void:
|
||||
if not Lands.land.is_empty():
|
||||
_report_beds()
|
||||
elif Net.is_server():
|
||||
_maybe_pass_night()
|
||||
|
||||
func _report_beds() -> void:
|
||||
var live := _alive()
|
||||
Lands.send_hub({"t": "beds", "alive": live.size(), "asleep": live.filter(func(q): return q.in_bed).size()})
|
||||
|
||||
## The chest or grave `nid`, if this player may use it right now.
|
||||
func _container(p: Node3D, nid: int) -> Node3D:
|
||||
@@ -648,6 +701,10 @@ func _container(p: Node3D, nid: int) -> Node3D:
|
||||
|
||||
const Rules = preload("res://scripts/net/rules.gd")
|
||||
|
||||
## (Land server) the hub's latest [alive, asleep] in every OTHER land, for
|
||||
## the "N of M asleep" message and the all-abed check here.
|
||||
var _hub_beds := [0, 0]
|
||||
|
||||
## Tells the player why not, if there's a reason.
|
||||
func _allowed(why: String) -> bool:
|
||||
if why.is_empty():
|
||||
|
||||
@@ -194,9 +194,18 @@ func open() -> void:
|
||||
get_tree().paused = not _online # a shared world can't be paused by one player
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
if _online:
|
||||
# Everyone on the server, in whichever land (J3b), with where they are
|
||||
# once anyone's somewhere else.
|
||||
var all: Array = Net.everyone()
|
||||
var lands := {}
|
||||
for p in all:
|
||||
lands[String(p.get("land", ""))] = true
|
||||
var names: Array[String] = []
|
||||
for p in Net.players.values():
|
||||
names.append(String(p.name) + (" (admin)" if p.get("admin", false) else ""))
|
||||
for p in all:
|
||||
var where := ""
|
||||
if lands.size() > 1 and not String(p.get("land", "")).is_empty():
|
||||
where = " · %s" % preload("res://scripts/world/terrain.gd").worldlet_name(String(p.land))
|
||||
names.append(String(p.name) + (" (admin)" if p.get("admin", false) else "") + where)
|
||||
names.sort()
|
||||
_players_label.text = "%s · %d online\n%s" % [Net.server_info.get("server_name", "Server"), names.size(), ", ".join(names)]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user