203 lines
5.4 KiB
GDScript
203 lines
5.4 KiB
GDScript
extends Node
|
|
|
|
signal player_connected(peer_id, player_info)
|
|
signal server_disconnected
|
|
|
|
const SERVER_ADDRESS: String = "127.0.0.1"
|
|
const SERVER_PORT: int = 8080
|
|
const MAX_PLAYERS: int = 10
|
|
const MAX_NICK_LENGTH := 24
|
|
const MAX_ADDRESS_LENGTH := 253
|
|
|
|
var players = {}
|
|
var player_info = {"nick": "host", "skin": Character.SkinColor.BLUE}
|
|
var _session_active := false
|
|
|
|
|
|
func _ready() -> void:
|
|
multiplayer.server_disconnected.connect(_on_server_disconnected)
|
|
multiplayer.connection_failed.connect(_on_connection_failed)
|
|
multiplayer.peer_disconnected.connect(_on_player_disconnected)
|
|
multiplayer.peer_connected.connect(_on_player_connected)
|
|
multiplayer.connected_to_server.connect(_on_connected_ok)
|
|
|
|
|
|
func start_host(nickname: String, skin_color_str: String):
|
|
var peer = ENetMultiplayerPeer.new()
|
|
var error = peer.create_server(SERVER_PORT, MAX_PLAYERS)
|
|
if error:
|
|
return error
|
|
|
|
peer.host.compress(ENetConnection.COMPRESS_RANGE_CODER)
|
|
multiplayer.multiplayer_peer = peer
|
|
_session_active = true
|
|
|
|
player_info["nick"] = sanitize_nickname(nickname, "Host_" + str(multiplayer.get_unique_id()))
|
|
player_info["skin"] = skin_str_to_e(skin_color_str)
|
|
|
|
if DisplayServer.get_name() == "headless":
|
|
return
|
|
|
|
players[1] = player_info
|
|
player_connected.emit(1, player_info)
|
|
|
|
|
|
func join_game(nickname: String, skin_color_str: String, address: String = SERVER_ADDRESS):
|
|
address = sanitize_address(address)
|
|
if address.is_empty():
|
|
return ERR_INVALID_PARAMETER
|
|
|
|
var peer = ENetMultiplayerPeer.new()
|
|
var error = peer.create_client(address, SERVER_PORT)
|
|
if error:
|
|
return error
|
|
|
|
peer.host.compress(ENetConnection.COMPRESS_RANGE_CODER)
|
|
multiplayer.multiplayer_peer = peer
|
|
_session_active = true
|
|
|
|
player_info["nick"] = sanitize_nickname(nickname, "Player_" + str(multiplayer.get_unique_id()))
|
|
player_info["skin"] = skin_str_to_e(skin_color_str)
|
|
|
|
|
|
func _on_connected_ok():
|
|
var peer_id = multiplayer.get_unique_id()
|
|
players[peer_id] = player_info
|
|
player_connected.emit(peer_id, player_info)
|
|
_register_player.rpc_id(1, player_info)
|
|
|
|
|
|
func _on_player_connected(id):
|
|
if not multiplayer.is_server():
|
|
return
|
|
for peer_id in players:
|
|
_sync_registered_player.rpc_id(id, peer_id, players[peer_id])
|
|
|
|
|
|
@rpc("any_peer", "reliable")
|
|
func _register_player(new_player_info):
|
|
if not multiplayer.is_server():
|
|
return
|
|
if not (new_player_info is Dictionary):
|
|
return
|
|
var new_player_id = multiplayer.get_remote_sender_id()
|
|
if new_player_id == 0:
|
|
return
|
|
if players.has(new_player_id):
|
|
return
|
|
var sanitized_info = sanitize_player_info(new_player_info, "Player_" + str(new_player_id))
|
|
players[new_player_id] = sanitized_info
|
|
player_connected.emit(new_player_id, sanitized_info)
|
|
_sync_registered_player.rpc(new_player_id, sanitized_info)
|
|
|
|
|
|
func _on_player_disconnected(id):
|
|
players.erase(id)
|
|
|
|
|
|
func _on_connection_failed():
|
|
_finish_session()
|
|
|
|
|
|
func _on_server_disconnected():
|
|
_finish_session()
|
|
|
|
|
|
func leave_game() -> void:
|
|
var peer := multiplayer.multiplayer_peer
|
|
if peer:
|
|
peer.close()
|
|
_finish_session()
|
|
|
|
|
|
func _finish_session() -> void:
|
|
var had_session := _session_active or multiplayer.multiplayer_peer != null or not players.is_empty()
|
|
_session_active = false
|
|
multiplayer.multiplayer_peer = null
|
|
players.clear()
|
|
if had_session:
|
|
server_disconnected.emit()
|
|
|
|
|
|
func skin_str_to_e(s):
|
|
match str(s).strip_edges().to_lower():
|
|
"blue":
|
|
return Character.SkinColor.BLUE
|
|
"yellow":
|
|
return Character.SkinColor.YELLOW
|
|
"green":
|
|
return Character.SkinColor.GREEN
|
|
"red":
|
|
return Character.SkinColor.RED
|
|
_:
|
|
return Character.SkinColor.BLUE
|
|
|
|
|
|
@rpc("authority", "reliable")
|
|
func _sync_registered_player(peer_id: int, registered_player_info: Dictionary):
|
|
if multiplayer.is_server():
|
|
return
|
|
if players.has(peer_id):
|
|
return
|
|
var sanitized_info = sanitize_player_info(registered_player_info, "Player_" + str(peer_id))
|
|
players[peer_id] = sanitized_info
|
|
player_connected.emit(peer_id, sanitized_info)
|
|
|
|
|
|
func sanitize_player_info(info: Dictionary, fallback_nick: String) -> Dictionary:
|
|
return {
|
|
"nick": sanitize_nickname(str(info.get("nick", "")), fallback_nick),
|
|
"skin": sanitize_skin_value(info.get("skin", Character.SkinColor.BLUE))
|
|
}
|
|
|
|
|
|
func sanitize_nickname(nickname: String, fallback: String) -> String:
|
|
var clean := ""
|
|
var last_was_space := false
|
|
for i in range(nickname.length()):
|
|
var codepoint := nickname.unicode_at(i)
|
|
if codepoint <= 31 or codepoint == 127:
|
|
if not last_was_space:
|
|
clean += " "
|
|
last_was_space = true
|
|
continue
|
|
|
|
var character := nickname.substr(i, 1)
|
|
if character == " ":
|
|
if last_was_space:
|
|
continue
|
|
last_was_space = true
|
|
else:
|
|
last_was_space = false
|
|
clean += character
|
|
|
|
clean = clean.strip_edges()
|
|
if clean.is_empty():
|
|
clean = fallback.strip_edges()
|
|
if clean.length() > MAX_NICK_LENGTH:
|
|
clean = clean.substr(0, MAX_NICK_LENGTH).strip_edges()
|
|
if clean.is_empty():
|
|
clean = "Player"
|
|
return clean
|
|
|
|
|
|
func sanitize_address(address: String) -> String:
|
|
var clean = address.strip_edges()
|
|
if clean.is_empty():
|
|
return SERVER_ADDRESS
|
|
if clean.length() > MAX_ADDRESS_LENGTH:
|
|
return ""
|
|
if clean.contains("://") or clean.contains("/") or clean.contains("\\") or clean.contains(":"):
|
|
return ""
|
|
return clean
|
|
|
|
|
|
func sanitize_skin_value(value) -> Character.SkinColor:
|
|
if value is int:
|
|
match value:
|
|
Character.SkinColor.BLUE, Character.SkinColor.YELLOW, Character.SkinColor.GREEN, Character.SkinColor.RED:
|
|
return value
|
|
_:
|
|
return Character.SkinColor.BLUE
|
|
return skin_str_to_e(str(value))
|