t_p6_browser: add workshop map browser with voting (3 files)

- workshop_browser.gd: scans server maps dir for .pck/.tscn files
- map_browser_rpc.gd: client-server RPC bridge for map list/change
- map_vote.gd: simple >50% majority vote with 30s timeout

File scope:
  server/scripts/browser/workshop_browser.gd (NEW)
  scripts/network/map_browser_rpc.gd (NEW)
  server/scripts/browser/map_vote.gd (NEW)
This commit is contained in:
2026-07-01 20:24:23 -04:00
parent 46ff83325f
commit 3465922be4
3 changed files with 549 additions and 0 deletions
+119
View File
@@ -0,0 +1,119 @@
## MapBrowserRPC — Client-Server RPC Bridge for Workshop Map Browser
##
## Autoload candidate (or child of NetworkManager) that provides ENet RPC
## methods for the workshop map browser system.
##
## ## Architecture
##
## ┌──────────────┐ request_map_list() ┌──────────────┐
## │ Client │ ──────────────────────► │ Server │
## │ │ ◄────────────────────── │ │
## │ │ send_map_list() │ │
## │ │ │ │
## │ │ request_map_change() │ │
## │ │ ──────────────────────► │ │
## └──────────────┘ └──────────────┘
##
## ## Signals
##
## map_list_received(map_list: Array) — emitted on client when map list arrives
## map_change_requested(map_id: String, peer_id: int) — emitted on server
## when a client requests a map change
## map_vote_started(map_id: String) — emitted on server when vote begins
##
## ## Integration
##
## Add as an autoload in project.godot:
## MapBrowserRPC="*res://scripts/network/map_browser_rpc.gd"
##
## Or add as a child of NetworkManager at runtime.
##
## ## RPC Visibility
##
## - Server → Client: send_map_list (authority, call_remote)
## - Client → Server: request_map_list (any_peer, call_remote)
## - Client → Server: request_map_change (any_peer, call_remote)
##
## =============================================================================
extends Node
# ---------------------------------------------------------------------------
# Signals
# ---------------------------------------------------------------------------
## Emitted on the client when the server sends the map list.
signal map_list_received(map_list: Array)
## Emitted on the server when a client requests a map change.
signal map_change_requested(map_id: String, peer_id: int)
## Emitted on the server when a map vote is initiated by a client request.
signal map_vote_started(map_id: String)
## Emitted on the client when the vote result is sent back.
signal map_vote_result(map_id: String, passed: bool, yes_count: int, no_count: int)
# ---------------------------------------------------------------------------
# RPC: Server → Client
# ---------------------------------------------------------------------------
## Server sends the serialized map list to a specific client.
## Called by server code after receiving request_map_list().
@rpc("authority", "call_remote", "reliable")
func send_map_list(map_list: Array) -> void:
if multiplayer.is_server():
return
map_list_received.emit(map_list)
print("[MapBrowserRPC] Received map list with %d maps" % map_list.size())
## Server broadcasts the final vote result to all clients.
@rpc("authority", "call_remote", "reliable")
func broadcast_vote_result(map_id: String, passed: bool, yes_count: int, no_count: int) -> void:
if multiplayer.is_server():
return
map_vote_result.emit(map_id, passed, yes_count, no_count)
print("[MapBrowserRPC] Vote result for %s: passed=%s (%d yes, %d no)" % [map_id, passed, yes_count, no_count])
# ---------------------------------------------------------------------------
# RPC: Client → Server
# ---------------------------------------------------------------------------
## Client requests the current map list from the server.
## The server should call send_map_list(peer_id, map_list) in response.
@rpc("any_peer", "call_remote", "reliable")
func request_map_list() -> void:
if not multiplayer.is_server():
return
var peer_id: int = multiplayer.get_remote_sender_id()
print("[MapBrowserRPC] Map list requested by peer %d" % peer_id)
# Delegate to WorkshopBrowser if available
var list: Array = []
if WorkshopBrowser and WorkshopBrowser.has_method(&"get_map_list"):
list = WorkshopBrowser.get_map_list()
else:
push_warning("[MapBrowserRPC] WorkshopBrowser singleton not available — returning empty list")
# Send the map list back to the requesting client
send_map_list.rpc_id(peer_id, list)
## Client requests a map change (initiates a vote or signals admin).
## Emits map_change_requested on the server for handling.
@rpc("any_peer", "call_remote", "reliable")
func request_map_change(map_id: String) -> void:
if not multiplayer.is_server():
return
var peer_id: int = multiplayer.get_remote_sender_id()
print("[MapBrowserRPC] Map change requested by peer %d: %s" % [peer_id, map_id])
# Validate the map exists via WorkshopBrowser
if WorkshopBrowser and WorkshopBrowser.has_method(&"has_map"):
if not WorkshopBrowser.has_map(map_id):
print("[MapBrowserRPC] Peer %d requested unknown map: %s" % [peer_id, map_id])
return
map_change_requested.emit(map_id, peer_id)
map_vote_started.emit(map_id)