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:
Paul Bailey
2026-11-06 23:42:50 +00:00
parent 85bbed249b
commit 86ebe07899
9 changed files with 404 additions and 27 deletions

View File

@@ -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: