This repository has been archived on 2020-09-30. You can view files and clone it, but cannot push or open issues or pull requests.
odyssey-old/Scenes/Global/Multiplayer.gd

281 lines
7.4 KiB
GDScript

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)