From 6bb81381d44e9ebd69afe4072d6c92fea6da7d1a Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Thu, 10 May 2018 19:01:38 -0400
Subject: [PATCH] [WIP] Split up lobby -> menu, lobby, custom_game

lobby.gd had gotten big, bloated, ugly, and hard to read. It needed a
refactor. The first thing I did is split up the *GUI* into the logical
steps it should be. So the new flow is:

 - Start the game
 - Click "Quick Play"
 - Matchmaking begins on my dedicated server

 - Start the game
 - Click "Custom Game"
 - *Only now* am I presented with server / client options

 - Start the game
 - Click "Singleplayer"
 - Singleplayer

NOW, and only now, and NO MATTER WHAT PATH I TAKE, I am taken to the
*lobby*, which now is where I:
 - Choose my name
 - Choose hero
 - See list of players
 - Get my team assigned
 - Anything else that I might like to put in
The point here is that this has *nothing* to do with handshaking /
matchmaking / etc! This is just part of the game! At this point I have
*already been connected* to the server. I've already been aquainted with
my other players. The game has begun.

I put the things that don't belong in any of these flows in
networking.gd, a sort of model-view sorta thing. All of these flows use
some sort of networking thing like `init_server` that tbh should be
*completely* abstracted from the UI.

It's totally a WIP!!! Above is the IDEA, but below is what I've actually
*done*:
 - Made the scenes, made a passable UI for each one that at least
 indicates ~what they'll do
 - Made the corresponding scripts, and split up the lobby script into
 ABOUT where I think it'll end up, but no promises
It still errors all over the place, and it's nowhere near properly
organized. PLUS, I'd also like to rewrite a lot of the code / rename
things as part of the initial refactor goal of making me able to
actually think about networking.
---
 assets/theme.tres       |  16 ++
 plans.md                |   2 +
 project.godot           |   6 +-
 scenes/custom_game.tscn | 235 +++++++++++++++++++
 scenes/lobby.tscn       | 609 +++++++-----------------------------------------
 scenes/menu.tscn        | 142 +++++++++++
 scripts/custom_game.gd  |   9 +
 scripts/lobby.gd        | 233 +-----------------
 scripts/matchmaking.gd  |   1 -
 scripts/menu.gd         |  83 +++++++
 scripts/networking.gd   | 164 +++++++++++++
 scripts/util.gd         |   2 +
 12 files changed, 739 insertions(+), 763 deletions(-)
 create mode 100644 assets/theme.tres
 create mode 100644 scenes/custom_game.tscn
 create mode 100644 scenes/menu.tscn
 create mode 100644 scripts/custom_game.gd
 create mode 100644 scripts/menu.gd
 create mode 100644 scripts/networking.gd

diff --git a/assets/theme.tres b/assets/theme.tres
new file mode 100644
index 0000000..f1d737a
--- /dev/null
+++ b/assets/theme.tres
@@ -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 )
+
diff --git a/plans.md b/plans.md
index 88f1138..b857052 100644
--- a/plans.md
+++ b/plans.md
@@ -26,6 +26,8 @@ Smaller TODOs:
 - Ira is OP?
   - Nerfed - 5 walls
 - Make motion more reactive?
+- Remember some player settings, probably in a player.json or something
+  - I'm mostly thinking of nickname, but also SR eventually
 
 Bugs:
 
diff --git a/project.godot b/project.godot
index 5d343c5..29645c9 100644
--- a/project.godot
+++ b/project.godot
@@ -11,13 +11,17 @@ config_version=3
 [application]
 
 config/name="nv-moba"
-run/main_scene="res://scenes/lobby.tscn"
+run/main_scene="res://scenes/menu.tscn"
 config/icon="res://icon.png"
 
 [autoload]
 
 util="*res://scripts/util.gd"
 
+[display]
+
+window/stretch/mode="viewport"
+
 [input]
 
 move_forwards=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":44,"unicode":0,"echo":false,"script":null)
diff --git a/scenes/custom_game.tscn b/scenes/custom_game.tscn
new file mode 100644
index 0000000..70b1d21
--- /dev/null
+++ b/scenes/custom_game.tscn
@@ -0,0 +1,235 @@
+[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 = 563.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 = 68.0
+margin_top = 261.0
+margin_right = 272.0
+margin_bottom = 331.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="LevelSelect" type="OptionButton" parent="." index="3"]
+
+anchor_left = 0.0
+anchor_top = 0.0
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_left = 72.0
+margin_top = 201.0
+margin_right = 243.0
+margin_bottom = 236.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
+action_mode = 0
+enabled_focus_mode = 2
+shortcut = null
+group = null
+text = "Platform map"
+flat = false
+align = 0
+selected = 0
+items = [ "Platform map", null, false, 0, null, "City-like thing", null, false, 1, null, "Slide", null, false, 2, null ]
+
+[node name="Client" type="Button" parent="." index="4"]
+
+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 = 812.0
+margin_bottom = 332.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="5"]
+
+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="6"]
+
+anchor_left = 0.0
+anchor_top = 0.0
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_left = 632.0
+margin_top = 202.0
+margin_right = 783.0
+margin_bottom = 231.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="7"]
+
+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="8"]
+
+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
+
+
diff --git a/scenes/lobby.tscn b/scenes/lobby.tscn
index 36112b9..a1a6f2b 100644
--- a/scenes/lobby.tscn
+++ b/scenes/lobby.tscn
@@ -1,18 +1,16 @@
-[gd_scene load_steps=7 format=2]
+[gd_scene load_steps=8 format=2]
 
-[ext_resource path="res://scripts/lobby.gd" type="Script" id=1]
-[ext_resource path="res://scripts/hero_select.gd" type="Script" id=2]
+[ext_resource path="res://assets/theme.tres" type="Theme" id=1]
+[ext_resource path="res://scripts/lobby.gd" type="Script" id=2]
+[ext_resource path="res://scenes/hero_select.tscn" type="PackedScene" id=3]
+[ext_resource path="res://assets/DejaVuSansMono.ttf" type="DynamicFontData" id=4]
 
-[sub_resource type="DynamicFontData" id=1]
-
-font_path = "res://assets/DejaVuSansMono.ttf"
-
-[sub_resource type="DynamicFont" id=2]
+[sub_resource type="DynamicFont" id=5]
 
 size = 30
 use_mipmaps = false
 use_filter = false
-font_data = SubResource( 1 )
+font_data = ExtResource( 4 )
 
 [sub_resource type="DynamicFontData" id=3]
 
@@ -31,45 +29,6 @@ anchor_left = 0.0
 anchor_top = 0.0
 anchor_right = 0.0
 anchor_bottom = 0.0
-margin_left = 80.0
-margin_top = 99.0
-margin_right = 120.0
-margin_bottom = 139.0
-rect_pivot_offset = Vector2( 0, 0 )
-mouse_filter = 0
-mouse_default_cursor_shape = 0
-size_flags_horizontal = 1
-size_flags_vertical = 1
-script = ExtResource( 1 )
-
-[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 = 7.0
-margin_top = -72.0
-margin_right = 195.0
-margin_bottom = -32.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="GameBrowser" type="Control" parent="." index="1"]
-
-editor/display_folded = true
-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 )
@@ -77,158 +36,68 @@ mouse_filter = 0
 mouse_default_cursor_shape = 0
 size_flags_horizontal = 1
 size_flags_vertical = 1
+theme = ExtResource( 1 )
+script = ExtResource( 2 )
 
-[node name="Play" type="Button" parent="GameBrowser" index="0"]
-
-anchor_left = 0.0
-anchor_top = 0.0
-anchor_right = 0.0
-anchor_bottom = 0.0
-margin_left = 9.0
-margin_top = -2.0
-margin_right = 405.0
-margin_bottom = 53.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 = "Just, Play!"
-flat = false
-align = 1
-
-[node name="PickAGameLabel" type="Label" parent="GameBrowser" index="1"]
-
-anchor_left = 0.0
-anchor_top = 0.0
-anchor_right = 0.0
-anchor_bottom = 0.0
-margin_left = 10.0
-margin_top = 65.0
-margin_right = 114.0
-margin_bottom = 79.0
-rect_pivot_offset = Vector2( 0, 0 )
-mouse_filter = 2
-mouse_default_cursor_shape = 0
-size_flags_horizontal = 1
-size_flags_vertical = 4
-text = "Or, pick a game:"
-percent_visible = 1.0
-lines_skipped = 0
-max_lines_visible = -1
+[node name="HeroSelect" parent="." index="0" instance=ExtResource( 3 )]
 
-[node name="GameList" type="ItemList" parent="GameBrowser" index="2"]
+editor/display_folded = true
+margin_left = 28.0
+margin_top = 98.0
+margin_right = 28.0
+margin_bottom = 98.0
+color = Color( 0.097229, 0.104696, 0.105469, 0 )
 
-anchor_left = 0.0
-anchor_top = 0.0
-anchor_right = 0.0
-anchor_bottom = 0.0
-margin_left = 8.0
-margin_top = 87.0
-margin_right = 411.0
-margin_bottom = 208.0
-rect_pivot_offset = Vector2( 0, 0 )
-rect_clip_content = true
-focus_mode = 2
-mouse_filter = 0
-mouse_default_cursor_shape = 0
-size_flags_horizontal = 1
-size_flags_vertical = 1
-items = [ "No Games Running", null, false ]
-select_mode = 0
-icon_mode = 1
-fixed_icon_size = Vector2( 0, 0 )
+[node name="Hero" parent="HeroSelect" index="0"]
 
-[node name="Join" type="Button" parent="GameBrowser" index="3"]
+margin_left = -2.0
+margin_top = 41.0
+margin_right = 370.0
+margin_bottom = 108.0
 
-anchor_left = 0.0
-anchor_top = 0.0
-anchor_right = 0.0
-anchor_bottom = 0.0
-margin_left = 9.0
-margin_top = 217.0
-margin_right = 163.0
-margin_bottom = 244.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 Selected Game"
-flat = false
-align = 1
+[node name="Confirm" parent="HeroSelect" index="1"]
 
-[node name="VSeparator" type="VSeparator" parent="." index="2"]
-
-anchor_left = 0.0
-anchor_top = 0.0
-anchor_right = 0.0
-anchor_bottom = 0.0
-margin_left = 431.0
-margin_top = -3.0
-margin_right = 436.0
-margin_bottom = 234.0
-rect_pivot_offset = Vector2( 0, 0 )
-mouse_filter = 0
-mouse_default_cursor_shape = 0
-size_flags_horizontal = 1
-size_flags_vertical = 1
+visible = false
 
-[node name="PlayerSettings" type="Control" parent="." index="3"]
+[node name="HeroDescription" parent="HeroSelect" index="2"]
 
-editor/display_folded = true
-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
+margin_left = -2.0
+margin_top = 133.0
+margin_right = 366.0
+margin_bottom = 250.0
+text = "L Let s put oeuntahoeu nthaoeu ntaoheunt aoheunt hoaenth oaentuh Let s put oeuntahoeu nthaoeu ntaoheunt aoheunt hoaenth "
 
-[node name="PlayerSettingsLabel" type="Label" parent="PlayerSettings" index="0"]
+[node name="Title" type="Label" parent="HeroSelect" index="3"]
 
 anchor_left = 0.0
 anchor_top = 0.0
 anchor_right = 0.0
 anchor_bottom = 0.0
-margin_left = 459.0
-margin_top = -6.0
-margin_right = 564.0
-margin_bottom = 13.0
+margin_left = 6.0
+margin_top = 9.0
+margin_right = 294.0
+margin_bottom = 45.0
 rect_pivot_offset = Vector2( 0, 0 )
 mouse_filter = 2
 mouse_default_cursor_shape = 0
 size_flags_horizontal = 1
 size_flags_vertical = 4
-text = "Player Settings"
+custom_fonts/font = SubResource( 5 )
+text = "Select your hero"
 percent_visible = 1.0
 lines_skipped = 0
 max_lines_visible = -1
 
-[node name="Username" type="TextEdit" parent="PlayerSettings" index="1"]
+[node name="Username" type="TextEdit" parent="." index="1"]
 
 anchor_left = 0.0
 anchor_top = 0.0
 anchor_right = 0.0
 anchor_bottom = 0.0
-margin_left = 457.0
-margin_top = 27.0
-margin_right = 636.0
-margin_bottom = 47.0
+margin_left = 42.0
+margin_top = 423.0
+margin_right = 383.0
+margin_bottom = 456.0
 rect_pivot_offset = Vector2( 0, 0 )
 focus_mode = 2
 mouse_filter = 0
@@ -252,330 +121,92 @@ caret_blink = false
 caret_blink_speed = 0.65
 caret_moving_by_right_click = true
 
-[node name="HeroSelectLabel" type="Label" parent="PlayerSettings" index="2"]
-
-anchor_left = 0.0
-anchor_top = 0.0
-anchor_right = 0.0
-anchor_bottom = 0.0
-margin_left = 460.0
-margin_top = 66.0
-margin_right = 538.0
-margin_bottom = 80.0
-rect_pivot_offset = Vector2( 0, 0 )
-mouse_filter = 2
-mouse_default_cursor_shape = 0
-size_flags_horizontal = 1
-size_flags_vertical = 4
-text = "Hero Select:"
-percent_visible = 1.0
-lines_skipped = 0
-max_lines_visible = -1
-
-[node name="HeroSelect" type="OptionButton" parent="PlayerSettings" index="3"]
-
-anchor_left = 0.0
-anchor_top = 0.0
-anchor_right = 0.0
-anchor_bottom = 0.0
-margin_left = 457.0
-margin_top = 94.0
-margin_right = 645.0
-margin_bottom = 114.0
-rect_rotation = -0.0115615
-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
-action_mode = 0
-enabled_focus_mode = 2
-shortcut = null
-group = null
-flat = false
-align = 0
-selected = -1
-items = [  ]
-script = ExtResource( 2 )
-
-[node name="TeamLabel" type="Label" parent="PlayerSettings" index="4"]
-
-anchor_left = 0.0
-anchor_top = 0.0
-anchor_right = 0.0
-anchor_bottom = 0.0
-margin_left = 459.0
-margin_top = 132.0
-margin_right = 558.0
-margin_bottom = 146.0
-rect_pivot_offset = Vector2( 0, 0 )
-mouse_filter = 2
-mouse_default_cursor_shape = 0
-size_flags_horizontal = 1
-size_flags_vertical = 4
-text = "Assigned team:"
-percent_visible = 1.0
-lines_skipped = 0
-max_lines_visible = -1
-
-[node name="Team" type="Label" parent="PlayerSettings" index="5"]
-
-anchor_left = 0.0
-anchor_top = 0.0
-anchor_right = 0.0
-anchor_bottom = 0.0
-margin_left = 460.0
-margin_top = 157.0
-margin_right = 500.0
-margin_bottom = 171.0
-rect_pivot_offset = Vector2( 0, 0 )
-mouse_filter = 2
-mouse_default_cursor_shape = 0
-size_flags_horizontal = 1
-size_flags_vertical = 4
-percent_visible = 1.0
-lines_skipped = 0
-max_lines_visible = -1
-
-[node name="HeroDescriptionLabel" type="Label" parent="PlayerSettings" index="6"]
+[node name="TeamLabel" type="Label" parent="." index="2"]
 
 anchor_left = 0.0
 anchor_top = 0.0
 anchor_right = 0.0
 anchor_bottom = 0.0
-margin_left = 677.0
-margin_top = -5.0
-margin_right = 790.0
-margin_bottom = 9.0
+margin_left = 498.0
+margin_top = 87.0
+margin_right = 666.0
+margin_bottom = 111.0
 rect_pivot_offset = Vector2( 0, 0 )
 mouse_filter = 2
 mouse_default_cursor_shape = 0
 size_flags_horizontal = 1
 size_flags_vertical = 4
-text = "Hero Description:"
+text = "Your team:"
 percent_visible = 1.0
 lines_skipped = 0
 max_lines_visible = -1
 
-[node name="HeroDescription" type="RichTextLabel" parent="PlayerSettings" index="7"]
-
-anchor_left = 0.0
-anchor_top = 0.0
-anchor_right = 0.0
-anchor_bottom = 0.0
-margin_left = 675.0
-margin_top = 15.0
-margin_right = 918.0
-margin_bottom = 245.0
-rect_pivot_offset = Vector2( 0, 0 )
-rect_clip_content = true
-mouse_filter = 0
-mouse_default_cursor_shape = 0
-size_flags_horizontal = 1
-size_flags_vertical = 1
-bbcode_enabled = false
-bbcode_text = ""
-visible_characters = -1
-percent_visible = 1.0
-meta_underlined = true
-tab_size = 4
-text = ""
-scroll_active = true
-scroll_following = false
-selection_enabled = false
-override_selected_font_color = false
-
-[node name="HSeparator3" type="HSeparator" parent="." index="4"]
+[node name="Team" type="Label" parent="." index="3"]
 
 anchor_left = 0.0
 anchor_top = 0.0
 anchor_right = 0.0
 anchor_bottom = 0.0
-margin_left = 19.0
-margin_top = 271.0
-margin_right = 645.0
-margin_bottom = 275.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="CustomGame" type="Control" parent="." index="5"]
-
-editor/display_folded = true
-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
-
-[node name="Label" type="Label" parent="CustomGame" index="0"]
-
-anchor_left = 0.0
-anchor_top = 0.0
-anchor_right = 0.0
-anchor_bottom = 0.0
-margin_left = 671.0
-margin_top = 275.0
-margin_right = 874.0
-margin_bottom = 289.0
+margin_left = 625.0
+margin_top = 87.0
+margin_right = 665.0
+margin_bottom = 111.0
 rect_pivot_offset = Vector2( 0, 0 )
 mouse_filter = 2
 mouse_default_cursor_shape = 0
 size_flags_horizontal = 1
 size_flags_vertical = 4
-text = "Or, host or join a custom game:"
 percent_visible = 1.0
 lines_skipped = 0
 max_lines_visible = -1
 
-[node name="Server" type="Button" parent="CustomGame" index="1"]
+[node name="Players" type="Control" parent="." index="4"]
 
 anchor_left = 0.0
 anchor_top = 0.0
 anchor_right = 0.0
 anchor_bottom = 0.0
-margin_left = 665.0
-margin_top = 300.0
-margin_right = 752.0
-margin_bottom = 335.0
+margin_left = 494.0
+margin_top = 128.0
+margin_right = 534.0
+margin_bottom = 168.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 = "Server"
-flat = false
-align = 1
 
-[node name="LevelSelect" type="OptionButton" parent="CustomGame" index="2"]
+[node name="PlayerList" type="Label" parent="Players" index="0"]
 
-anchor_left = 0.0
-anchor_top = 0.0
-anchor_right = 0.0
-anchor_bottom = 0.0
-margin_left = 770.0
-margin_top = 300.0
-margin_right = 895.0
-margin_bottom = 335.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
-action_mode = 0
-enabled_focus_mode = 2
-shortcut = null
-group = null
-text = "Platform map"
-flat = false
-align = 0
-selected = 0
-items = [ "Platform map", null, false, 0, null, "City-like thing", null, false, 1, null, "Slide", null, false, 2, null ]
-
-[node name="Client" type="Button" parent="CustomGame" index="3"]
-
-anchor_left = 0.0
-anchor_top = 0.0
-anchor_right = 0.0
-anchor_bottom = 0.0
-margin_left = 667.0
-margin_top = 354.0
-margin_right = 753.0
-margin_bottom = 393.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 = "Client"
-flat = false
-align = 1
-
-[node name="IPLabel" type="Label" parent="CustomGame" index="4"]
-
-anchor_left = 0.0
-anchor_top = 0.0
-anchor_right = 0.0
-anchor_bottom = 0.0
-margin_left = 673.0
-margin_top = 403.0
-margin_right = 698.0
-margin_bottom = 419.0
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+margin_left = -14.0
+margin_top = 2.0
+margin_right = 487.0
+margin_bottom = 280.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:"
+custom_fonts/font = SubResource( 4 )
+text = "Waiting for players to connect...."
 percent_visible = 1.0
 lines_skipped = 0
 max_lines_visible = -1
 
-[node name="IP" type="TextEdit" parent="CustomGame" index="5"]
+[node name="StartGame" type="Button" parent="Players" index="1"]
 
 anchor_left = 0.0
 anchor_top = 0.0
 anchor_right = 0.0
 anchor_bottom = 0.0
-margin_left = 707.0
-margin_top = 401.0
-margin_right = 851.0
-margin_bottom = 419.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="Singleplayer" type="Button" parent="CustomGame" index="6"]
-
-anchor_left = 0.0
-anchor_top = 0.0
-anchor_right = 0.0
-anchor_bottom = 0.0
-margin_left = 668.0
-margin_top = 431.0
-margin_right = 760.0
-margin_bottom = 470.0
+margin_left = 2.0
+margin_top = 325.0
+margin_right = 124.0
+margin_bottom = 365.0
 rect_pivot_offset = Vector2( 0, 0 )
 focus_mode = 2
 mouse_filter = 0
@@ -586,105 +217,25 @@ toggle_mode = false
 enabled_focus_mode = 2
 shortcut = null
 group = null
-text = "Singleplayer"
+text = "Ready!"
 flat = false
 align = 1
 
-[node name="VSeparator2" type="VSeparator" parent="." index="6"]
-
-anchor_left = 0.0
-anchor_top = 0.0
-anchor_right = 0.0
-anchor_bottom = 0.0
-margin_left = 653.0
-margin_top = 273.0
-margin_right = 657.0
-margin_bottom = 467.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="JoinedGameLobby" type="Control" parent="." index="7"]
-
-editor/display_folded = true
-visible = false
-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
-
-[node name="PlayerListLabel" type="Label" parent="JoinedGameLobby" index="0"]
-
-anchor_left = 0.0
-anchor_top = 0.0
-anchor_right = 0.0
-anchor_bottom = 0.0
-margin_left = 14.0
-margin_top = 287.0
-margin_right = 136.0
-margin_bottom = 301.0
-rect_pivot_offset = Vector2( 0, 0 )
-mouse_filter = 2
-mouse_default_cursor_shape = 0
-size_flags_horizontal = 1
-size_flags_vertical = 4
-text = "Connected Players:"
-percent_visible = 1.0
-lines_skipped = 0
-max_lines_visible = -1
-
-[node name="PlayerList" type="Label" parent="JoinedGameLobby" index="1"]
-
-anchor_left = 0.5
-anchor_top = 0.5
-anchor_right = 0.5
-anchor_bottom = 0.5
-margin_left = -7.0
-margin_top = 287.0
-margin_right = 639.0
-margin_bottom = 433.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( 4 )
-text = "Waiting for players to connect...."
-percent_visible = 1.0
-lines_skipped = 0
-max_lines_visible = -1
-
-[node name="StartGame" type="Button" parent="JoinedGameLobby" index="2"]
+[node name="VSeparator" type="VSeparator" parent="." index="5"]
 
 anchor_left = 0.0
 anchor_top = 0.0
 anchor_right = 0.0
 anchor_bottom = 0.0
-margin_left = 13.0
-margin_top = 443.0
-margin_right = 135.0
-margin_bottom = 483.0
+margin_left = 453.0
+margin_top = 50.0
+margin_right = 471.0
+margin_bottom = 538.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 = "Start game"
-flat = false
-align = 1
 
 
+[editable path="HeroSelect"]
diff --git a/scenes/menu.tscn b/scenes/menu.tscn
new file mode 100644
index 0000000..fb0d0f4
--- /dev/null
+++ b/scenes/menu.tscn
@@ -0,0 +1,142 @@
+[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 = "Singleplayer"
+flat = false
+align = 1
+
+
diff --git a/scripts/custom_game.gd b/scripts/custom_game.gd
new file mode 100644
index 0000000..6cda66e
--- /dev/null
+++ b/scripts/custom_game.gd
@@ -0,0 +1,9 @@
+extends Control
+
+onready var networking = preload("res://scripts/networking.gd").new()
+
+func _ready():
+	add_child(networking)
+
+	get_node("Server").connect("pressed", networking, server_init)
+	get_node("Client").connect("pressed", networking, client_init)
diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index e39ac1a..809c945 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -1,8 +1,5 @@
-extends "res://scripts/args.gd"
+extends Control
 
-# class member variables go here, for example:
-# var a = 2
-# var b = "textvar"
 var port = null # Defined by command-line argument with default
 
 var player_info = {}
@@ -18,40 +15,10 @@ onready var matchmaking = preload("res://scripts/matchmaking.gd").new()
 
 var matchmaker_tcp
 
-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('-matchmaker', false, 'Whether to be the sole matchmaker')
-	opts.add('-client', false, 'Immediately connect as client')
-	opts.add('-silent', false, 'If the server is not playing, merely serving')
-	opts.add('-port', 54672, 'The port to run a server on or connect to')
-	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)
 
 func _ready():
-
 	add_child(matchmaking)
 
-	my_info.version = [0,0,0] # Semantic versioning: [0].[1].[2]
-
-	randomize()
-	parse_args()
-
 	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")
@@ -65,104 +32,6 @@ func _ready():
 	get_tree().connect("network_peer_disconnected", self, "_player_disconnected")
 	get_tree().connect("connected_to_server", self, "_connected_ok")
 
-func parse_args():
-	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("-matchmaker"):
-		call_deferred("_matchmaker_init")
-	if o.get_value("-client"):
-		call_deferred("_client_init")
-	if o.get_value("-port"):
-		port = o.get_value("-port")
-	if o.get_value("-start-game"):
-		my_info.start_game = true
-	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()
-
-func connect_global_server():
-	ip = global_server_ip
-	_client_init()
-
-slave func _client_init(given_port=null):
-	collect_info()
-	var peer = NetworkedMultiplayerENet.new()
-	if not ip:
-		ip = get_node("CustomGame/IP").get_text()
-	ip = IP.resolve_hostname(ip)
-	if given_port:
-		port = given_port
-	print("Connecting to " + ip + ":" + str(port))
-	peer.create_client(ip, 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(port, 1)
-	get_tree().set_network_peer(peer)
-	player_info[1] = my_info
-	start_game()
-
-func _server_init():
-	collect_info()
-	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
-	var matchmaker_peer = StreamPeerTCP.new()
-	matchmaker_peer.connect_to_host("127.0.0.1", matchmaking.SERVER_TO_SERVER_PORT)
-	matchmaker_tcp = PacketPeerStream.new()
-	matchmaker_tcp.set_stream_peer(matchmaker_peer)
-	# matchmaker_tcp.put_packet([matchmaking.messages.ready_to_connect, port])
-	matchmaker_tcp.put_var(matchmaking.messages.ready_to_connect)
-	matchmaker_tcp.put_var(port)
-	is_connected = true
-	get_node("CustomGame/Server").set_text("Serving!")
-	get_node("JoinedGameLobby").show()
-	if server_playing:
-		player_info[1] = my_info
-	if "start_game" in my_info and my_info.start_game:
-		start_game()
-
-func _matchmaker_init():
-	matchmaking.run_matchmaker()
-
-func _player_connected(id):
-	pass
-
-func _player_disconnected(id):
-	if get_tree().is_network_server():
-		rpc("unregister_player", id)
-	call_deferred("render_player_list")
-
-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 collect_info():
 	if not "username" in my_info:
 		my_info.username = get_node("PlayerSettings/Username").get_text()
@@ -171,43 +40,6 @@ func collect_info():
 	if not "is_right_team" in my_info:
 		my_info.is_right_team = false # Server assigns team, wait for that
 
-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() == matchmaking.GAME_SIZE:
-			start_game()
-		if begun:
-			rpc_id(new_peer, "pre_configure_game", my_info.level)
-			rpc_id(new_peer, "post_configure_game")
-
-sync func unregister_player(peer):
-	player_info.erase(peer)
-	get_node("/root/Level/Players/%d" % peer).queue_free()
-
 func select_hero(hero):
 	var description = get_node("PlayerSettings/HeroSelect").hero_text[hero]
 	get_node("PlayerSettings/HeroDescription").set_text(description)
@@ -227,15 +59,6 @@ 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 = ""
@@ -250,57 +73,3 @@ func render_player_list():
 			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()
-
-sync func post_configure_game():
-	# Begin all players (including self)
-	for p in player_info:
-		begin_player_deferred(p)
-
diff --git a/scripts/matchmaking.gd b/scripts/matchmaking.gd
index d5cd778..8e03394 100644
--- a/scripts/matchmaking.gd
+++ b/scripts/matchmaking.gd
@@ -83,7 +83,6 @@ func add_to_game(netid, port):
 	lobby.rpc_id(netid, "_client_init", port)
 
 func skirmish_to_game(port, count=1):
-	print("dropping 'em in")
 	for i in range(count):
 		if not skirmishing_players.size():
 			return false
diff --git a/scripts/menu.gd b/scripts/menu.gd
new file mode 100644
index 0000000..e7dc6e9
--- /dev/null
+++ b/scripts/menu.gd
@@ -0,0 +1,83 @@
+extends "res://scripts/args.gd"
+
+func _ready():
+	randomize()
+	_parse_args()
+	_gui_setup()
+
+# 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")
+
+func _find_game():
+	print("still refactoring matchmaker")
+
+func _custom_game():
+	get_tree().change_scene("res://scenes/custom_game.tscn")
+
+func _singleplayer():
+	print("still refactoring singleplayer")
+
+# Command line
+
+func _set_up_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('-matchmaker', false, 'Whether to be the sole matchmaker')
+	opts.add('-client', false, 'Immediately connect as client')
+	opts.add('-silent', false, 'If the server is not playing, merely serving')
+	opts.add('-port', 54672, 'The port to run a server on or connect to')
+	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)
+
+func _parse_args():
+	var o = setup_options()
+	o.parse()
+
+	# if o.get_value("-silent"):
+	# 	server_playing = false
+	# 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("-matchmaker"):
+	# 	call_deferred("_matchmaker_init")
+	# if o.get_value("-client"):
+	# 	call_deferred("_client_init")
+	# if o.get_value("-port"):
+	# 	port = o.get_value("-port")
+	# if o.get_value("-start-game"):
+	# 	my_info.start_game = true
+	# 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()
+
diff --git a/scripts/networking.gd b/scripts/networking.gd
new file mode 100644
index 0000000..5011cc7
--- /dev/null
+++ b/scripts/networking.gd
@@ -0,0 +1,164 @@
+extends Node
+
+func connect_global_server():
+	ip = global_server_ip
+	_client_init()
+
+slave func client_init(given_port=null):
+	collect_info()
+	var peer = NetworkedMultiplayerENet.new()
+	if not ip:
+		ip = get_node("CustomGame/IP").get_text()
+	ip = IP.resolve_hostname(ip)
+	if given_port:
+		port = given_port
+	print("Connecting to " + ip + ":" + str(port))
+	peer.create_client(ip, 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(port, 1)
+	get_tree().set_network_peer(peer)
+	player_info[1] = my_info
+	start_game()
+
+func server_init():
+	collect_info()
+	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
+	var matchmaker_peer = StreamPeerTCP.new()
+	matchmaker_peer.connect_to_host("127.0.0.1", matchmaking.SERVER_TO_SERVER_PORT)
+	matchmaker_tcp = PacketPeerStream.new()
+	matchmaker_tcp.set_stream_peer(matchmaker_peer)
+	# matchmaker_tcp.put_packet([matchmaking.messages.ready_to_connect, port])
+	matchmaker_tcp.put_var(matchmaking.messages.ready_to_connect)
+	matchmaker_tcp.put_var(port)
+	is_connected = true
+	get_node("CustomGame/Server").set_text("Serving!")
+	get_node("JoinedGameLobby").show()
+	if server_playing:
+		player_info[1] = my_info
+	if "start_game" in my_info and my_info.start_game:
+		start_game()
+
+func matchmaker_init():
+	matchmaking.run_matchmaker()
+
+func player_disconnected(id):
+	if get_tree().is_network_server():
+		rpc("unregister_player", id)
+	call_deferred("render_player_list")
+
+func player_connected():
+	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
+
+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() == matchmaking.GAME_SIZE:
+			start_game()
+		if begun:
+			rpc_id(new_peer, "pre_configure_game", my_info.level)
+			rpc_id(new_peer, "post_configure_game")
+
+sync func unregister_player(peer):
+	player_info.erase(peer)
+	get_node("/root/Level/Players/%d" % peer).queue_free()
+
+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()
+
+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()
+
+sync func post_configure_game():
+	# Begin all players (including self)
+	for p in player_info:
+		begin_player_deferred(p)
+
diff --git a/scripts/util.gd b/scripts/util.gd
index 3f49fa8..3e29d0c 100644
--- a/scripts/util.gd
+++ b/scripts/util.gd
@@ -1,5 +1,7 @@
 extends Node
 
+var version = [0,0,0] # Semantic versioning: [0].[1].[2]
+
 func get_master_player():
 	var path = "/root/Level/Players/%d" % get_tree().get_network_unique_id()
 	if has_node(path):