Files
VRyHungry1/scripts/network_manager.gd
T
2026-07-29 14:45:09 +02:00

175 lines
6.2 KiB
GDScript

extends Node
const DEFAULT_PORT := 24565
const MAX_CLIENTS := 7
const MULTIPLAYER_SCENE := "res://Scenes/multiplayer.tscn"
signal player_joined(peer_id: int)
signal player_left(peer_id: int)
signal session_started(is_server: bool)
signal session_ended()
signal connection_failed()
# World hooks, registered by main.gd once the scene tree exists.
var _world: Node = null
var _players_spawner: MultiplayerSpawner = null
var _items_spawner: MultiplayerSpawner = null
var _content_root: Node = null
func _ready() -> void:
multiplayer.peer_connected.connect(_on_peer_connected)
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
multiplayer.connected_to_server.connect(_on_connected_to_server)
multiplayer.connection_failed.connect(_on_connection_failed)
multiplayer.server_disconnected.connect(_on_server_disconnected)
_handle_cmdline()
# --- Public API ------------------------------------------------------------
## Start hosting. The host is peer 1 and also plays (listen server).
func host(port: int = DEFAULT_PORT) -> Error:
var peer := ENetMultiplayerPeer.new()
var err := peer.create_server(port, MAX_CLIENTS)
if err != OK:
print("NetworkManager HOST failed to create_server on port %d: %s" % [port, error_string(err)])
return err
multiplayer.multiplayer_peer = peer
print("NetworkManager HOST started on port %d (peer id %d)" % [port, multiplayer.get_unique_id()])
session_started.emit(true)
get_tree().change_scene_to_file.call_deferred(MULTIPLAYER_SCENE)
return OK
## Join an existing host.
func join(address: String = "127.0.0.1", port: int = DEFAULT_PORT) -> Error:
var peer := ENetMultiplayerPeer.new()
var err := peer.create_client(address, port)
if err != OK:
print("NetworkManager JOIN failed to create_client %s:%d: %s" % [address, port, error_string(err)])
return err
multiplayer.multiplayer_peer = peer
print("NetworkManager JOIN connecting to %s:%d ..." % [address, port])
return OK
## Leave the session and tear down transport.
func leave() -> void:
_go_offline()
print("NetworkManager Session ended")
session_ended.emit()
# Restore Godot's default OfflineMultiplayerPeer (rather than leaving the peer
# null), so is_multiplayer_authority()/get_unique_id() keep working while we are
# back in single-player / menu state.
func _go_offline() -> void:
if multiplayer.multiplayer_peer:
multiplayer.multiplayer_peer.close()
multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()
unregister_world()
func is_server() -> bool:
return is_online() and multiplayer.is_server()
## True only when a real ENet session is active. Godot installs a default
## OfflineMultiplayerPeer, so a non-null peer alone does not mean "online".
func is_online() -> bool:
var p := multiplayer.multiplayer_peer
return p != null and not (p is OfflineMultiplayerPeer)
## True on the machine that owns authoritative world logic: the server when
## online, or the single player when offline. Station logic and spawning should
## only run where this is true, so state has one source of truth.
func owns_world() -> bool:
return not is_online() or is_server()
# --- Station work-progress seam -------------------------------------------
## Reusable entry point for a client to contribute work to a station (e.g. a
## future chopping/gesture station). The client detects the gesture locally and
## calls this; the server validates and accumulates. Timer-driven stations like
## the Hob don't need it, but it is the drop-in seam for input-driven ones.
@rpc("any_peer", "reliable")
func submit_work(station_path: NodePath, amount: float) -> void:
if not is_server():
return
var station := get_node_or_null(station_path)
if station and station.has_method("add_work"):
print("NetworkManager submit_work: peer %d contributed %.2f to %s" % [multiplayer.get_remote_sender_id(), amount, station.name])
station.add_work(multiplayer.get_remote_sender_id(), amount)
## Called by the world scene once it's ready, passing its spawners. Must run
## before world_ready()/host()/join() on every peer so the custom spawn
## function is installed before any spawn packet can arrive.
func register_world(world: Node, players_spawner: MultiplayerSpawner, items_spawner: MultiplayerSpawner) -> void:
_world = world
_players_spawner = players_spawner
_items_spawner = items_spawner
_content_root = items_spawner.get_node(items_spawner.spawn_path) if items_spawner else world
print("NetworkManager World registered (players_spawner=%s items_spawner=%s)" % [str(players_spawner != null), str(items_spawner != null)])
## Called when the world scene goes away (disconnect, leaving the session) so
## the autoload doesn't hold stale/freed references across a scene reload.
func unregister_world() -> void:
_world = null
_content_root = null
_players_spawner = null
_items_spawner = null
# --- Session signal handlers ----------------------------------------------
func _on_peer_connected(peer_id: int) -> void:
print("NetworkManager peer_connected: %d" % peer_id)
# Only the server reacts by materialising that peer's player.
if is_server():
print("NetworkManager player_present: %d" % peer_id)
player_joined.emit(peer_id)
func _on_peer_disconnected(peer_id: int) -> void:
print("NetworkManager peer_disconnected: %d" % peer_id)
if is_server():
print("NetworkManager player_absent: %d" % peer_id)
player_left.emit(peer_id)
func _on_connected_to_server() -> void:
print("NetworkManager connected_to_server (my id=%d)" % multiplayer.get_unique_id())
session_started.emit(false)
get_tree().change_scene_to_file.call_deferred(MULTIPLAYER_SCENE)
func _on_connection_failed() -> void:
print("NetworkManager connection_failed")
_go_offline()
connection_failed.emit()
func _on_server_disconnected() -> void:
print("NetworkManager server_disconnected")
_go_offline()
session_ended.emit()
# --- Command-line driven test bootstrap -----------------------------------
func _handle_cmdline() -> void:
print("NetworkManager _handle_cmdline")
var args := OS.get_cmdline_user_args()
if args.has("--server"):
print("NetworkManager cmdline: --server")
host()
elif args.has("--join"):
var idx := args.find("--join")
var addr := "127.0.0.1"
if idx + 1 < args.size():
addr = args[idx + 1]
print("NetworkManager cmdline: --join %s" % addr)
join(addr)