Files
Blockline/scripts/server_settings.gd
T

82 lines
3.8 KiB
GDScript
Raw Normal View History

2026-09-12 11:01:37 +02:00
class_name ServerSettings
extends RefCounted
const DEFAULTS := {"server_name": "BLOCKLINE Dedicated", "bind": "0.0.0.0", "port": 27840,
"mode": "TDM", "map": "quarry", "max_players": 8, "bots": 8, "difficulty": 1, "score_limit": 50,
"time_limit": 600, "round_delay": 10, "discovery": true}
const RANGES := {"port": [1024, 65535], "max_players": [2, 8], "bots": [0, 8],
"difficulty": [0, 3], "score_limit": [1, 200], "time_limit": [60, 1800], "round_delay": [3, 120]}
const OPTIONS := {"name": "server_name", "bind": "bind", "port": "port", "mode": "mode", "map": "map",
"max-players": "max_players", "bots": "bots", "difficulty": "difficulty",
"score-limit": "score_limit", "time-limit": "time_limit", "round-delay": "round_delay", "discovery": "discovery"}
static func validate(settings: Dictionary) -> String:
for key in settings:
if not DEFAULTS.has(key):
return "Unbekannte Einstellung: " + str(key)
for key in RANGES:
var value: Variant = settings.get(key)
if not (value is int or value is float) or not is_finite(float(value)) or float(value) != floor(float(value)):
return "%s muss eine ganze Zahl sein." % key
if value < RANGES[key][0] or value > RANGES[key][1]:
return "%s muss zwischen %s und %s liegen." % [key, RANGES[key][0], RANGES[key][1]]
if settings.bots > settings.max_players:
return "bots darf max_players nicht uebersteigen."
if settings.port == LanDiscovery.DISCOVERY_PORT:
return "UDP 27841 ist fuer die LAN-Suche reserviert."
if not settings.get("server_name") is String or settings.server_name.strip_edges().is_empty() or settings.server_name.length() > 40:
return "server_name muss 1 bis 40 Zeichen enthalten."
if not settings.get("bind") is String or not settings.bind.is_valid_ip_address() or settings.bind.contains(":"):
return "bind muss eine lokale IPv4-Adresse sein (z.B. 0.0.0.0)."
if settings.get("mode") not in ["TDM", "FFA", "DOM", "KC"]:
return "mode muss TDM, FFA, DOM oder KC sein."
if settings.get("map", "quarry") not in MapCatalog.IDS:
return "map muss quarry oder dust2 sein."
if not settings.get("discovery") is bool:
return "discovery muss true oder false sein."
return ""
static func read(args: PackedStringArray) -> Dictionary:
var path := "res://server.json"
for arg in args:
if arg.begins_with("--config="):
path = arg.trim_prefix("--config=")
if not FileAccess.file_exists(path):
return {"error": "Konfiguration fehlt: " + path}
var parser := JSON.new()
if parser.parse(FileAccess.get_file_as_string(path)) != OK or not parser.data is Dictionary:
return {"error": "Ungueltiges JSON in " + path + ": " + parser.get_error_message()}
var settings := DEFAULTS.duplicate()
settings.merge(parser.data, true)
var result := {"settings": settings, "error": "", "run_for": 0, "stop_file": ""}
for arg in args:
if arg == "--dedicated" or arg.begins_with("--config="):
continue
var split := arg.find("=")
if not arg.begins_with("--") or split < 3:
return {"error": "Option muss --name=wert verwenden: " + arg}
var option := arg.substr(2, split - 2)
var value := arg.substr(split + 1)
if option == "stop-file":
result.stop_file = value
elif option == "run-for":
if not value.is_valid_int() or int(value) < 1 or int(value) > 86400:
return {"error": "run-for muss 1 bis 86400 Sekunden sein."}
result.run_for = int(value)
elif OPTIONS.has(option):
var key: String = OPTIONS[option]
if RANGES.has(key):
if not value.is_valid_int():
return {"error": option + " muss eine ganze Zahl sein."}
settings[key] = int(value)
elif key == "discovery":
if value not in ["true", "false"]:
return {"error": "discovery muss true oder false sein."}
settings[key] = value == "true"
else:
settings[key] = value
else:
return {"error": "Unbekannte Option: " + option}
result.error = validate(settings)
return result