extends Node signal ms_updated(action, data) signal left() # Hosting info const SERVER_PORT = 5513 const MAX_PLAYERS = 30 var port = SERVER_PORT # Throttle network updates of often-changed variables by this many physics frames const SYSTEMS_UPDATE_INTERVAL = 10 # Master server data const MASTER_SERVER_ADDR = "fgms.zyg.ovh" const MASTER_SERVER_UDP_PORT = 9434 const MS_GAME_CODE = "odyssey-0-a1" const GOTM_OVERRIDE = false # Master server entry var ms_active = false var ms_key = "" var server_name = "" onready var gotm_mode = Gotm.is_live() or GOTM_OVERRIDE var hosting = false export var player_name = "" var player_info = {} var round_info = {} onready var scene_manager = $"/root/SceneManager" func _ready(): player_name = "tider-" + str(randi() % 1000) if gotm_mode: Gotm.connect("lobby_changed", self, "_lobby_changed") func bind_events(): get_tree().connect("network_peer_connected", self, "_player_connected") get_tree().connect("network_peer_disconnected", self, "_player_disconnected") get_tree().connect("connected_to_server", self, "_connected_ok") get_tree().connect("connection_failed", self, "_connected_fail") get_tree().connect("server_disconnected", self, "_server_disconnected") func punch_nat(): var socketUDP = PacketPeerUDP.new() if socketUDP.listen(SERVER_PORT) != OK: push_error("error listening on port: " + str(SERVER_PORT)) socketUDP.set_dest_address(MASTER_SERVER_ADDR, MASTER_SERVER_UDP_PORT) socketUDP.put_packet("hi server!".to_ascii()) # Poll for answer from server, since godot doesn't have events for UDP packets (ノへ ̄、) var failed = 0 while failed < 5: yield(get_tree().create_timer(.3), "timeout") if socketUDP.get_available_packet_count() < 1: failed += 1 print("no reply (attempt #", failed, ")") continue port = socketUDP.get_var(false) print("Received ", port) break socketUDP.close() func discover_upnp(): var upnp = UPNP.new() upnp.discover(2000, 2, "InternetGatewayDevice") return upnp.add_port_mapping(SERVER_PORT) func host(): scene_manager.enter_loader() scene_manager.loading_text = "Starting server" # Wait just a sec to draw yield(get_tree().create_timer(0.3), "timeout") # Run port forwarding/nat punchthrough if the platform doesn't do it already if not gotm_mode: print("Running UPNP magicks") if discover_upnp() == UPNP.UPNP_RESULT_SUCCESS: print("UPNP mapping added") else: push_warning("UPNP magicks fail, punching NAT in the face") yield(punch_nat(), "completed") server_name = player_name + "'s server" player_info[1] = { "name": player_name } round_info = { "map": "odyssey" } if gotm_mode: # Add to master server before hosting (in GOTM mode) create_ms_entry() bind_events() var peer = NetworkedMultiplayerENet.new() var server_res = peer.create_server(port, MAX_PLAYERS) if server_res != OK: match server_res: ERR_CANT_CREATE: push_error("Can't create server") ERR_ALREADY_IN_USE: push_error("Already in use") return get_tree().network_peer = peer print("Hosting") hosting = true scene_manager.loading_text = null scene_manager.load_scene("res://Scenes/Game.tscn", [ "res://Scenes/Maps/odyssey.tscn" ]) if not gotm_mode: # Add to master server after hosting create_ms_entry() func join(server): scene_manager.enter_loader() scene_manager.loading_text = "Joining server" # Wait just a sec to draw yield(get_tree().create_timer(0.3), "timeout") bind_events() var peer = NetworkedMultiplayerENet.new() var addr = null if gotm_mode: var success = yield(server.join(), "completed") addr = Gotm.lobby.host.address else: addr = server.address peer.create_client(addr, SERVER_PORT) get_tree().network_peer = peer print("Connecting to ", addr) func leave(): var peer = get_tree().network_peer if get_tree().is_network_server(): # Tell MS we're leaving if ms_active: _ms_request("remove", { "key": ms_key }) yield(self, "ms_updated") elif peer != null: #TODO Send leave message pass get_tree().network_peer = null emit_signal("left") func _player_connected(id): rpc_id(id, "register_player", player_name) if get_tree().is_network_server(): rpc_id(id, "_handshake", { "round": round_info, "players": player_info }) func _player_disconnected(id): print(player_info[id].name, " (", id, ") disconnected") player_info.erase(id) func _connected_ok(): print("Connected to server") scene_manager.loading_text = null scene_manager.load_scene("res://Scenes/Game.tscn", []) func _server_disconnected(): print("Disconnected from server") func _connected_fail(): push_warning("Connection failed") remote func register_player(username: String): var id = get_tree().get_rpc_sender_id() player_info[id] = { name=username } print(player_info[id].name, " (", id, ") connected") remote func _handshake(infos): round_info = infos["round"] player_info = infos["players"] func _ms_request(endpoint: String, data): var http_request = HTTPRequest.new() add_child(http_request) http_request.connect("request_completed", self, "_ms_response", [endpoint]) print_debug("Telling ms to " + endpoint) var error = http_request.request( "https://" + MASTER_SERVER_ADDR + "/" + endpoint, ["Content-Type: application/json"], true, HTTPClient.METHOD_POST, JSON.print(data)) if error != OK: push_error("An error occurred in the HTTP request.") func ms_get_entries(): if gotm_mode: var fetch = GotmLobbyFetch.new() var lobbies = yield(fetch.first(), "completed") emit_signal("ms_updated", "list_games", lobbies) else: var http_request = HTTPRequest.new() add_child(http_request) http_request.connect("request_completed", self, "_ms_response", ["list_games"]) var error = http_request.request( "https://" + MASTER_SERVER_ADDR + "/" + MS_GAME_CODE, ["Content-Type: application/json"], true, HTTPClient.METHOD_GET) if error != OK: push_error("An error occurred in the HTTP request.") func get_game_data(): return { "name": server_name, "port": port, "max_players": MAX_PLAYERS, "players": player_info.size() } func create_ms_entry(): if gotm_mode: Gotm.host_lobby(true) Gotm.lobby.hidden = false else: _ms_request("new", { "game_id": MS_GAME_CODE, "data": get_game_data() }) update_ms_entry() func update_ms_entry(): if gotm_mode: var data = get_game_data() Gotm.lobby.name = data.name for key in data: Gotm.lobby.set_property(key, data[key]) else: if ms_active: _ms_request("update", { "key": ms_key, "data": get_game_data() }) var time_left = 30 func _process(delta): if not ms_active: return time_left -= delta if time_left < 0: update_ms_entry() time_left = 30 func _ms_response(_result: int, response_code: int, _headers: PoolStringArray, body: PoolByteArray, action: String): print_debug("MS said " + str(response_code)) if response_code > 299: push_error("ms action \"" + action + "\" returned error: " + str(response_code) + " - " + body.get_string_from_utf8()) return var json = JSON.parse(body.get_string_from_utf8()) match action: "new": if json.result.ok: ms_active = true ms_key = json.result.key emit_signal("ms_updated", action, json.result) func _notification(what): # Are we quittin son? if what == MainLoop.NOTIFICATION_WM_QUIT_REQUEST: leave() yield(self, "left") func get_current_map(): match round_info.map: "odyssey": return GameWorld.Map.ODYSSEY "runtime": return GameWorld.Map.RUNTIME _: return GameWorld.Map.EMPTY func _lobby_changed(): print("Lobby changed ", Gotm.lobby)