M2.6: server rules (PvP, raiding, locks and friends, death penalty, difficulty, wolf grace), chat, admin commands, server console

This commit is contained in:
Paul Bailey
2026-11-04 22:54:40 +00:00
parent 47fc116839
commit 2ab79b68c6
26 changed files with 1195 additions and 57 deletions

View File

@@ -54,6 +54,7 @@ static func open(p: String = DEFAULT_PATH) -> RefCounted:
if FileAccess.file_exists(c.names_path()) else null
if n is Dictionary:
c.names = n
c.load_friends()
return c
func names_path() -> String:
@@ -104,3 +105,104 @@ func add_admin(id: String) -> void:
if not id in data.admins:
data.admins.append(id)
save()
func ban(id: String) -> void:
if not id in data.banned:
data.banned.append(id)
save()
func unban(id: String) -> bool:
if not id in data.banned:
return false
data.banned.erase(id)
save()
return true
## A reserved name back up for grabs (the player who had it keeps their
## character, and picks another name next time they join).
func release_name(n: String) -> bool:
if not names.has(n.to_lower()):
return false
names.erase(n.to_lower())
save_names()
return true
## The name a player id goes by here ("" if none).
func name_of(id: String) -> String:
for k in names:
if names[k].id == id:
return String(names[k].name)
return ""
# --- rules that can change while the server runs (/set) --------------------------
## What each can be set to. The others in DEFAULTS are fixed at start-up.
const SETTABLE := {
"pvp": ["false", "true"],
"raiding": ["false", "true"],
"locks": ["off", "owner", "owner_and_friends"],
"death_penalty": ["grave", "keep", "drop_all"],
"sleep": ["all_online", "half", "anyone", "off"],
"pause_when_empty": ["false", "true"],
"difficulty": ["easy", "normal", "hard"],
"wolf_grace_days": "0-10",
"motd": "text",
"server_name": "text",
}
## Changes a rule and saves server.json. Returns "" or what's wrong.
func set_rule(k: String, value: String) -> String:
if not SETTABLE.has(k):
return "Can't set '%s'. These can be set: %s" % [k, ", ".join(PackedStringArray(SETTABLE.keys()))]
var allowed: Variant = SETTABLE[k]
var val: Variant
if allowed is Array:
if not value in allowed:
return "%s can be: %s" % [k, " / ".join(PackedStringArray(allowed))]
val = (value == "true") if value in ["true", "false"] else value
elif allowed == "0-10":
if not value.is_valid_int() or int(value) < 0 or int(value) > 10:
return "%s is a number from 0 to 10" % k
val = int(value)
else:
if value.strip_edges().is_empty() or value.length() > 80:
return "%s needs some text (up to 80 characters)" % k
val = value.strip_edges()
data[k] = val
overrides.erase(k) # (a flag no longer wins over what the admin just set)
save()
return ""
# --- friends (docs/MULTIPLAYER_PLAN.md §4) ----------------------------------------
# Per player: whose doors and chests they share. Stored as owner id -> [ids],
# in friends.json next to server.json.
var friends: Dictionary = {}
func friends_path() -> String:
return path.get_base_dir().path_join("friends.json")
func load_friends() -> void:
var f: Variant = JSON.parse_string(FileAccess.get_file_as_string(friends_path())) \
if FileAccess.file_exists(friends_path()) else null
friends = f if f is Dictionary else {}
## `owner` lets `friend` use their things.
func add_friend(owner: String, friend: String) -> void:
var l: Array = friends.get(owner, [])
if not friend in l:
l.append(friend)
friends[owner] = l
_write(friends_path(), friends)
func remove_friend(owner: String, friend: String) -> bool:
var l: Array = friends.get(owner, [])
if not friend in l:
return false
l.erase(friend)
friends[owner] = l
_write(friends_path(), friends)
return true
func is_friend(owner: String, friend: String) -> bool:
return friend in friends.get(owner, [])