@ -0,0 +1,16 @@ | |||
[gd_resource type="Theme" load_steps=3 format=2] | |||
[ext_resource path="res://assets/DejaVuSansMono.ttf" type="DynamicFontData" id=1] | |||
[sub_resource type="DynamicFont" id=1] | |||
size = 18 | |||
use_mipmaps = false | |||
use_filter = false | |||
font_data = ExtResource( 1 ) | |||
_sections_unfolded = [ "Font", "Settings" ] | |||
[resource] | |||
default_font = SubResource( 1 ) | |||
@ -0,0 +1,232 @@ | |||
[gd_scene load_steps=5 format=2] | |||
[ext_resource path="res://assets/theme.tres" type="Theme" id=1] | |||
[ext_resource path="res://scripts/custom_game.gd" type="Script" id=2] | |||
[ext_resource path="res://assets/DejaVuSansMono.ttf" type="DynamicFontData" id=3] | |||
[sub_resource type="DynamicFont" id=1] | |||
size = 30 | |||
use_mipmaps = false | |||
use_filter = false | |||
font_data = ExtResource( 3 ) | |||
[node name="CustomGame" type="Control"] | |||
anchor_left = 0.0 | |||
anchor_top = 0.0 | |||
anchor_right = 0.0 | |||
anchor_bottom = 0.0 | |||
margin_right = 40.0 | |||
margin_bottom = 40.0 | |||
rect_pivot_offset = Vector2( 0, 0 ) | |||
mouse_filter = 0 | |||
mouse_default_cursor_shape = 0 | |||
size_flags_horizontal = 1 | |||
size_flags_vertical = 1 | |||
theme = ExtResource( 1 ) | |||
script = ExtResource( 2 ) | |||
[node name="VSeparator" type="VSeparator" parent="." index="0"] | |||
anchor_left = 0.0 | |||
anchor_top = 0.0 | |||
anchor_right = 0.0 | |||
anchor_bottom = 0.0 | |||
margin_left = 498.0 | |||
margin_top = 140.0 | |||
margin_right = 518.0 | |||
margin_bottom = 481.0 | |||
rect_pivot_offset = Vector2( 0, 0 ) | |||
mouse_filter = 0 | |||
mouse_default_cursor_shape = 0 | |||
size_flags_horizontal = 1 | |||
size_flags_vertical = 1 | |||
[node name="Label" type="Label" parent="." index="1"] | |||
anchor_left = 0.0 | |||
anchor_top = 0.0 | |||
anchor_right = 0.0 | |||
anchor_bottom = 0.0 | |||
margin_left = 54.0 | |||
margin_top = 66.0 | |||
margin_right = 395.0 | |||
margin_bottom = 102.0 | |||
rect_pivot_offset = Vector2( 0, 0 ) | |||
mouse_filter = 2 | |||
mouse_default_cursor_shape = 0 | |||
size_flags_horizontal = 1 | |||
size_flags_vertical = 4 | |||
custom_fonts/font = SubResource( 1 ) | |||
text = "Custom Game" | |||
percent_visible = 1.0 | |||
lines_skipped = 0 | |||
max_lines_visible = -1 | |||
[node name="Server" type="Button" parent="." index="2"] | |||
anchor_left = 0.0 | |||
anchor_top = 0.0 | |||
anchor_right = 0.0 | |||
anchor_bottom = 0.0 | |||
margin_left = 73.0 | |||
margin_top = 195.0 | |||
margin_right = 366.0 | |||
margin_bottom = 252.0 | |||
rect_pivot_offset = Vector2( 0, 0 ) | |||
focus_mode = 2 | |||
mouse_filter = 0 | |||
mouse_default_cursor_shape = 0 | |||
size_flags_horizontal = 1 | |||
size_flags_vertical = 1 | |||
toggle_mode = false | |||
enabled_focus_mode = 2 | |||
shortcut = null | |||
group = null | |||
text = "Host Game" | |||
flat = false | |||
align = 1 | |||
[node name="Client" type="Button" parent="." index="3"] | |||
anchor_left = 0.0 | |||
anchor_top = 0.0 | |||
anchor_right = 0.0 | |||
anchor_bottom = 0.0 | |||
margin_left = 588.0 | |||
margin_top = 258.0 | |||
margin_right = 890.0 | |||
margin_bottom = 315.0 | |||
rect_pivot_offset = Vector2( 0, 0 ) | |||
focus_mode = 2 | |||
mouse_filter = 0 | |||
mouse_default_cursor_shape = 0 | |||
size_flags_horizontal = 1 | |||
size_flags_vertical = 1 | |||
toggle_mode = false | |||
enabled_focus_mode = 2 | |||
shortcut = null | |||
group = null | |||
text = "Join Game" | |||
flat = false | |||
align = 1 | |||
[node name="IPLabel" type="Label" parent="." index="4"] | |||
anchor_left = 0.0 | |||
anchor_top = 0.0 | |||
anchor_right = 0.0 | |||
anchor_bottom = 0.0 | |||
margin_left = 592.0 | |||
margin_top = 205.0 | |||
margin_right = 625.0 | |||
margin_bottom = 227.0 | |||
rect_pivot_offset = Vector2( 0, 0 ) | |||
mouse_filter = 2 | |||
mouse_default_cursor_shape = 0 | |||
size_flags_horizontal = 1 | |||
size_flags_vertical = 4 | |||
text = "IP:" | |||
percent_visible = 1.0 | |||
lines_skipped = 0 | |||
max_lines_visible = -1 | |||
[node name="IP" type="TextEdit" parent="." index="5"] | |||
anchor_left = 0.0 | |||
anchor_top = 0.0 | |||
anchor_right = 0.0 | |||
anchor_bottom = 0.0 | |||
margin_left = 632.0 | |||
margin_top = 203.0 | |||
margin_right = 891.0 | |||
margin_bottom = 232.0 | |||
rect_pivot_offset = Vector2( 0, 0 ) | |||
focus_mode = 2 | |||
mouse_filter = 0 | |||
mouse_default_cursor_shape = 0 | |||
size_flags_horizontal = 1 | |||
size_flags_vertical = 1 | |||
text = "127.0.0.1" | |||
readonly = false | |||
highlight_current_line = false | |||
syntax_highlighting = false | |||
show_line_numbers = false | |||
highlight_all_occurrences = false | |||
override_selected_font_color = false | |||
context_menu_enabled = true | |||
smooth_scrolling = false | |||
v_scroll_speed = 80.0 | |||
hiding_enabled = 0 | |||
wrap_lines = false | |||
caret_block_mode = false | |||
caret_blink = false | |||
caret_blink_speed = 0.65 | |||
caret_moving_by_right_click = true | |||
[node name="Label2" type="Label" parent="." index="6"] | |||
anchor_left = 0.0 | |||
anchor_top = 0.0 | |||
anchor_right = 0.0 | |||
anchor_bottom = 0.0 | |||
margin_left = 72.0 | |||
margin_top = 145.0 | |||
margin_right = 171.0 | |||
margin_bottom = 167.0 | |||
rect_pivot_offset = Vector2( 0, 0 ) | |||
mouse_filter = 2 | |||
mouse_default_cursor_shape = 0 | |||
size_flags_horizontal = 1 | |||
size_flags_vertical = 4 | |||
text = "Host game" | |||
percent_visible = 1.0 | |||
lines_skipped = 0 | |||
max_lines_visible = -1 | |||
[node name="Label3" type="Label" parent="." index="7"] | |||
anchor_left = 0.0 | |||
anchor_top = 0.0 | |||
anchor_right = 0.0 | |||
anchor_bottom = 0.0 | |||
margin_left = 577.0 | |||
margin_top = 147.0 | |||
margin_right = 617.0 | |||
margin_bottom = 169.0 | |||
rect_pivot_offset = Vector2( 0, 0 ) | |||
mouse_filter = 2 | |||
mouse_default_cursor_shape = 0 | |||
size_flags_horizontal = 1 | |||
size_flags_vertical = 4 | |||
text = "Join Game" | |||
percent_visible = 1.0 | |||
lines_skipped = 0 | |||
max_lines_visible = -1 | |||
[node name="Back" type="Button" parent="." index="8"] | |||
anchor_left = 0.0 | |||
anchor_top = 0.0 | |||
anchor_right = 0.0 | |||
anchor_bottom = 0.0 | |||
margin_left = 437.0 | |||
margin_top = 509.0 | |||
margin_right = 581.0 | |||
margin_bottom = 537.0 | |||
rect_pivot_offset = Vector2( 0, 0 ) | |||
focus_mode = 2 | |||
mouse_filter = 0 | |||
mouse_default_cursor_shape = 0 | |||
size_flags_horizontal = 1 | |||
size_flags_vertical = 1 | |||
toggle_mode = false | |||
enabled_focus_mode = 2 | |||
shortcut = null | |||
group = null | |||
text = "Back to menu" | |||
flat = false | |||
align = 1 | |||
@ -0,0 +1,166 @@ | |||
[gd_scene load_steps=5 format=2] | |||
[ext_resource path="res://assets/theme.tres" type="Theme" id=1] | |||
[ext_resource path="res://scripts/menu.gd" type="Script" id=2] | |||
[sub_resource type="DynamicFontData" id=1] | |||
font_path = "res://assets/DejaVuSansMono.ttf" | |||
[sub_resource type="DynamicFont" id=2] | |||
size = 30 | |||
use_mipmaps = false | |||
use_filter = false | |||
font_data = SubResource( 1 ) | |||
[node name="Menu" type="Control"] | |||
anchor_left = 0.0 | |||
anchor_top = 0.0 | |||
anchor_right = 0.0 | |||
anchor_bottom = 0.0 | |||
margin_right = 1024.0 | |||
margin_bottom = 600.0 | |||
rect_pivot_offset = Vector2( 0, 0 ) | |||
mouse_filter = 0 | |||
mouse_default_cursor_shape = 0 | |||
size_flags_horizontal = 1 | |||
size_flags_vertical = 1 | |||
theme = ExtResource( 1 ) | |||
script = ExtResource( 2 ) | |||
[node name="Title" type="Label" parent="." index="0"] | |||
anchor_left = 0.0 | |||
anchor_top = 0.0 | |||
anchor_right = 0.0 | |||
anchor_bottom = 0.0 | |||
margin_left = 60.0 | |||
margin_top = 50.0 | |||
margin_right = 248.0 | |||
margin_bottom = 90.0 | |||
rect_pivot_offset = Vector2( 0, 0 ) | |||
mouse_filter = 2 | |||
mouse_default_cursor_shape = 0 | |||
size_flags_horizontal = 1 | |||
size_flags_vertical = 4 | |||
custom_fonts/font = SubResource( 2 ) | |||
text = "VANAGLORIA" | |||
percent_visible = 1.0 | |||
lines_skipped = 0 | |||
max_lines_visible = -1 | |||
[node name="Center" type="Control" parent="." index="1"] | |||
anchor_left = 0.5 | |||
anchor_top = 0.5 | |||
anchor_right = 0.5 | |||
anchor_bottom = 0.5 | |||
margin_left = -20.0 | |||
margin_top = -20.0 | |||
margin_right = 20.0 | |||
margin_bottom = 20.0 | |||
rect_pivot_offset = Vector2( 0, 0 ) | |||
mouse_filter = 0 | |||
mouse_default_cursor_shape = 0 | |||
size_flags_horizontal = 1 | |||
size_flags_vertical = 1 | |||
[node name="Play" type="Button" parent="Center" index="0"] | |||
anchor_left = 0.0 | |||
anchor_top = 0.0 | |||
anchor_right = 0.0 | |||
anchor_bottom = 0.0 | |||
margin_left = -250.0 | |||
margin_top = -70.0 | |||
margin_right = 298.0 | |||
margin_bottom = -10.0 | |||
rect_pivot_offset = Vector2( 0, 0 ) | |||
focus_mode = 2 | |||
mouse_filter = 0 | |||
mouse_default_cursor_shape = 0 | |||
size_flags_horizontal = 1 | |||
size_flags_vertical = 1 | |||
toggle_mode = false | |||
enabled_focus_mode = 2 | |||
shortcut = null | |||
group = null | |||
text = "Quick Play" | |||
flat = false | |||
align = 1 | |||
[node name="CustomGame" type="Button" parent="Center" index="1"] | |||
anchor_left = 0.0 | |||
anchor_top = 0.0 | |||
anchor_right = 0.0 | |||
anchor_bottom = 0.0 | |||
margin_left = -252.0 | |||
margin_top = 14.0 | |||
margin_right = 300.0 | |||
margin_bottom = 75.0 | |||
rect_pivot_offset = Vector2( 0, 0 ) | |||
focus_mode = 2 | |||
mouse_filter = 0 | |||
mouse_default_cursor_shape = 0 | |||
size_flags_horizontal = 1 | |||
size_flags_vertical = 1 | |||
toggle_mode = false | |||
enabled_focus_mode = 2 | |||
shortcut = null | |||
group = null | |||
text = "Custom Game" | |||
flat = false | |||
align = 1 | |||
[node name="Singleplayer" type="Button" parent="Center" index="2"] | |||
anchor_left = 0.0 | |||
anchor_top = 0.0 | |||
anchor_right = 0.0 | |||
anchor_bottom = 0.0 | |||
margin_left = -251.0 | |||
margin_top = 104.0 | |||
margin_right = 299.0 | |||
margin_bottom = 170.0 | |||
rect_pivot_offset = Vector2( 0, 0 ) | |||
focus_mode = 2 | |||
mouse_filter = 0 | |||
mouse_default_cursor_shape = 0 | |||
size_flags_horizontal = 1 | |||
size_flags_vertical = 1 | |||
toggle_mode = false | |||
enabled_focus_mode = 2 | |||
shortcut = null | |||
group = null | |||
text = "Practice Range" | |||
flat = false | |||
align = 1 | |||
[node name="Quit" type="Button" parent="Center" index="3"] | |||
anchor_left = 0.0 | |||
anchor_top = 0.0 | |||
anchor_right = 0.0 | |||
anchor_bottom = 0.0 | |||
margin_left = -252.0 | |||
margin_top = 200.0 | |||
margin_right = 298.0 | |||
margin_bottom = 266.0 | |||
rect_pivot_offset = Vector2( 0, 0 ) | |||
focus_mode = 2 | |||
mouse_filter = 0 | |||
mouse_default_cursor_shape = 0 | |||
size_flags_horizontal = 1 | |||
size_flags_vertical = 1 | |||
toggle_mode = false | |||
enabled_focus_mode = 2 | |||
shortcut = null | |||
group = null | |||
text = "Quit" | |||
flat = false | |||
align = 1 | |||
@ -0,0 +1,87 @@ | |||
[gd_scene load_steps=4 format=2] | |||
[ext_resource path="res://scenes/lobby.tscn" type="PackedScene" id=1] | |||
[ext_resource path="res://assets/DejaVuSansMono.ttf" type="DynamicFontData" id=2] | |||
[sub_resource type="DynamicFont" id=1] | |||
size = 40 | |||
use_mipmaps = false | |||
use_filter = false | |||
font_data = ExtResource( 2 ) | |||
[node name="Lobby" instance=ExtResource( 1 )] | |||
[node name="Label" type="Label" parent="." index="0"] | |||
anchor_left = 0.0 | |||
anchor_top = 0.0 | |||
anchor_right = 0.0 | |||
anchor_bottom = 0.0 | |||
margin_left = 602.0 | |||
margin_top = 276.0 | |||
margin_right = 938.0 | |||
margin_bottom = 324.0 | |||
rect_pivot_offset = Vector2( 0, 0 ) | |||
mouse_filter = 2 | |||
mouse_default_cursor_shape = 0 | |||
size_flags_horizontal = 1 | |||
size_flags_vertical = 4 | |||
custom_fonts/font = SubResource( 1 ) | |||
text = "Practice Range" | |||
percent_visible = 1.0 | |||
lines_skipped = 0 | |||
max_lines_visible = -1 | |||
[node name="HeroSelect" parent="." index="1"] | |||
margin_top = 62.0 | |||
margin_bottom = 62.0 | |||
[node name="Username" parent="." index="2"] | |||
visible = false | |||
margin_top = 308.0 | |||
margin_bottom = 341.0 | |||
[node name="Spectating" parent="." index="3"] | |||
visible = false | |||
[node name="LevelSelect" parent="." index="4"] | |||
margin_top = 401.0 | |||
margin_bottom = 442.0 | |||
items = [ "Platform map", null, false, 0, null, "City-like thing", null, false, 1, null, "Slide", null, false, 2, null ] | |||
[node name="TeamLabel" parent="." index="5"] | |||
visible = false | |||
[node name="Team" parent="." index="6"] | |||
visible = false | |||
[node name="PlayerList" parent="." index="7"] | |||
visible = false | |||
[node name="Ready" parent="." index="8"] | |||
visible = false | |||
[node name="StartGame" parent="." index="9"] | |||
visible = true | |||
margin_left = 41.0 | |||
margin_top = 486.0 | |||
margin_right = 163.0 | |||
margin_bottom = 526.0 | |||
text = "Play!" | |||
[node name="VSeparator" parent="." index="10"] | |||
visible = false | |||
[editable path="HeroSelect"] |
@ -0,0 +1,14 @@ | |||
extends Control | |||
func _ready(): | |||
get_node("Server").connect("pressed", self, "_start_server") | |||
get_node("Client").connect("pressed", self, "_start_client") | |||
get_node("Back").connect("pressed", get_tree(), "change_scene", ["res://scenes/menu.tscn"]) | |||
func _start_server(): | |||
# Custom Game can assume we're playing as well | |||
networking.start_server() | |||
func _start_client(): | |||
var ip = get_node("IP").text | |||
networking.start_client(ip) |
@ -1,275 +1,114 @@ | |||
extends "res://scripts/args.gd" | |||
extends Control | |||
# class member variables go here, for example: | |||
# var a = 2 | |||
# var b = "textvar" | |||
var SERVER_PORT = 54672 | |||
var MAX_PLAYERS = 10 | |||
var player_info = {} | |||
var my_info = {} | |||
var begun = false | |||
var server_playing = true | |||
var global_server_ip = "nv.cosinegaming.com" | |||
var players_done = [] | |||
var is_connected = false # Technically this can be done with ENetcetera but it's easier this way | |||
func setup_options(): | |||
var opts = Options.new() | |||
opts.set_banner(('A non-violent MOBA inspired by Overwatch and Zineth')) | |||
opts.add('-singleplayer', false, 'Whether to run singeplayer, starting immediately') | |||
opts.add('-server', false, 'Whether to run as server') | |||
opts.add('-client', false, 'Immediately connect as client') | |||
opts.add('-silent', false, 'If the server is not playing, merely serving') | |||
opts.add('-hero', 'r', 'Your choice of hero (index)') | |||
opts.add('-level', 'r', 'Your choice of level (index) - server only!') | |||
opts.add('-start-game', false, 'Join as a client and immediately start the game') | |||
opts.add('-ai', true, 'Run this client as AI') | |||
opts.add('-no-record', true, "Don't record this play for AI later") | |||
opts.add('-h', false, "Print help") | |||
return opts | |||
func option_sel(button_name, option): | |||
var button = get_node(button_name) | |||
if option == "r": | |||
option = randi() % button.get_item_count() | |||
else: | |||
option = int(option) | |||
button.select(option) | |||
onready var hero_select = get_node("HeroSelect/Hero") | |||
onready var level_select = get_node("LevelSelect") | |||
onready var start_game_button = get_node("StartGame") | |||
onready var ready_button = get_node("Ready") | |||
func _ready(): | |||
my_info.version = [0,0,0] # Semantic versioning: [0].[1].[2] | |||
randomize() | |||
get_node("GameBrowser/Play").connect("pressed", self, "connect_global_server") | |||
get_node("PlayerSettings/HeroSelect").connect("item_selected", self, "select_hero") | |||
get_node("PlayerSettings/Username").connect("text_changed", self, "resend_name") | |||
get_node("JoinedGameLobby/StartGame").connect("pressed", self, "start_game") | |||
get_node("CustomGame/Server").connect("pressed", self, "_server_init") | |||
get_node("CustomGame/Client").connect("pressed", self, "_client_init") | |||
get_node("CustomGame/Singleplayer").connect("pressed", self, "_singleplayer_init") | |||
get_node("CustomGame/LevelSelect").connect("item_selected", self, "select_level") | |||
var o = setup_options() | |||
o.parse() | |||
if o.get_value("-silent"): | |||
server_playing = false # TODO: Uncaps :( | |||
if o.get_value("-hero"): | |||
var hero = o.get_value("-hero") | |||
option_sel("PlayerSettings/HeroSelect", hero) | |||
# For some reason, calling option_sel doesn't trigger the actual selection | |||
select_hero(get_node("PlayerSettings/HeroSelect").get_selected_id()) | |||
if o.get_value("-level"): | |||
option_sel("CustomGame/LevelSelect", o.get_value("-level")) | |||
if o.get_value("-server"): | |||
call_deferred("_server_init") | |||
if o.get_value("-client"): | |||
call_deferred("_client_init") | |||
if o.get_value("-start-game"): | |||
my_info.start_game = true | |||
call_deferred("_client_init") | |||
if o.get_value("-singleplayer"): | |||
call_deferred("_singleplayer_init") | |||
if o.get_value("-ai"): | |||
my_info.is_ai = true | |||
if not o.get_value("-no-record") and not o.get_value("-ai"): | |||
my_info.record = true | |||
if o.get_value('-h'): | |||
o.print_help() | |||
quit() | |||
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") | |||
func connect_global_server(): | |||
_client_init(global_server_ip) | |||
func _client_init(ip=null): | |||
collect_info() | |||
var peer = NetworkedMultiplayerENet.new() | |||
if not ip: | |||
ip = get_node("CustomGame/IP").get_text() | |||
ip = IP.resolve_hostname(ip) | |||
peer.create_client(ip, SERVER_PORT) | |||
get_tree().set_network_peer(peer) | |||
get_node("CustomGame/Client").set_text("Clienting!") | |||
func _singleplayer_init(): | |||
collect_info() | |||
var peer = NetworkedMultiplayerENet.new() | |||
peer.create_server(SERVER_PORT, 1) | |||
get_tree().set_network_peer(peer) | |||
player_info[1] = my_info | |||
start_game() | |||
func _server_init(): | |||
collect_info() | |||
var peer = NetworkedMultiplayerENet.new() | |||
peer.create_server(SERVER_PORT, MAX_PLAYERS) | |||
get_tree().set_network_peer(peer) | |||
is_connected = true | |||
get_node("CustomGame/Server").set_text("Serving!") | |||
get_node("JoinedGameLobby").show() | |||
if server_playing: | |||
player_info[1] = my_info | |||
# Connect (to networking) | |||
get_node("Username").connect("text_changed", self, "_send_name") | |||
get_node("Spectating").connect("toggled", self, "_set_info_callback", ["spectating"]) | |||
ready_button.connect("toggled", self, "_set_info_callback", ["ready"]) | |||
start_game_button.connect("pressed", networking, "start_game") | |||
# Connect (from networking) | |||
networking.connect("info_updated", self, "_render_player_list") | |||
get_tree().connect("connected_to_server", self, "_connected") | |||
# Connect (static) | |||
get_node("Back").connect("pressed", self, "_exit_to_menu") | |||
var spectating = util.args.get_value("-silent") | |||
get_node("Spectating").pressed = spectating | |||
# Shown, maybe, in _check_begun | |||
start_game_button.hide() | |||
if get_tree().is_network_server(): | |||
start_game_button.show() | |||
func _player_connected(id): | |||
pass | |||
if get_tree().is_network_server(): | |||
# We put level in our players dict because it's automatically broadcast to other players | |||
var level = util.args.get_value("-level") | |||
if level == "r": | |||
level = randi() % level_select.get_item_count() | |||
level = int(level) | |||
_set_level(level) | |||
level_select.show() | |||
level_select.select(level) | |||
level_select.connect("item_selected", self, "_set_level") | |||
else: | |||
level_select.hide() | |||
func _player_disconnected(id): | |||
if get_tree().is_network_server(): | |||
rpc("unregister_player", id) | |||
call_deferred("render_player_list") | |||
_connected() | |||
func _connected_ok(): | |||
rpc("register_player", get_tree().get_network_unique_id(), my_info) | |||
if "start_game" in my_info and my_info.start_game: | |||
rpc_id(1, "start_game") | |||
get_node("JoinedGameLobby").show() | |||
is_connected = true | |||
func _connected(): | |||
func collect_info(): | |||
if not "username" in my_info: | |||
my_info.username = get_node("PlayerSettings/Username").get_text() | |||
if not "hero" in my_info: | |||
my_info.hero = get_node("PlayerSettings/HeroSelect").get_selected_id() | |||
if not "is_right_team" in my_info: | |||
my_info.is_right_team = false # Server assigns team, wait for that | |||
_send_name() | |||
networking.set_info_from_server("ready", false) | |||
networking.set_info_from_server("spectating", util.args.get_value("-silent")) | |||
networking.set_info_from_server("begun", false) | |||
if util.args.get_value("-hero") == "r": | |||
hero_select.random_hero() | |||
else: | |||
hero_select.set_hero(int(util.args.get_value("-hero"))) | |||
remote func register_player(new_peer, info): | |||
player_info[new_peer] = info | |||
render_player_list() | |||
if (get_tree().is_network_server()): | |||
var right_team_count = 0 | |||
# Send current players' info to new player | |||
for old_peer in player_info: | |||
# Send new player, old player's info | |||
rpc_id(new_peer, "register_player", old_peer, player_info[old_peer]) | |||
if old_peer != new_peer: | |||
# We need to assign team later, so count current | |||
if player_info[old_peer].is_right_team: | |||
right_team_count += 1 | |||
# You'd think this part could be met with a simple `rpc(`, but actually it can't | |||
# My best guess is this is because we haven't registered the names yet, but I'm not sure (TODO) | |||
if old_peer != 1: | |||
# Send old player, new player's info (not us, no infinite loop) | |||
rpc_id(old_peer, "register_player", new_peer, info) | |||
if begun: | |||
rpc_id(old_peer, "spawn_player", new_peer) | |||
rpc_id(old_peer, "begin_player_deferred", new_peer) # Spawning is deferred | |||
if not server_playing: | |||
# We didn't catch this in player_info | |||
rpc_id(1, "spawn_player", new_peer) | |||
rpc_id(1, "begin_player_deferred", new_peer) # Spawning is deferred | |||
var assign_right_team = right_team_count * 2 < player_info.size() | |||
rpc("assign_team", new_peer, assign_right_team) | |||
if not begun and player_info.size() == MAX_PLAYERS: | |||
start_game() | |||
if begun: | |||
rpc_id(new_peer, "pre_configure_game", my_info.level) | |||
rpc_id(new_peer, "post_configure_game") | |||
if util.args.get_value("-start-game"): | |||
networking.start_game() | |||
sync func unregister_player(peer): | |||
player_info.erase(peer) | |||
get_node("/root/Level/Players/%d" % peer).queue_free() | |||
func _set_level(level): | |||
networking.level = level | |||
func select_hero(hero): | |||
var description = get_node("PlayerSettings/HeroSelect").hero_text[hero] | |||
get_node("PlayerSettings/HeroDescription").set_text(description) | |||
if is_connected: | |||
rpc("set_hero", get_tree().get_network_unique_id(), hero) | |||
# Because of the annoying way callbacks work (automatic parameter, optional parameter) | |||
# We need a utility function for making these kinds of callbacks for set_info | |||
func _set_info_callback(value, key): | |||
networking.set_info_from_server(key, value) | |||
sync func set_hero(peer, hero): | |||
player_info[peer].hero = hero | |||
render_player_list() | |||
func resend_name(): | |||
if is_connected: | |||
var name = get_node("PlayerSettings/Username").get_text() | |||
rpc("set_name", get_tree().get_network_unique_id(), name) | |||
sync func set_name(peer, name): | |||
player_info[peer].username = name | |||
render_player_list() | |||
sync func assign_team(peer, is_right_team): | |||
player_info[peer].is_right_team = is_right_team | |||
if peer == get_tree().get_network_unique_id(): | |||
if is_right_team: | |||
get_node("PlayerSettings/Team").set_text("Right Team") | |||
else: | |||
get_node("PlayerSettings/Team").set_text("Left Team") | |||
render_player_list() | |||
func render_player_list(): | |||
if has_node("PlayerSettings"): | |||
var list = "" | |||
var hero_names = get_node("PlayerSettings/HeroSelect").hero_names | |||
for p in player_info: | |||
list += "%-15s" % player_info[p].username | |||
list += "%-20s" % hero_names[player_info[p].hero] | |||
if player_info[p].is_right_team: | |||
list += "Right Team" | |||
else: | |||
list += "Left Team" | |||
networking.players[peer].hero = hero | |||
_render_player_list() | |||
func _send_name(): | |||
var name = get_node("Username").text | |||
networking.set_info_from_server("username", name) | |||
func _check_begun(): | |||
if networking.players.has(1) and networking.players[1].has("begun"): | |||
var game_started = networking.players[1].begun | |||
if game_started: | |||
start_game_button.show() | |||
start_game_button.text = "Join game" | |||
# The "Ready" toggle doesn't really make sense on a started game | |||
ready_button.hide() | |||
func _render_player_list(): | |||
_check_begun() | |||
var list = "" | |||
var hero_names = hero_select.hero_names | |||
for p in networking.players: | |||
var player = networking.players[p] | |||
# A spectating server is just a dedicated server, ignore it | |||
if p and player.has("spectating") and not (player.spectating and p == 1): | |||
var username = player.username if player.has("username") else "Loading..." | |||
list += "%-15s " % username | |||
var hero = hero_names[player.hero] if player.has("hero") else "Loading..." | |||
list += "%-10s " % hero | |||
var team = "Loading..." | |||
if player.has("is_right_team"): | |||
if player.is_right_team: | |||
team = "Right Team" | |||
else: | |||
team = "Left Team" | |||
list += "%-11s" % team | |||
var ready_text = "Ready" if player.has("ready") and player.ready else "" | |||
list += "%-6s" % ready_text | |||
if player.has("spectating") and player.spectating: | |||
list += "Spectating" | |||
list += "\n" | |||
get_node("JoinedGameLobby/PlayerList").set_text(list) | |||
sync func start_game(): | |||
my_info.level = get_node("CustomGame/LevelSelect").get_selected_id() | |||
rpc("pre_configure_game", my_info.level) | |||
sync func done_preconfiguring(who): | |||
players_done.append(who) | |||
if players_done.size() == player_info.size(): | |||
# We call deferred in case singleplayer has placing the player in queue still | |||
call_deferred("rpc", "post_configure_game") | |||
sync func spawn_player(p): | |||
var hero = player_info[p].hero | |||
var player = load("res://scenes/heroes/" + str(hero) + ".tscn").instance() | |||
player.set_name(str(p)) | |||
player.set_network_master(p) | |||
player.player_info = player_info[p] | |||
get_node("/root/Level/Players").call_deferred("add_child", player) | |||
sync func pre_configure_game(level): | |||
begun = true | |||
my_info.level = level # Remember the level for future player registration | |||
var self_peer_id = get_tree().get_network_unique_id() | |||
# Remove the interface so as to not fuck with things | |||
# But we still need the lobby alive to deal with networking! | |||
for element in get_node("/root/Lobby").get_children(): | |||
element.queue_free() | |||
var world = load("res://scenes/levels/%d.tscn" % level).instance() | |||
get_node("/root").add_child(world) | |||
# Load all players (including self) | |||
for p in player_info: | |||
player_info[p].level = level | |||
spawn_player(p) | |||
rpc_id(1, "done_preconfiguring", self_peer_id) | |||
func begin_player(peer): | |||
get_node("/root/Level/Players/%d" % peer).begin() | |||
remote func begin_player_deferred(peer): | |||
call_deferred("begin_player", peer) | |||
sync func reset_state(): | |||
players_done = [] | |||
get_node("/root/Level").queue_free() | |||
get_node("PlayerList").set_text(list) | |||
sync func post_configure_game(): | |||
# Begin all players (including self) | |||
for p in player_info: | |||
begin_player_deferred(p) | |||
func _exit_to_menu(): | |||
get_tree().network_peer.close_connection() | |||
get_tree().change_scene("res://scenes/menu.tscn") | |||
@ -0,0 +1,105 @@ | |||
extends Node | |||
var SERVER_TO_SERVER_PORT = 54671 | |||
var MATCHMAKING_PORT = 54672 | |||
var GAME_SIZE = 6 | |||
# Number of games we can make without blowing up the computer | |||
var MAX_GAMES = 50 # Based on how many ports I decided to forward | |||
var next_port = 54673 | |||
# Filled with queue info which contains | |||
# { "netid" } | |||
var skirmishing_players = [] | |||
var skirmish | |||
# To avoid the confusion of the gameSERVERS being CLIENTS, | |||
# we just call them games whenever possible | |||
# var games = [] | |||
var game_connections = [] | |||
var game_streams = [] | |||
# Matchmaker to game servers | |||
var matchmaker_to_games | |||
enum messages { | |||
ready_to_connect, | |||
} | |||
onready var lobby = get_node("..") | |||
func _ready(): | |||
# By default, having this node doesn't do naything | |||
# You must call start_matchmaker to enable it | |||
# If not called, don't call _process (= don't matchmake) | |||
set_process(false) | |||
func start_matchmaker(): | |||
# Actually run the matchmaker | |||
set_process(true) | |||
# Setup skirmish server | |||
skirmish = spawn_server(true) | |||
# Set up communication between GAMESERVERS | |||
# This is necessary for eg, when a player leaves to backfill | |||
matchmaker_to_games = TCP_Server.new() | |||
if matchmaker_to_games.listen(SERVER_TO_SERVER_PORT): | |||
print("Error, could not listen") | |||
# Use ENet for matchmaker because we can (makes client code cleaner) | |||
var matchmaker_to_players = NetworkedMultiplayerENet.new() | |||
print("Starting matchmaker on port " + str(MATCHMAKING_PORT)) | |||
matchmaker_to_players.create_server(MATCHMAKING_PORT, MAX_GAMES) | |||
get_tree().set_network_peer(matchmaker_to_players) | |||
matchmaker_to_players.connect("peer_connected", self, "queue") | |||
func _process(delta): | |||
# Manage connection to GAMESERVERS (not clients) | |||
if matchmaker_to_games.is_connection_available(): # check if a gameserver's trying to connect | |||
var game = matchmaker_to_games.take_connection() # accept connection | |||
game_connections.append(game) # store the connection | |||
var stream = PacketPeerStream.new() | |||
stream.set_stream_peer(game) # bind peerstream to new client | |||
game_streams.append(stream) # make new data transfer object for game | |||
for stream in game_streams: | |||
if stream.get_available_packet_count(): | |||
var message = stream.get_var() | |||
if message == messages.ready_to_connect: | |||
var port = stream.get_var() | |||
print("Server " + str(port) + " has requested connection") | |||
skirmish_to_game(port, GAME_SIZE) | |||
func queue(netid): | |||
print("Player " + str(netid) + " connected.") | |||
add_to_game(netid, skirmish) | |||
skirmishing_players.append(netid) | |||
check_queue() | |||
func add_to_game(netid, port): | |||
networking.rpc_id(netid, "reconnect", networking.global_server_ip, port) | |||
func skirmish_to_game(port, count=1): | |||
for i in range(count): | |||
if not skirmishing_players.size(): | |||
return false | |||
print(skirmishing_players[0]) | |||
print("to") | |||
print(port) | |||
add_to_game(skirmishing_players[0], port) | |||
return true | |||
func check_queue(): | |||
# Prefer making a full game to backfilling | |||
if skirmishing_players.size() >= GAME_SIZE: | |||
spawn_server() | |||
# games.append(port) | |||
func spawn_server(skirmish=false): | |||
var args = ['-port='+str(next_port)] | |||
if skirmish: | |||
# Begin skirmish immediately, so players "join" instead of "ready" | |||
args.append("-start-game") | |||
OS.execute("util/server.sh", args, false) | |||
next_port += 1 | |||
return (next_port - 1) # Return original port | |||
@ -0,0 +1,56 @@ | |||
extends Control | |||
func _ready(): | |||
randomize() | |||
_gui_setup() | |||
_arg_actions() | |||
# GUI | |||
func _gui_setup(): | |||
get_node("Center/Play").connect("pressed", self, "_find_game") | |||
get_node("Center/CustomGame").connect("pressed", self, "_custom_game") | |||
get_node("Center/Singleplayer").connect("pressed", self, "_singleplayer") | |||
get_node("Center/Quit").connect("pressed", get_tree(), "quit") | |||
func _find_game(): | |||
var ip = networking.global_server_ip | |||
# var ip = util.args.get_value("-ip") | |||
var port = networking.matchmaking.MATCHMAKING_PORT | |||
networking.start_client(ip, port) | |||
func _custom_game(): | |||
get_tree().change_scene("res://scenes/custom_game.tscn") | |||
func _singleplayer(): | |||
networking.start_server() | |||
get_tree().change_scene("res://scenes/singleplayer_lobby.tscn") | |||
# Command line | |||
func _option_sel(button_name, option): | |||
var button = get_node(button_name) | |||
if option == "r": | |||
option = randi() % button.get_item_count() | |||
else: | |||
option = int(option) | |||
button.select(option) | |||
func _arg_actions(): | |||
var o = util.args | |||
# if o.get_value("-ai"): | |||
# my_info.is_ai = true | |||
# if not o.get_value("-no-record") and not o.get_value("-ai"): | |||
# my_info.record = true | |||
if o.get_value("-server"): | |||
networking.start_server() | |||
if o.get_value("-matchmaker"): | |||
networking.matchmaking.start_matchmaker() | |||
if o.get_value("-client"): | |||
networking.start_client() | |||
if o.get_value("-singleplayer"): | |||
_singleplayer() | |||
if o.get_value('-h'): | |||
o.print_help() | |||
get_tree().quit() | |||
@ -0,0 +1,202 @@ | |||
extends Node | |||
# Public variables | |||
# ================ | |||
onready var matchmaking = preload("res://scripts/matchmaking.gd").new() | |||
remote var players = {} | |||
var global_server_ip = "nv.cosinegaming.com" | |||
var matchmaker_tcp | |||
var level | |||
signal info_updated | |||
# Public methods | |||
# ============== | |||
func start_client(ip="", port=0): | |||
if not ip: | |||
ip = util.args.get_value("-ip") | |||
ip = IP.resolve_hostname(ip) | |||
if not port: | |||
port = util.args.get_value("-port") | |||
var peer = NetworkedMultiplayerENet.new() | |||
print("Connecting to " + ip + ":" + str(port)) | |||
peer.create_client(ip, port) | |||
get_tree().set_network_peer(peer) | |||
get_tree().change_scene("res://scenes/lobby.tscn") | |||
remote func reconnect(ip, port): | |||
# Reset previously known players | |||
players = {} | |||
start_client(ip, port) | |||
func start_server(port=0): | |||
if not port: | |||
port = util.args.get_value("-port") | |||
var peer = NetworkedMultiplayerENet.new() | |||
print("Starting server on port " + str(port)) | |||
peer.create_server(port, matchmaking.GAME_SIZE) | |||
get_tree().set_network_peer(peer) | |||
# As soon as we're listening, let the matchmaker know | |||
_connect_to_matchmaker(port) | |||
_register_player(get_tree().get_network_unique_id()) | |||
if util.args.get_value("-silent"): | |||
set_info("spectating", true) | |||
get_tree().change_scene("res://scenes/lobby.tscn") | |||
master func set_info(key, value, peer=0): | |||
if not peer: | |||
peer = get_tree().get_network_unique_id() | |||
rpc("_set_info", str(key), value, peer) | |||
# When connectivity is not yet guaranteed, the only one we know is always | |||
# connected to everyone is the server. So in initial handshakes, it's better to | |||
# tell the server what to tell everyone to do | |||
func set_info_from_server(key, value, peer=0): | |||
if not peer: | |||
peer = get_tree().get_network_unique_id() | |||
rpc_id(1, "set_info", key, value, peer) | |||
func start_game(): | |||
rpc_id(1, "_start_game") | |||
sync func reset_state(): | |||
for p in players: | |||
players[p].begun = false | |||
# TODO: Do I in fact want to unready everyone automatically? | |||
players[p].ready = false | |||
get_node("/root/Level").queue_free() | |||
# Private methods | |||
# =============== | |||
func _ready(): | |||
add_child(matchmaking) | |||
get_tree().connect("network_peer_disconnected", self, "_unregister_player") | |||
get_tree().connect("network_peer_connected", self, "_register_player") | |||
get_tree().connect("connected_to_server", self, "_on_connect") | |||
connect("info_updated", self, "_check_info") | |||
remote func _register_player(new_peer): | |||
if get_tree().is_network_server(): | |||
# I tell new player about all the existing people | |||
_send_all_info(new_peer) | |||
set_info("is_right_team", _right_team_next(), new_peer) | |||
sync func _unregister_player(peer): | |||
players.erase(peer) | |||
var p = util.get_player(peer) | |||
if p: | |||
p.queue_free() | |||
emit_signal("info_updated") | |||
func _connect_to_matchmaker(game_port): | |||
var matchmaker_peer = StreamPeerTCP.new() | |||
matchmaker_peer.connect_to_host("127.0.0.1", matchmaking.SERVER_TO_SERVER_PORT) | |||
var matchmaker_tcp = PacketPeerStream.new() | |||
matchmaker_tcp.set_stream_peer(matchmaker_peer) | |||
matchmaker_tcp.put_var(matchmaking.messages.ready_to_connect) | |||
matchmaker_tcp.put_var(game_port) | |||
master func _start_game(): | |||
rpc("_pre_configure_game", level) | |||
func _send_all_info(new_peer): | |||
for p in players: | |||
if p != new_peer: | |||
for key in players[p]: | |||
var val = players[p][key] | |||
# TODO: This broadcasts every connected peer, | |||
# which isn't really a problem but it's lazy | |||
set_info(key, val, p) | |||
func _right_team_next(): | |||
var right_team_count = 0 | |||
for p in players: | |||
var player = players[p] | |||
if player.has("is_right_team") and player.is_right_team: | |||
right_team_count += 1 | |||
return (right_team_count <= players.size() / 2) | |||
sync func _set_info(key, value, peer): | |||
if not players.has(peer): | |||
players[peer] = {} | |||
players[peer][key] = value | |||
emit_signal("info_updated") | |||
func _on_connect(): | |||
_register_player(get_tree().get_network_unique_id()) | |||
emit_signal("info_updated") | |||
func _check_info(): | |||
# Check for "everyone is ready" | |||
# Only have 1 person check this, might as well be server | |||
if get_tree().is_network_server(): | |||
var ready = true | |||
var all_done = true | |||
for p in players: | |||
if not players[p].has("spectating") or not players[p].spectating: | |||
if not players[p].has("ready") or not players[p].ready: | |||
ready = false | |||
if not players[p].has("begun") or not players[p].begun: | |||
all_done = false | |||
if all_done: | |||
rpc("_post_configure_game") | |||
elif ready: | |||
# If we're all done, then we don't need to even check a start_game | |||
start_game() | |||
sync func _spawn_player(p): | |||
var hero = 0 | |||
if players[p].has("hero"): | |||
hero = players[p].hero | |||
var player = load("res://scenes/heroes/" + str(hero) + ".tscn").instance() | |||
player.set_name(str(p)) | |||
player.set_network_master(p) | |||
player.player_info = players[p] | |||
get_node("/root/Level/Players").add_child(player) | |||
func _begin_player(p): | |||
var player = util.get_player(p) | |||
player.begin() | |||
sync func _pre_configure_game(level): | |||
var self_peer_id = get_tree().get_network_unique_id() | |||
var self_begun = players[self_peer_id].begun | |||
if not self_begun: | |||
get_node("/root/Lobby").hide() | |||
var world = load("res://scenes/levels/%d.tscn" % level).instance() | |||
get_node("/root").add_child(world) | |||
# Load all players (including self) | |||
for p in players: | |||
if not players[p].spectating: | |||
var existing_player = util.get_player(p) | |||
if not self_begun or not existing_player: | |||
_spawn_player(p) | |||
for p in players: | |||
if not players[p].spectating: | |||
# Begin requires all players | |||
_begin_player(p) | |||
# Why do we check first? Weird error. It's because set_info triggers a | |||
# start_game if everyone is ready | |||
# This causes a stack overflow if we call it from here repeatedly | |||
# So we only change it once, only start_game twice, and avoida segfault | |||
if not self_begun: | |||
set_info("begun", true) | |||
sync func _post_configure_game(): | |||
# Begin all players (including self) | |||
# TODO: What do? Maybe, unpause game? | |||
pass | |||
@ -0,0 +1 @@ | |||
godot-server -matchmaker |
@ -1,2 +1,2 @@ | |||
godot-server -level=2 -silent -server -start-game | |||
godot-server -level=2 -silent -server "$@" | |||