250 lines
6.8 KiB
GDScript
250 lines
6.8 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-a2"
|
|
const GOTM_OVERRIDE := false
|
|
|
|
# Master server entry
|
|
var ms_active := false
|
|
var ms_key := ""
|
|
var server_name := ""
|
|
|
|
var hosting := false
|
|
|
|
export var player_name := ""
|
|
|
|
var player_info := {}
|
|
var round_info := {}
|
|
|
|
onready var scene_manager := $"/root/SceneManager"
|
|
|
|
func _ready():
|
|
randomize()
|
|
player_name = Names.get_random_name()
|
|
|
|
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 #%d)" % 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(map_name: String = "odyssey"):
|
|
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
|
|
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 = "%s's server" % player_name
|
|
player_info[1] = { "name": player_name }
|
|
round_info = { "map": map_name }
|
|
|
|
bind_events()
|
|
var peer := NetworkedMultiplayerENet.new()
|
|
peer.compression_mode = NetworkedMultiplayerENet.COMPRESS_FASTLZ
|
|
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/%s.tscn" % map_name
|
|
])
|
|
|
|
# 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()
|
|
peer.compression_mode = NetworkedMultiplayerENet.COMPRESS_FASTLZ
|
|
|
|
var addr = server.address
|
|
|
|
peer.create_client(addr, SERVER_PORT)
|
|
get_tree().network_peer = peer
|
|
print("Connecting to %s" % 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("%s (%d) disconnected" % [player_info[id].name, id])
|
|
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("%s (%d) connected" % [player_info[id].name, id])
|
|
|
|
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 %s" % endpoint)
|
|
var error = http_request.request(
|
|
"https://%s/%s" % [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():
|
|
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://%s/%s" % [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():
|
|
_ms_request("new", {
|
|
"game_id": MS_GAME_CODE,
|
|
"data": get_game_data()
|
|
})
|
|
|
|
func update_ms_entry():
|
|
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 %s" % response_code)
|
|
if response_code > 299:
|
|
push_error("ms action '%s' returned error: %s - %s" % [action, 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
|