From cdf74110f22a7cc0a62815537883075980931c81 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sat, 28 Apr 2018 15:46:49 -0400
Subject: [PATCH 01/40] Add .import files needed for running from source

---
 .gitignore                        |    1 -
 assets/heroes/0.obj.import        |   19 +
 assets/heroes/0_head.obj.import   |   19 +
 assets/heroes/1.obj.import        |   19 +
 assets/heroes/1_head.obj.import   |   19 +
 assets/heroes/2.obj.import        |   19 +
 assets/heroes/2_head.obj.import   |   19 +
 assets/heroes/3.obj.import        |   19 +
 assets/heroes/3_head.obj.import   |   19 +
 assets/heroes/4.obj.import        |   19 +
 assets/heroes/4_beam.obj.import   |   19 +
 assets/heroes/4_head.obj.import   |   19 +
 assets/heroes/5.obj.import        |   19 +
 assets/heroes/5_head.obj.import   |   19 +
 assets/heroes/5_portal.obj.import |   19 +
 assets/levels/0.obj.import        |   19 +
 assets/levels/1.obj.import        |   19 +
 assets/levels/2.dae.import        | 1065 +++++++++++++++++++++++++++++++++++++
 assets/levels/2.obj.import        |   19 +
 assets/maze-high-obj.obj.import   |   19 +
 assets/maze.obj.import            |   19 +
 assets/objective-left.png.import  |   32 ++
 assets/objective-right.png.import |   32 ++
 assets/objective.obj.import       |   19 +
 assets/player.obj.import          |   19 +
 docs/2018-02-14-heroes.png.import |   32 ++
 icon.png.import                   |   32 ++
 27 files changed, 1592 insertions(+), 1 deletion(-)
 create mode 100644 assets/heroes/0.obj.import
 create mode 100644 assets/heroes/0_head.obj.import
 create mode 100644 assets/heroes/1.obj.import
 create mode 100644 assets/heroes/1_head.obj.import
 create mode 100644 assets/heroes/2.obj.import
 create mode 100644 assets/heroes/2_head.obj.import
 create mode 100644 assets/heroes/3.obj.import
 create mode 100644 assets/heroes/3_head.obj.import
 create mode 100644 assets/heroes/4.obj.import
 create mode 100644 assets/heroes/4_beam.obj.import
 create mode 100644 assets/heroes/4_head.obj.import
 create mode 100644 assets/heroes/5.obj.import
 create mode 100644 assets/heroes/5_head.obj.import
 create mode 100644 assets/heroes/5_portal.obj.import
 create mode 100644 assets/levels/0.obj.import
 create mode 100644 assets/levels/1.obj.import
 create mode 100644 assets/levels/2.dae.import
 create mode 100644 assets/levels/2.obj.import
 create mode 100644 assets/maze-high-obj.obj.import
 create mode 100644 assets/maze.obj.import
 create mode 100644 assets/objective-left.png.import
 create mode 100644 assets/objective-right.png.import
 create mode 100644 assets/objective.obj.import
 create mode 100644 assets/player.obj.import
 create mode 100644 docs/2018-02-14-heroes.png.import
 create mode 100644 icon.png.import

diff --git a/.gitignore b/.gitignore
index a522fb4..0d731b2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,7 +3,6 @@
 *.swo
 .fscache
 .import
-*.import
 *.blend1
 nohup.out
 recordings
diff --git a/assets/heroes/0.obj.import b/assets/heroes/0.obj.import
new file mode 100644
index 0000000..db296f2
--- /dev/null
+++ b/assets/heroes/0.obj.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="wavefront_obj"
+type="Mesh"
+path="res://.import/0.obj-5ff3661b08325671ce64079449bc78dd.mesh"
+
+[deps]
+
+files=[ "res://.import/0.obj-5ff3661b08325671ce64079449bc78dd.mesh" ]
+
+source_file="res://assets/heroes/0.obj"
+source_md5="214f8b376199d3c947df1d85a0928dec"
+
+dest_files=[ "res://.import/0.obj-5ff3661b08325671ce64079449bc78dd.mesh", "res://.import/0.obj-5ff3661b08325671ce64079449bc78dd.mesh" ]
+dest_md5="4306afcb3a4b4978cce778634c97e7d0"
+
+[params]
+
+generate_tangents=true
diff --git a/assets/heroes/0_head.obj.import b/assets/heroes/0_head.obj.import
new file mode 100644
index 0000000..7840441
--- /dev/null
+++ b/assets/heroes/0_head.obj.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="wavefront_obj"
+type="Mesh"
+path="res://.import/0_head.obj-0b002be4b283361f171e2d4dd9a85105.mesh"
+
+[deps]
+
+files=[ "res://.import/0_head.obj-0b002be4b283361f171e2d4dd9a85105.mesh" ]
+
+source_file="res://assets/heroes/0_head.obj"
+source_md5="4ae67462b018895ed3f258a0db4239e4"
+
+dest_files=[ "res://.import/0_head.obj-0b002be4b283361f171e2d4dd9a85105.mesh", "res://.import/0_head.obj-0b002be4b283361f171e2d4dd9a85105.mesh" ]
+dest_md5="7094164b68aeac1c4dd9bd292282e4b9"
+
+[params]
+
+generate_tangents=true
diff --git a/assets/heroes/1.obj.import b/assets/heroes/1.obj.import
new file mode 100644
index 0000000..dbf44af
--- /dev/null
+++ b/assets/heroes/1.obj.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="wavefront_obj"
+type="Mesh"
+path="res://.import/1.obj-3a9ae1e4a3d92228bd05741755fdbcdd.mesh"
+
+[deps]
+
+files=[ "res://.import/1.obj-3a9ae1e4a3d92228bd05741755fdbcdd.mesh" ]
+
+source_file="res://assets/heroes/1.obj"
+source_md5="6a83c78e54b84bd01eb44630c48362f2"
+
+dest_files=[ "res://.import/1.obj-3a9ae1e4a3d92228bd05741755fdbcdd.mesh", "res://.import/1.obj-3a9ae1e4a3d92228bd05741755fdbcdd.mesh" ]
+dest_md5="808804a04eb10327391dd96c30f14435"
+
+[params]
+
+generate_tangents=true
diff --git a/assets/heroes/1_head.obj.import b/assets/heroes/1_head.obj.import
new file mode 100644
index 0000000..60bc839
--- /dev/null
+++ b/assets/heroes/1_head.obj.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="wavefront_obj"
+type="Mesh"
+path="res://.import/1_head.obj-661a3a390ef1299857e3de1aba5a1d01.mesh"
+
+[deps]
+
+files=[ "res://.import/1_head.obj-661a3a390ef1299857e3de1aba5a1d01.mesh" ]
+
+source_file="res://assets/heroes/1_head.obj"
+source_md5="78a4e4475d699dbd061ec9a916cbd606"
+
+dest_files=[ "res://.import/1_head.obj-661a3a390ef1299857e3de1aba5a1d01.mesh", "res://.import/1_head.obj-661a3a390ef1299857e3de1aba5a1d01.mesh" ]
+dest_md5="65c8304300dd9929d0b3b008623845a0"
+
+[params]
+
+generate_tangents=true
diff --git a/assets/heroes/2.obj.import b/assets/heroes/2.obj.import
new file mode 100644
index 0000000..b9e3bec
--- /dev/null
+++ b/assets/heroes/2.obj.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="wavefront_obj"
+type="Mesh"
+path="res://.import/2.obj-85c8b4ac312b9585e4dda4f545679e80.mesh"
+
+[deps]
+
+files=[ "res://.import/2.obj-85c8b4ac312b9585e4dda4f545679e80.mesh" ]
+
+source_file="res://assets/heroes/2.obj"
+source_md5="7b2a64047a6477edd16b10f1c8601e15"
+
+dest_files=[ "res://.import/2.obj-85c8b4ac312b9585e4dda4f545679e80.mesh", "res://.import/2.obj-85c8b4ac312b9585e4dda4f545679e80.mesh" ]
+dest_md5="cf97b53977d069c0f48e104093031357"
+
+[params]
+
+generate_tangents=true
diff --git a/assets/heroes/2_head.obj.import b/assets/heroes/2_head.obj.import
new file mode 100644
index 0000000..78e75e3
--- /dev/null
+++ b/assets/heroes/2_head.obj.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="wavefront_obj"
+type="Mesh"
+path="res://.import/2_head.obj-642d7e5721ba3621d4aeef33bf9079f3.mesh"
+
+[deps]
+
+files=[ "res://.import/2_head.obj-642d7e5721ba3621d4aeef33bf9079f3.mesh" ]
+
+source_file="res://assets/heroes/2_head.obj"
+source_md5="3800b7ca51634172c29e2627d0688739"
+
+dest_files=[ "res://.import/2_head.obj-642d7e5721ba3621d4aeef33bf9079f3.mesh", "res://.import/2_head.obj-642d7e5721ba3621d4aeef33bf9079f3.mesh" ]
+dest_md5="f41bf13cd6278973e335edcb5af07800"
+
+[params]
+
+generate_tangents=true
diff --git a/assets/heroes/3.obj.import b/assets/heroes/3.obj.import
new file mode 100644
index 0000000..2a8a081
--- /dev/null
+++ b/assets/heroes/3.obj.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="wavefront_obj"
+type="Mesh"
+path="res://.import/3.obj-a2b36e0242832fc3ee6cf8133bec1df6.mesh"
+
+[deps]
+
+files=[ "res://.import/3.obj-a2b36e0242832fc3ee6cf8133bec1df6.mesh" ]
+
+source_file="res://assets/heroes/3.obj"
+source_md5="ae31181eed13485b325d439fd465e246"
+
+dest_files=[ "res://.import/3.obj-a2b36e0242832fc3ee6cf8133bec1df6.mesh", "res://.import/3.obj-a2b36e0242832fc3ee6cf8133bec1df6.mesh" ]
+dest_md5="3afa20d19f1fa09fcf2fee691c245317"
+
+[params]
+
+generate_tangents=true
diff --git a/assets/heroes/3_head.obj.import b/assets/heroes/3_head.obj.import
new file mode 100644
index 0000000..a2f1225
--- /dev/null
+++ b/assets/heroes/3_head.obj.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="wavefront_obj"
+type="Mesh"
+path="res://.import/3_head.obj-31149354fd38179ee6897017177772af.mesh"
+
+[deps]
+
+files=[ "res://.import/3_head.obj-31149354fd38179ee6897017177772af.mesh" ]
+
+source_file="res://assets/heroes/3_head.obj"
+source_md5="4f6b31cfbe16a55cce978896cf8b0b12"
+
+dest_files=[ "res://.import/3_head.obj-31149354fd38179ee6897017177772af.mesh", "res://.import/3_head.obj-31149354fd38179ee6897017177772af.mesh" ]
+dest_md5="8ba28a8f98e213a2382b508bb344084c"
+
+[params]
+
+generate_tangents=true
diff --git a/assets/heroes/4.obj.import b/assets/heroes/4.obj.import
new file mode 100644
index 0000000..8be5592
--- /dev/null
+++ b/assets/heroes/4.obj.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="wavefront_obj"
+type="Mesh"
+path="res://.import/4.obj-773905e6e7c2c08ad34fe36bfaacca4e.mesh"
+
+[deps]
+
+files=[ "res://.import/4.obj-773905e6e7c2c08ad34fe36bfaacca4e.mesh" ]
+
+source_file="res://assets/heroes/4.obj"
+source_md5="8a2ea58bae6a5ba0b5a28b7ab6163ed9"
+
+dest_files=[ "res://.import/4.obj-773905e6e7c2c08ad34fe36bfaacca4e.mesh", "res://.import/4.obj-773905e6e7c2c08ad34fe36bfaacca4e.mesh" ]
+dest_md5="93358c6c816510bd069825e089cee878"
+
+[params]
+
+generate_tangents=true
diff --git a/assets/heroes/4_beam.obj.import b/assets/heroes/4_beam.obj.import
new file mode 100644
index 0000000..7e5cf97
--- /dev/null
+++ b/assets/heroes/4_beam.obj.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="wavefront_obj"
+type="Mesh"
+path="res://.import/4_beam.obj-00b64391bae3ea00b0d3ef163bfa8860.mesh"
+
+[deps]
+
+files=[ "res://.import/4_beam.obj-00b64391bae3ea00b0d3ef163bfa8860.mesh" ]
+
+source_file="res://assets/heroes/4_beam.obj"
+source_md5="8583eb948f98e108f6848c4fc96a2562"
+
+dest_files=[ "res://.import/4_beam.obj-00b64391bae3ea00b0d3ef163bfa8860.mesh", "res://.import/4_beam.obj-00b64391bae3ea00b0d3ef163bfa8860.mesh" ]
+dest_md5="76cbfe93be8dd02d9519c5284d1e638f"
+
+[params]
+
+generate_tangents=true
diff --git a/assets/heroes/4_head.obj.import b/assets/heroes/4_head.obj.import
new file mode 100644
index 0000000..9af9276
--- /dev/null
+++ b/assets/heroes/4_head.obj.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="wavefront_obj"
+type="Mesh"
+path="res://.import/4_head.obj-0f05fd2c5005558302023bc511be8ae6.mesh"
+
+[deps]
+
+files=[ "res://.import/4_head.obj-0f05fd2c5005558302023bc511be8ae6.mesh" ]
+
+source_file="res://assets/heroes/4_head.obj"
+source_md5="2ed548efe830daac45b9e4fa4b5b1b73"
+
+dest_files=[ "res://.import/4_head.obj-0f05fd2c5005558302023bc511be8ae6.mesh", "res://.import/4_head.obj-0f05fd2c5005558302023bc511be8ae6.mesh" ]
+dest_md5="9a188b6c84c8a296472464a3366650fc"
+
+[params]
+
+generate_tangents=true
diff --git a/assets/heroes/5.obj.import b/assets/heroes/5.obj.import
new file mode 100644
index 0000000..d8ee356
--- /dev/null
+++ b/assets/heroes/5.obj.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="wavefront_obj"
+type="Mesh"
+path="res://.import/5.obj-aae555f5fa1f112fdc160f94735bf9d5.mesh"
+
+[deps]
+
+files=[ "res://.import/5.obj-aae555f5fa1f112fdc160f94735bf9d5.mesh" ]
+
+source_file="res://assets/heroes/5.obj"
+source_md5="5a020906bcd263a5302ebe5074b43c01"
+
+dest_files=[ "res://.import/5.obj-aae555f5fa1f112fdc160f94735bf9d5.mesh", "res://.import/5.obj-aae555f5fa1f112fdc160f94735bf9d5.mesh" ]
+dest_md5="bb5b4b3ca681eb185284560d7f4a8aed"
+
+[params]
+
+generate_tangents=true
diff --git a/assets/heroes/5_head.obj.import b/assets/heroes/5_head.obj.import
new file mode 100644
index 0000000..c14f359
--- /dev/null
+++ b/assets/heroes/5_head.obj.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="wavefront_obj"
+type="Mesh"
+path="res://.import/5_head.obj-99b3a2eaf8ae9e841b6fdcc8d4884ea7.mesh"
+
+[deps]
+
+files=[ "res://.import/5_head.obj-99b3a2eaf8ae9e841b6fdcc8d4884ea7.mesh" ]
+
+source_file="res://assets/heroes/5_head.obj"
+source_md5="453157b7ed836fe44c392ad6dda3ff67"
+
+dest_files=[ "res://.import/5_head.obj-99b3a2eaf8ae9e841b6fdcc8d4884ea7.mesh", "res://.import/5_head.obj-99b3a2eaf8ae9e841b6fdcc8d4884ea7.mesh" ]
+dest_md5="a2bc2ff9d1a92c3be2c8477eceb08768"
+
+[params]
+
+generate_tangents=true
diff --git a/assets/heroes/5_portal.obj.import b/assets/heroes/5_portal.obj.import
new file mode 100644
index 0000000..7ffa2e6
--- /dev/null
+++ b/assets/heroes/5_portal.obj.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="wavefront_obj"
+type="Mesh"
+path="res://.import/5_portal.obj-a337ed5122c40b180597dd9794ecfe6d.mesh"
+
+[deps]
+
+files=[ "res://.import/5_portal.obj-a337ed5122c40b180597dd9794ecfe6d.mesh" ]
+
+source_file="res://assets/heroes/5_portal.obj"
+source_md5="bc944713e3a053455322914a39987891"
+
+dest_files=[ "res://.import/5_portal.obj-a337ed5122c40b180597dd9794ecfe6d.mesh", "res://.import/5_portal.obj-a337ed5122c40b180597dd9794ecfe6d.mesh" ]
+dest_md5="b63a25154e6cdd3e7bcb1a3fe4e0e04a"
+
+[params]
+
+generate_tangents=true
diff --git a/assets/levels/0.obj.import b/assets/levels/0.obj.import
new file mode 100644
index 0000000..034df77
--- /dev/null
+++ b/assets/levels/0.obj.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="wavefront_obj"
+type="Mesh"
+path="res://.import/0.obj-75904c05129c42580cc5d5c2ce0f88f4.mesh"
+
+[deps]
+
+files=[ "res://.import/0.obj-75904c05129c42580cc5d5c2ce0f88f4.mesh" ]
+
+source_file="res://assets/levels/0.obj"
+source_md5="c43e5185ee65f03ac94dc947611d85d5"
+
+dest_files=[ "res://.import/0.obj-75904c05129c42580cc5d5c2ce0f88f4.mesh", "res://.import/0.obj-75904c05129c42580cc5d5c2ce0f88f4.mesh" ]
+dest_md5="23c015582e544d38c3302925e86550ca"
+
+[params]
+
+generate_tangents=true
diff --git a/assets/levels/1.obj.import b/assets/levels/1.obj.import
new file mode 100644
index 0000000..f62774f
--- /dev/null
+++ b/assets/levels/1.obj.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="wavefront_obj"
+type="Mesh"
+path="res://.import/1.obj-8c0967187ed7a9d970c8667286d96ec8.mesh"
+
+[deps]
+
+files=[ "res://.import/1.obj-8c0967187ed7a9d970c8667286d96ec8.mesh" ]
+
+source_file="res://assets/levels/1.obj"
+source_md5="c76ce309215e4ab659d00d70f85051c1"
+
+dest_files=[ "res://.import/1.obj-8c0967187ed7a9d970c8667286d96ec8.mesh", "res://.import/1.obj-8c0967187ed7a9d970c8667286d96ec8.mesh" ]
+dest_md5="772c79044dd30ef6a01e2c1b99e3f78c"
+
+[params]
+
+generate_tangents=true
diff --git a/assets/levels/2.dae.import b/assets/levels/2.dae.import
new file mode 100644
index 0000000..b15d042
--- /dev/null
+++ b/assets/levels/2.dae.import
@@ -0,0 +1,1065 @@
+[remap]
+
+importer="scene"
+type="PackedScene"
+path="res://.import/2.dae-83174786709253a298266e3265ca3c25.scn"
+
+[deps]
+
+source_file="res://assets/levels/2.dae"
+source_md5="84d7de37fe7be63f3b4e14b95f5c32ef"
+
+dest_files=[ "res://.import/2.dae-83174786709253a298266e3265ca3c25.scn" ]
+dest_md5="6bda751ca4d80216f370598be2f292c7"
+
+[params]
+
+nodes/root_type="Spatial"
+nodes/root_name="Scene Root"
+nodes/root_scale=1.0
+nodes/custom_script=""
+nodes/storage=0
+materials/location=1
+materials/storage=1
+materials/keep_on_reimport=true
+meshes/compress=true
+meshes/ensure_tangents=true
+meshes/storage=0
+meshes/light_baking=0
+meshes/lightmap_texel_size=0.1
+external_files/store_in_subdir=false
+animation/import=true
+animation/fps=15
+animation/filter_script=""
+animation/storage=false
+animation/keep_custom_tracks=false
+animation/optimizer/enabled=true
+animation/optimizer/max_linear_error=0.05
+animation/optimizer/max_angular_error=0.01
+animation/optimizer/max_angle=22
+animation/optimizer/remove_unused_tracks=true
+animation/clips/amount=0
+animation/clip_1/name=""
+animation/clip_1/start_frame=0
+animation/clip_1/end_frame=0
+animation/clip_1/loops=false
+animation/clip_2/name=""
+animation/clip_2/start_frame=0
+animation/clip_2/end_frame=0
+animation/clip_2/loops=false
+animation/clip_3/name=""
+animation/clip_3/start_frame=0
+animation/clip_3/end_frame=0
+animation/clip_3/loops=false
+animation/clip_4/name=""
+animation/clip_4/start_frame=0
+animation/clip_4/end_frame=0
+animation/clip_4/loops=false
+animation/clip_5/name=""
+animation/clip_5/start_frame=0
+animation/clip_5/end_frame=0
+animation/clip_5/loops=false
+animation/clip_6/name=""
+animation/clip_6/start_frame=0
+animation/clip_6/end_frame=0
+animation/clip_6/loops=false
+animation/clip_7/name=""
+animation/clip_7/start_frame=0
+animation/clip_7/end_frame=0
+animation/clip_7/loops=false
+animation/clip_8/name=""
+animation/clip_8/start_frame=0
+animation/clip_8/end_frame=0
+animation/clip_8/loops=false
+animation/clip_9/name=""
+animation/clip_9/start_frame=0
+animation/clip_9/end_frame=0
+animation/clip_9/loops=false
+animation/clip_10/name=""
+animation/clip_10/start_frame=0
+animation/clip_10/end_frame=0
+animation/clip_10/loops=false
+animation/clip_11/name=""
+animation/clip_11/start_frame=0
+animation/clip_11/end_frame=0
+animation/clip_11/loops=false
+animation/clip_12/name=""
+animation/clip_12/start_frame=0
+animation/clip_12/end_frame=0
+animation/clip_12/loops=false
+animation/clip_13/name=""
+animation/clip_13/start_frame=0
+animation/clip_13/end_frame=0
+animation/clip_13/loops=false
+animation/clip_14/name=""
+animation/clip_14/start_frame=0
+animation/clip_14/end_frame=0
+animation/clip_14/loops=false
+animation/clip_15/name=""
+animation/clip_15/start_frame=0
+animation/clip_15/end_frame=0
+animation/clip_15/loops=false
+animation/clip_16/name=""
+animation/clip_16/start_frame=0
+animation/clip_16/end_frame=0
+animation/clip_16/loops=false
+animation/clip_17/name=""
+animation/clip_17/start_frame=0
+animation/clip_17/end_frame=0
+animation/clip_17/loops=false
+animation/clip_18/name=""
+animation/clip_18/start_frame=0
+animation/clip_18/end_frame=0
+animation/clip_18/loops=false
+animation/clip_19/name=""
+animation/clip_19/start_frame=0
+animation/clip_19/end_frame=0
+animation/clip_19/loops=false
+animation/clip_20/name=""
+animation/clip_20/start_frame=0
+animation/clip_20/end_frame=0
+animation/clip_20/loops=false
+animation/clip_21/name=""
+animation/clip_21/start_frame=0
+animation/clip_21/end_frame=0
+animation/clip_21/loops=false
+animation/clip_22/name=""
+animation/clip_22/start_frame=0
+animation/clip_22/end_frame=0
+animation/clip_22/loops=false
+animation/clip_23/name=""
+animation/clip_23/start_frame=0
+animation/clip_23/end_frame=0
+animation/clip_23/loops=false
+animation/clip_24/name=""
+animation/clip_24/start_frame=0
+animation/clip_24/end_frame=0
+animation/clip_24/loops=false
+animation/clip_25/name=""
+animation/clip_25/start_frame=0
+animation/clip_25/end_frame=0
+animation/clip_25/loops=false
+animation/clip_26/name=""
+animation/clip_26/start_frame=0
+animation/clip_26/end_frame=0
+animation/clip_26/loops=false
+animation/clip_27/name=""
+animation/clip_27/start_frame=0
+animation/clip_27/end_frame=0
+animation/clip_27/loops=false
+animation/clip_28/name=""
+animation/clip_28/start_frame=0
+animation/clip_28/end_frame=0
+animation/clip_28/loops=false
+animation/clip_29/name=""
+animation/clip_29/start_frame=0
+animation/clip_29/end_frame=0
+animation/clip_29/loops=false
+animation/clip_30/name=""
+animation/clip_30/start_frame=0
+animation/clip_30/end_frame=0
+animation/clip_30/loops=false
+animation/clip_31/name=""
+animation/clip_31/start_frame=0
+animation/clip_31/end_frame=0
+animation/clip_31/loops=false
+animation/clip_32/name=""
+animation/clip_32/start_frame=0
+animation/clip_32/end_frame=0
+animation/clip_32/loops=false
+animation/clip_33/name=""
+animation/clip_33/start_frame=0
+animation/clip_33/end_frame=0
+animation/clip_33/loops=false
+animation/clip_34/name=""
+animation/clip_34/start_frame=0
+animation/clip_34/end_frame=0
+animation/clip_34/loops=false
+animation/clip_35/name=""
+animation/clip_35/start_frame=0
+animation/clip_35/end_frame=0
+animation/clip_35/loops=false
+animation/clip_36/name=""
+animation/clip_36/start_frame=0
+animation/clip_36/end_frame=0
+animation/clip_36/loops=false
+animation/clip_37/name=""
+animation/clip_37/start_frame=0
+animation/clip_37/end_frame=0
+animation/clip_37/loops=false
+animation/clip_38/name=""
+animation/clip_38/start_frame=0
+animation/clip_38/end_frame=0
+animation/clip_38/loops=false
+animation/clip_39/name=""
+animation/clip_39/start_frame=0
+animation/clip_39/end_frame=0
+animation/clip_39/loops=false
+animation/clip_40/name=""
+animation/clip_40/start_frame=0
+animation/clip_40/end_frame=0
+animation/clip_40/loops=false
+animation/clip_41/name=""
+animation/clip_41/start_frame=0
+animation/clip_41/end_frame=0
+animation/clip_41/loops=false
+animation/clip_42/name=""
+animation/clip_42/start_frame=0
+animation/clip_42/end_frame=0
+animation/clip_42/loops=false
+animation/clip_43/name=""
+animation/clip_43/start_frame=0
+animation/clip_43/end_frame=0
+animation/clip_43/loops=false
+animation/clip_44/name=""
+animation/clip_44/start_frame=0
+animation/clip_44/end_frame=0
+animation/clip_44/loops=false
+animation/clip_45/name=""
+animation/clip_45/start_frame=0
+animation/clip_45/end_frame=0
+animation/clip_45/loops=false
+animation/clip_46/name=""
+animation/clip_46/start_frame=0
+animation/clip_46/end_frame=0
+animation/clip_46/loops=false
+animation/clip_47/name=""
+animation/clip_47/start_frame=0
+animation/clip_47/end_frame=0
+animation/clip_47/loops=false
+animation/clip_48/name=""
+animation/clip_48/start_frame=0
+animation/clip_48/end_frame=0
+animation/clip_48/loops=false
+animation/clip_49/name=""
+animation/clip_49/start_frame=0
+animation/clip_49/end_frame=0
+animation/clip_49/loops=false
+animation/clip_50/name=""
+animation/clip_50/start_frame=0
+animation/clip_50/end_frame=0
+animation/clip_50/loops=false
+animation/clip_51/name=""
+animation/clip_51/start_frame=0
+animation/clip_51/end_frame=0
+animation/clip_51/loops=false
+animation/clip_52/name=""
+animation/clip_52/start_frame=0
+animation/clip_52/end_frame=0
+animation/clip_52/loops=false
+animation/clip_53/name=""
+animation/clip_53/start_frame=0
+animation/clip_53/end_frame=0
+animation/clip_53/loops=false
+animation/clip_54/name=""
+animation/clip_54/start_frame=0
+animation/clip_54/end_frame=0
+animation/clip_54/loops=false
+animation/clip_55/name=""
+animation/clip_55/start_frame=0
+animation/clip_55/end_frame=0
+animation/clip_55/loops=false
+animation/clip_56/name=""
+animation/clip_56/start_frame=0
+animation/clip_56/end_frame=0
+animation/clip_56/loops=false
+animation/clip_57/name=""
+animation/clip_57/start_frame=0
+animation/clip_57/end_frame=0
+animation/clip_57/loops=false
+animation/clip_58/name=""
+animation/clip_58/start_frame=0
+animation/clip_58/end_frame=0
+animation/clip_58/loops=false
+animation/clip_59/name=""
+animation/clip_59/start_frame=0
+animation/clip_59/end_frame=0
+animation/clip_59/loops=false
+animation/clip_60/name=""
+animation/clip_60/start_frame=0
+animation/clip_60/end_frame=0
+animation/clip_60/loops=false
+animation/clip_61/name=""
+animation/clip_61/start_frame=0
+animation/clip_61/end_frame=0
+animation/clip_61/loops=false
+animation/clip_62/name=""
+animation/clip_62/start_frame=0
+animation/clip_62/end_frame=0
+animation/clip_62/loops=false
+animation/clip_63/name=""
+animation/clip_63/start_frame=0
+animation/clip_63/end_frame=0
+animation/clip_63/loops=false
+animation/clip_64/name=""
+animation/clip_64/start_frame=0
+animation/clip_64/end_frame=0
+animation/clip_64/loops=false
+animation/clip_65/name=""
+animation/clip_65/start_frame=0
+animation/clip_65/end_frame=0
+animation/clip_65/loops=false
+animation/clip_66/name=""
+animation/clip_66/start_frame=0
+animation/clip_66/end_frame=0
+animation/clip_66/loops=false
+animation/clip_67/name=""
+animation/clip_67/start_frame=0
+animation/clip_67/end_frame=0
+animation/clip_67/loops=false
+animation/clip_68/name=""
+animation/clip_68/start_frame=0
+animation/clip_68/end_frame=0
+animation/clip_68/loops=false
+animation/clip_69/name=""
+animation/clip_69/start_frame=0
+animation/clip_69/end_frame=0
+animation/clip_69/loops=false
+animation/clip_70/name=""
+animation/clip_70/start_frame=0
+animation/clip_70/end_frame=0
+animation/clip_70/loops=false
+animation/clip_71/name=""
+animation/clip_71/start_frame=0
+animation/clip_71/end_frame=0
+animation/clip_71/loops=false
+animation/clip_72/name=""
+animation/clip_72/start_frame=0
+animation/clip_72/end_frame=0
+animation/clip_72/loops=false
+animation/clip_73/name=""
+animation/clip_73/start_frame=0
+animation/clip_73/end_frame=0
+animation/clip_73/loops=false
+animation/clip_74/name=""
+animation/clip_74/start_frame=0
+animation/clip_74/end_frame=0
+animation/clip_74/loops=false
+animation/clip_75/name=""
+animation/clip_75/start_frame=0
+animation/clip_75/end_frame=0
+animation/clip_75/loops=false
+animation/clip_76/name=""
+animation/clip_76/start_frame=0
+animation/clip_76/end_frame=0
+animation/clip_76/loops=false
+animation/clip_77/name=""
+animation/clip_77/start_frame=0
+animation/clip_77/end_frame=0
+animation/clip_77/loops=false
+animation/clip_78/name=""
+animation/clip_78/start_frame=0
+animation/clip_78/end_frame=0
+animation/clip_78/loops=false
+animation/clip_79/name=""
+animation/clip_79/start_frame=0
+animation/clip_79/end_frame=0
+animation/clip_79/loops=false
+animation/clip_80/name=""
+animation/clip_80/start_frame=0
+animation/clip_80/end_frame=0
+animation/clip_80/loops=false
+animation/clip_81/name=""
+animation/clip_81/start_frame=0
+animation/clip_81/end_frame=0
+animation/clip_81/loops=false
+animation/clip_82/name=""
+animation/clip_82/start_frame=0
+animation/clip_82/end_frame=0
+animation/clip_82/loops=false
+animation/clip_83/name=""
+animation/clip_83/start_frame=0
+animation/clip_83/end_frame=0
+animation/clip_83/loops=false
+animation/clip_84/name=""
+animation/clip_84/start_frame=0
+animation/clip_84/end_frame=0
+animation/clip_84/loops=false
+animation/clip_85/name=""
+animation/clip_85/start_frame=0
+animation/clip_85/end_frame=0
+animation/clip_85/loops=false
+animation/clip_86/name=""
+animation/clip_86/start_frame=0
+animation/clip_86/end_frame=0
+animation/clip_86/loops=false
+animation/clip_87/name=""
+animation/clip_87/start_frame=0
+animation/clip_87/end_frame=0
+animation/clip_87/loops=false
+animation/clip_88/name=""
+animation/clip_88/start_frame=0
+animation/clip_88/end_frame=0
+animation/clip_88/loops=false
+animation/clip_89/name=""
+animation/clip_89/start_frame=0
+animation/clip_89/end_frame=0
+animation/clip_89/loops=false
+animation/clip_90/name=""
+animation/clip_90/start_frame=0
+animation/clip_90/end_frame=0
+animation/clip_90/loops=false
+animation/clip_91/name=""
+animation/clip_91/start_frame=0
+animation/clip_91/end_frame=0
+animation/clip_91/loops=false
+animation/clip_92/name=""
+animation/clip_92/start_frame=0
+animation/clip_92/end_frame=0
+animation/clip_92/loops=false
+animation/clip_93/name=""
+animation/clip_93/start_frame=0
+animation/clip_93/end_frame=0
+animation/clip_93/loops=false
+animation/clip_94/name=""
+animation/clip_94/start_frame=0
+animation/clip_94/end_frame=0
+animation/clip_94/loops=false
+animation/clip_95/name=""
+animation/clip_95/start_frame=0
+animation/clip_95/end_frame=0
+animation/clip_95/loops=false
+animation/clip_96/name=""
+animation/clip_96/start_frame=0
+animation/clip_96/end_frame=0
+animation/clip_96/loops=false
+animation/clip_97/name=""
+animation/clip_97/start_frame=0
+animation/clip_97/end_frame=0
+animation/clip_97/loops=false
+animation/clip_98/name=""
+animation/clip_98/start_frame=0
+animation/clip_98/end_frame=0
+animation/clip_98/loops=false
+animation/clip_99/name=""
+animation/clip_99/start_frame=0
+animation/clip_99/end_frame=0
+animation/clip_99/loops=false
+animation/clip_100/name=""
+animation/clip_100/start_frame=0
+animation/clip_100/end_frame=0
+animation/clip_100/loops=false
+animation/clip_101/name=""
+animation/clip_101/start_frame=0
+animation/clip_101/end_frame=0
+animation/clip_101/loops=false
+animation/clip_102/name=""
+animation/clip_102/start_frame=0
+animation/clip_102/end_frame=0
+animation/clip_102/loops=false
+animation/clip_103/name=""
+animation/clip_103/start_frame=0
+animation/clip_103/end_frame=0
+animation/clip_103/loops=false
+animation/clip_104/name=""
+animation/clip_104/start_frame=0
+animation/clip_104/end_frame=0
+animation/clip_104/loops=false
+animation/clip_105/name=""
+animation/clip_105/start_frame=0
+animation/clip_105/end_frame=0
+animation/clip_105/loops=false
+animation/clip_106/name=""
+animation/clip_106/start_frame=0
+animation/clip_106/end_frame=0
+animation/clip_106/loops=false
+animation/clip_107/name=""
+animation/clip_107/start_frame=0
+animation/clip_107/end_frame=0
+animation/clip_107/loops=false
+animation/clip_108/name=""
+animation/clip_108/start_frame=0
+animation/clip_108/end_frame=0
+animation/clip_108/loops=false
+animation/clip_109/name=""
+animation/clip_109/start_frame=0
+animation/clip_109/end_frame=0
+animation/clip_109/loops=false
+animation/clip_110/name=""
+animation/clip_110/start_frame=0
+animation/clip_110/end_frame=0
+animation/clip_110/loops=false
+animation/clip_111/name=""
+animation/clip_111/start_frame=0
+animation/clip_111/end_frame=0
+animation/clip_111/loops=false
+animation/clip_112/name=""
+animation/clip_112/start_frame=0
+animation/clip_112/end_frame=0
+animation/clip_112/loops=false
+animation/clip_113/name=""
+animation/clip_113/start_frame=0
+animation/clip_113/end_frame=0
+animation/clip_113/loops=false
+animation/clip_114/name=""
+animation/clip_114/start_frame=0
+animation/clip_114/end_frame=0
+animation/clip_114/loops=false
+animation/clip_115/name=""
+animation/clip_115/start_frame=0
+animation/clip_115/end_frame=0
+animation/clip_115/loops=false
+animation/clip_116/name=""
+animation/clip_116/start_frame=0
+animation/clip_116/end_frame=0
+animation/clip_116/loops=false
+animation/clip_117/name=""
+animation/clip_117/start_frame=0
+animation/clip_117/end_frame=0
+animation/clip_117/loops=false
+animation/clip_118/name=""
+animation/clip_118/start_frame=0
+animation/clip_118/end_frame=0
+animation/clip_118/loops=false
+animation/clip_119/name=""
+animation/clip_119/start_frame=0
+animation/clip_119/end_frame=0
+animation/clip_119/loops=false
+animation/clip_120/name=""
+animation/clip_120/start_frame=0
+animation/clip_120/end_frame=0
+animation/clip_120/loops=false
+animation/clip_121/name=""
+animation/clip_121/start_frame=0
+animation/clip_121/end_frame=0
+animation/clip_121/loops=false
+animation/clip_122/name=""
+animation/clip_122/start_frame=0
+animation/clip_122/end_frame=0
+animation/clip_122/loops=false
+animation/clip_123/name=""
+animation/clip_123/start_frame=0
+animation/clip_123/end_frame=0
+animation/clip_123/loops=false
+animation/clip_124/name=""
+animation/clip_124/start_frame=0
+animation/clip_124/end_frame=0
+animation/clip_124/loops=false
+animation/clip_125/name=""
+animation/clip_125/start_frame=0
+animation/clip_125/end_frame=0
+animation/clip_125/loops=false
+animation/clip_126/name=""
+animation/clip_126/start_frame=0
+animation/clip_126/end_frame=0
+animation/clip_126/loops=false
+animation/clip_127/name=""
+animation/clip_127/start_frame=0
+animation/clip_127/end_frame=0
+animation/clip_127/loops=false
+animation/clip_128/name=""
+animation/clip_128/start_frame=0
+animation/clip_128/end_frame=0
+animation/clip_128/loops=false
+animation/clip_129/name=""
+animation/clip_129/start_frame=0
+animation/clip_129/end_frame=0
+animation/clip_129/loops=false
+animation/clip_130/name=""
+animation/clip_130/start_frame=0
+animation/clip_130/end_frame=0
+animation/clip_130/loops=false
+animation/clip_131/name=""
+animation/clip_131/start_frame=0
+animation/clip_131/end_frame=0
+animation/clip_131/loops=false
+animation/clip_132/name=""
+animation/clip_132/start_frame=0
+animation/clip_132/end_frame=0
+animation/clip_132/loops=false
+animation/clip_133/name=""
+animation/clip_133/start_frame=0
+animation/clip_133/end_frame=0
+animation/clip_133/loops=false
+animation/clip_134/name=""
+animation/clip_134/start_frame=0
+animation/clip_134/end_frame=0
+animation/clip_134/loops=false
+animation/clip_135/name=""
+animation/clip_135/start_frame=0
+animation/clip_135/end_frame=0
+animation/clip_135/loops=false
+animation/clip_136/name=""
+animation/clip_136/start_frame=0
+animation/clip_136/end_frame=0
+animation/clip_136/loops=false
+animation/clip_137/name=""
+animation/clip_137/start_frame=0
+animation/clip_137/end_frame=0
+animation/clip_137/loops=false
+animation/clip_138/name=""
+animation/clip_138/start_frame=0
+animation/clip_138/end_frame=0
+animation/clip_138/loops=false
+animation/clip_139/name=""
+animation/clip_139/start_frame=0
+animation/clip_139/end_frame=0
+animation/clip_139/loops=false
+animation/clip_140/name=""
+animation/clip_140/start_frame=0
+animation/clip_140/end_frame=0
+animation/clip_140/loops=false
+animation/clip_141/name=""
+animation/clip_141/start_frame=0
+animation/clip_141/end_frame=0
+animation/clip_141/loops=false
+animation/clip_142/name=""
+animation/clip_142/start_frame=0
+animation/clip_142/end_frame=0
+animation/clip_142/loops=false
+animation/clip_143/name=""
+animation/clip_143/start_frame=0
+animation/clip_143/end_frame=0
+animation/clip_143/loops=false
+animation/clip_144/name=""
+animation/clip_144/start_frame=0
+animation/clip_144/end_frame=0
+animation/clip_144/loops=false
+animation/clip_145/name=""
+animation/clip_145/start_frame=0
+animation/clip_145/end_frame=0
+animation/clip_145/loops=false
+animation/clip_146/name=""
+animation/clip_146/start_frame=0
+animation/clip_146/end_frame=0
+animation/clip_146/loops=false
+animation/clip_147/name=""
+animation/clip_147/start_frame=0
+animation/clip_147/end_frame=0
+animation/clip_147/loops=false
+animation/clip_148/name=""
+animation/clip_148/start_frame=0
+animation/clip_148/end_frame=0
+animation/clip_148/loops=false
+animation/clip_149/name=""
+animation/clip_149/start_frame=0
+animation/clip_149/end_frame=0
+animation/clip_149/loops=false
+animation/clip_150/name=""
+animation/clip_150/start_frame=0
+animation/clip_150/end_frame=0
+animation/clip_150/loops=false
+animation/clip_151/name=""
+animation/clip_151/start_frame=0
+animation/clip_151/end_frame=0
+animation/clip_151/loops=false
+animation/clip_152/name=""
+animation/clip_152/start_frame=0
+animation/clip_152/end_frame=0
+animation/clip_152/loops=false
+animation/clip_153/name=""
+animation/clip_153/start_frame=0
+animation/clip_153/end_frame=0
+animation/clip_153/loops=false
+animation/clip_154/name=""
+animation/clip_154/start_frame=0
+animation/clip_154/end_frame=0
+animation/clip_154/loops=false
+animation/clip_155/name=""
+animation/clip_155/start_frame=0
+animation/clip_155/end_frame=0
+animation/clip_155/loops=false
+animation/clip_156/name=""
+animation/clip_156/start_frame=0
+animation/clip_156/end_frame=0
+animation/clip_156/loops=false
+animation/clip_157/name=""
+animation/clip_157/start_frame=0
+animation/clip_157/end_frame=0
+animation/clip_157/loops=false
+animation/clip_158/name=""
+animation/clip_158/start_frame=0
+animation/clip_158/end_frame=0
+animation/clip_158/loops=false
+animation/clip_159/name=""
+animation/clip_159/start_frame=0
+animation/clip_159/end_frame=0
+animation/clip_159/loops=false
+animation/clip_160/name=""
+animation/clip_160/start_frame=0
+animation/clip_160/end_frame=0
+animation/clip_160/loops=false
+animation/clip_161/name=""
+animation/clip_161/start_frame=0
+animation/clip_161/end_frame=0
+animation/clip_161/loops=false
+animation/clip_162/name=""
+animation/clip_162/start_frame=0
+animation/clip_162/end_frame=0
+animation/clip_162/loops=false
+animation/clip_163/name=""
+animation/clip_163/start_frame=0
+animation/clip_163/end_frame=0
+animation/clip_163/loops=false
+animation/clip_164/name=""
+animation/clip_164/start_frame=0
+animation/clip_164/end_frame=0
+animation/clip_164/loops=false
+animation/clip_165/name=""
+animation/clip_165/start_frame=0
+animation/clip_165/end_frame=0
+animation/clip_165/loops=false
+animation/clip_166/name=""
+animation/clip_166/start_frame=0
+animation/clip_166/end_frame=0
+animation/clip_166/loops=false
+animation/clip_167/name=""
+animation/clip_167/start_frame=0
+animation/clip_167/end_frame=0
+animation/clip_167/loops=false
+animation/clip_168/name=""
+animation/clip_168/start_frame=0
+animation/clip_168/end_frame=0
+animation/clip_168/loops=false
+animation/clip_169/name=""
+animation/clip_169/start_frame=0
+animation/clip_169/end_frame=0
+animation/clip_169/loops=false
+animation/clip_170/name=""
+animation/clip_170/start_frame=0
+animation/clip_170/end_frame=0
+animation/clip_170/loops=false
+animation/clip_171/name=""
+animation/clip_171/start_frame=0
+animation/clip_171/end_frame=0
+animation/clip_171/loops=false
+animation/clip_172/name=""
+animation/clip_172/start_frame=0
+animation/clip_172/end_frame=0
+animation/clip_172/loops=false
+animation/clip_173/name=""
+animation/clip_173/start_frame=0
+animation/clip_173/end_frame=0
+animation/clip_173/loops=false
+animation/clip_174/name=""
+animation/clip_174/start_frame=0
+animation/clip_174/end_frame=0
+animation/clip_174/loops=false
+animation/clip_175/name=""
+animation/clip_175/start_frame=0
+animation/clip_175/end_frame=0
+animation/clip_175/loops=false
+animation/clip_176/name=""
+animation/clip_176/start_frame=0
+animation/clip_176/end_frame=0
+animation/clip_176/loops=false
+animation/clip_177/name=""
+animation/clip_177/start_frame=0
+animation/clip_177/end_frame=0
+animation/clip_177/loops=false
+animation/clip_178/name=""
+animation/clip_178/start_frame=0
+animation/clip_178/end_frame=0
+animation/clip_178/loops=false
+animation/clip_179/name=""
+animation/clip_179/start_frame=0
+animation/clip_179/end_frame=0
+animation/clip_179/loops=false
+animation/clip_180/name=""
+animation/clip_180/start_frame=0
+animation/clip_180/end_frame=0
+animation/clip_180/loops=false
+animation/clip_181/name=""
+animation/clip_181/start_frame=0
+animation/clip_181/end_frame=0
+animation/clip_181/loops=false
+animation/clip_182/name=""
+animation/clip_182/start_frame=0
+animation/clip_182/end_frame=0
+animation/clip_182/loops=false
+animation/clip_183/name=""
+animation/clip_183/start_frame=0
+animation/clip_183/end_frame=0
+animation/clip_183/loops=false
+animation/clip_184/name=""
+animation/clip_184/start_frame=0
+animation/clip_184/end_frame=0
+animation/clip_184/loops=false
+animation/clip_185/name=""
+animation/clip_185/start_frame=0
+animation/clip_185/end_frame=0
+animation/clip_185/loops=false
+animation/clip_186/name=""
+animation/clip_186/start_frame=0
+animation/clip_186/end_frame=0
+animation/clip_186/loops=false
+animation/clip_187/name=""
+animation/clip_187/start_frame=0
+animation/clip_187/end_frame=0
+animation/clip_187/loops=false
+animation/clip_188/name=""
+animation/clip_188/start_frame=0
+animation/clip_188/end_frame=0
+animation/clip_188/loops=false
+animation/clip_189/name=""
+animation/clip_189/start_frame=0
+animation/clip_189/end_frame=0
+animation/clip_189/loops=false
+animation/clip_190/name=""
+animation/clip_190/start_frame=0
+animation/clip_190/end_frame=0
+animation/clip_190/loops=false
+animation/clip_191/name=""
+animation/clip_191/start_frame=0
+animation/clip_191/end_frame=0
+animation/clip_191/loops=false
+animation/clip_192/name=""
+animation/clip_192/start_frame=0
+animation/clip_192/end_frame=0
+animation/clip_192/loops=false
+animation/clip_193/name=""
+animation/clip_193/start_frame=0
+animation/clip_193/end_frame=0
+animation/clip_193/loops=false
+animation/clip_194/name=""
+animation/clip_194/start_frame=0
+animation/clip_194/end_frame=0
+animation/clip_194/loops=false
+animation/clip_195/name=""
+animation/clip_195/start_frame=0
+animation/clip_195/end_frame=0
+animation/clip_195/loops=false
+animation/clip_196/name=""
+animation/clip_196/start_frame=0
+animation/clip_196/end_frame=0
+animation/clip_196/loops=false
+animation/clip_197/name=""
+animation/clip_197/start_frame=0
+animation/clip_197/end_frame=0
+animation/clip_197/loops=false
+animation/clip_198/name=""
+animation/clip_198/start_frame=0
+animation/clip_198/end_frame=0
+animation/clip_198/loops=false
+animation/clip_199/name=""
+animation/clip_199/start_frame=0
+animation/clip_199/end_frame=0
+animation/clip_199/loops=false
+animation/clip_200/name=""
+animation/clip_200/start_frame=0
+animation/clip_200/end_frame=0
+animation/clip_200/loops=false
+animation/clip_201/name=""
+animation/clip_201/start_frame=0
+animation/clip_201/end_frame=0
+animation/clip_201/loops=false
+animation/clip_202/name=""
+animation/clip_202/start_frame=0
+animation/clip_202/end_frame=0
+animation/clip_202/loops=false
+animation/clip_203/name=""
+animation/clip_203/start_frame=0
+animation/clip_203/end_frame=0
+animation/clip_203/loops=false
+animation/clip_204/name=""
+animation/clip_204/start_frame=0
+animation/clip_204/end_frame=0
+animation/clip_204/loops=false
+animation/clip_205/name=""
+animation/clip_205/start_frame=0
+animation/clip_205/end_frame=0
+animation/clip_205/loops=false
+animation/clip_206/name=""
+animation/clip_206/start_frame=0
+animation/clip_206/end_frame=0
+animation/clip_206/loops=false
+animation/clip_207/name=""
+animation/clip_207/start_frame=0
+animation/clip_207/end_frame=0
+animation/clip_207/loops=false
+animation/clip_208/name=""
+animation/clip_208/start_frame=0
+animation/clip_208/end_frame=0
+animation/clip_208/loops=false
+animation/clip_209/name=""
+animation/clip_209/start_frame=0
+animation/clip_209/end_frame=0
+animation/clip_209/loops=false
+animation/clip_210/name=""
+animation/clip_210/start_frame=0
+animation/clip_210/end_frame=0
+animation/clip_210/loops=false
+animation/clip_211/name=""
+animation/clip_211/start_frame=0
+animation/clip_211/end_frame=0
+animation/clip_211/loops=false
+animation/clip_212/name=""
+animation/clip_212/start_frame=0
+animation/clip_212/end_frame=0
+animation/clip_212/loops=false
+animation/clip_213/name=""
+animation/clip_213/start_frame=0
+animation/clip_213/end_frame=0
+animation/clip_213/loops=false
+animation/clip_214/name=""
+animation/clip_214/start_frame=0
+animation/clip_214/end_frame=0
+animation/clip_214/loops=false
+animation/clip_215/name=""
+animation/clip_215/start_frame=0
+animation/clip_215/end_frame=0
+animation/clip_215/loops=false
+animation/clip_216/name=""
+animation/clip_216/start_frame=0
+animation/clip_216/end_frame=0
+animation/clip_216/loops=false
+animation/clip_217/name=""
+animation/clip_217/start_frame=0
+animation/clip_217/end_frame=0
+animation/clip_217/loops=false
+animation/clip_218/name=""
+animation/clip_218/start_frame=0
+animation/clip_218/end_frame=0
+animation/clip_218/loops=false
+animation/clip_219/name=""
+animation/clip_219/start_frame=0
+animation/clip_219/end_frame=0
+animation/clip_219/loops=false
+animation/clip_220/name=""
+animation/clip_220/start_frame=0
+animation/clip_220/end_frame=0
+animation/clip_220/loops=false
+animation/clip_221/name=""
+animation/clip_221/start_frame=0
+animation/clip_221/end_frame=0
+animation/clip_221/loops=false
+animation/clip_222/name=""
+animation/clip_222/start_frame=0
+animation/clip_222/end_frame=0
+animation/clip_222/loops=false
+animation/clip_223/name=""
+animation/clip_223/start_frame=0
+animation/clip_223/end_frame=0
+animation/clip_223/loops=false
+animation/clip_224/name=""
+animation/clip_224/start_frame=0
+animation/clip_224/end_frame=0
+animation/clip_224/loops=false
+animation/clip_225/name=""
+animation/clip_225/start_frame=0
+animation/clip_225/end_frame=0
+animation/clip_225/loops=false
+animation/clip_226/name=""
+animation/clip_226/start_frame=0
+animation/clip_226/end_frame=0
+animation/clip_226/loops=false
+animation/clip_227/name=""
+animation/clip_227/start_frame=0
+animation/clip_227/end_frame=0
+animation/clip_227/loops=false
+animation/clip_228/name=""
+animation/clip_228/start_frame=0
+animation/clip_228/end_frame=0
+animation/clip_228/loops=false
+animation/clip_229/name=""
+animation/clip_229/start_frame=0
+animation/clip_229/end_frame=0
+animation/clip_229/loops=false
+animation/clip_230/name=""
+animation/clip_230/start_frame=0
+animation/clip_230/end_frame=0
+animation/clip_230/loops=false
+animation/clip_231/name=""
+animation/clip_231/start_frame=0
+animation/clip_231/end_frame=0
+animation/clip_231/loops=false
+animation/clip_232/name=""
+animation/clip_232/start_frame=0
+animation/clip_232/end_frame=0
+animation/clip_232/loops=false
+animation/clip_233/name=""
+animation/clip_233/start_frame=0
+animation/clip_233/end_frame=0
+animation/clip_233/loops=false
+animation/clip_234/name=""
+animation/clip_234/start_frame=0
+animation/clip_234/end_frame=0
+animation/clip_234/loops=false
+animation/clip_235/name=""
+animation/clip_235/start_frame=0
+animation/clip_235/end_frame=0
+animation/clip_235/loops=false
+animation/clip_236/name=""
+animation/clip_236/start_frame=0
+animation/clip_236/end_frame=0
+animation/clip_236/loops=false
+animation/clip_237/name=""
+animation/clip_237/start_frame=0
+animation/clip_237/end_frame=0
+animation/clip_237/loops=false
+animation/clip_238/name=""
+animation/clip_238/start_frame=0
+animation/clip_238/end_frame=0
+animation/clip_238/loops=false
+animation/clip_239/name=""
+animation/clip_239/start_frame=0
+animation/clip_239/end_frame=0
+animation/clip_239/loops=false
+animation/clip_240/name=""
+animation/clip_240/start_frame=0
+animation/clip_240/end_frame=0
+animation/clip_240/loops=false
+animation/clip_241/name=""
+animation/clip_241/start_frame=0
+animation/clip_241/end_frame=0
+animation/clip_241/loops=false
+animation/clip_242/name=""
+animation/clip_242/start_frame=0
+animation/clip_242/end_frame=0
+animation/clip_242/loops=false
+animation/clip_243/name=""
+animation/clip_243/start_frame=0
+animation/clip_243/end_frame=0
+animation/clip_243/loops=false
+animation/clip_244/name=""
+animation/clip_244/start_frame=0
+animation/clip_244/end_frame=0
+animation/clip_244/loops=false
+animation/clip_245/name=""
+animation/clip_245/start_frame=0
+animation/clip_245/end_frame=0
+animation/clip_245/loops=false
+animation/clip_246/name=""
+animation/clip_246/start_frame=0
+animation/clip_246/end_frame=0
+animation/clip_246/loops=false
+animation/clip_247/name=""
+animation/clip_247/start_frame=0
+animation/clip_247/end_frame=0
+animation/clip_247/loops=false
+animation/clip_248/name=""
+animation/clip_248/start_frame=0
+animation/clip_248/end_frame=0
+animation/clip_248/loops=false
+animation/clip_249/name=""
+animation/clip_249/start_frame=0
+animation/clip_249/end_frame=0
+animation/clip_249/loops=false
+animation/clip_250/name=""
+animation/clip_250/start_frame=0
+animation/clip_250/end_frame=0
+animation/clip_250/loops=false
+animation/clip_251/name=""
+animation/clip_251/start_frame=0
+animation/clip_251/end_frame=0
+animation/clip_251/loops=false
+animation/clip_252/name=""
+animation/clip_252/start_frame=0
+animation/clip_252/end_frame=0
+animation/clip_252/loops=false
+animation/clip_253/name=""
+animation/clip_253/start_frame=0
+animation/clip_253/end_frame=0
+animation/clip_253/loops=false
+animation/clip_254/name=""
+animation/clip_254/start_frame=0
+animation/clip_254/end_frame=0
+animation/clip_254/loops=false
+animation/clip_255/name=""
+animation/clip_255/start_frame=0
+animation/clip_255/end_frame=0
+animation/clip_255/loops=false
+animation/clip_256/name=""
+animation/clip_256/start_frame=0
+animation/clip_256/end_frame=0
+animation/clip_256/loops=false
diff --git a/assets/levels/2.obj.import b/assets/levels/2.obj.import
new file mode 100644
index 0000000..1efa28c
--- /dev/null
+++ b/assets/levels/2.obj.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="wavefront_obj"
+type="Mesh"
+path="res://.import/2.obj-3a5433501168477746c24182726a90c2.mesh"
+
+[deps]
+
+files=[ "res://.import/2.obj-3a5433501168477746c24182726a90c2.mesh" ]
+
+source_file="res://assets/levels/2.obj"
+source_md5="9c1795f0c017f636969954e503a75157"
+
+dest_files=[ "res://.import/2.obj-3a5433501168477746c24182726a90c2.mesh", "res://.import/2.obj-3a5433501168477746c24182726a90c2.mesh" ]
+dest_md5="557048f3ebad19044a3c342d9c06e870"
+
+[params]
+
+generate_tangents=true
diff --git a/assets/maze-high-obj.obj.import b/assets/maze-high-obj.obj.import
new file mode 100644
index 0000000..dec7aba
--- /dev/null
+++ b/assets/maze-high-obj.obj.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="wavefront_obj"
+type="Mesh"
+path="res://.import/maze-high-obj.obj-24db734e0c48b4dd09ff2d9426ff3915.mesh"
+
+[deps]
+
+files=[ "res://.import/maze-high-obj.obj-24db734e0c48b4dd09ff2d9426ff3915.mesh" ]
+
+source_file="res://assets/maze-high-obj.obj"
+source_md5="6957a6487d30265864dc3d40baf411a2"
+
+dest_files=[ "res://.import/maze-high-obj.obj-24db734e0c48b4dd09ff2d9426ff3915.mesh", "res://.import/maze-high-obj.obj-24db734e0c48b4dd09ff2d9426ff3915.mesh" ]
+dest_md5="ce1017fcf90646333644720dd66b599f"
+
+[params]
+
+generate_tangents=true
diff --git a/assets/maze.obj.import b/assets/maze.obj.import
new file mode 100644
index 0000000..d8bc021
--- /dev/null
+++ b/assets/maze.obj.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="wavefront_obj"
+type="Mesh"
+path="res://.import/maze.obj-70de96fca66f7b42f2e7948110e529ab.mesh"
+
+[deps]
+
+files=[ "res://.import/maze.obj-70de96fca66f7b42f2e7948110e529ab.mesh" ]
+
+source_file="res://assets/maze.obj"
+source_md5="f35f5a0e67714a456ed725761a30cd8e"
+
+dest_files=[ "res://.import/maze.obj-70de96fca66f7b42f2e7948110e529ab.mesh", "res://.import/maze.obj-70de96fca66f7b42f2e7948110e529ab.mesh" ]
+dest_md5="611ce2fc213387da9af0b853967a2f25"
+
+[params]
+
+generate_tangents=true
diff --git a/assets/objective-left.png.import b/assets/objective-left.png.import
new file mode 100644
index 0000000..6485018
--- /dev/null
+++ b/assets/objective-left.png.import
@@ -0,0 +1,32 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/objective-left.png-587402b851e8944e16eb289e1b058974.stex"
+
+[deps]
+
+source_file="res://assets/objective-left.png"
+source_md5="c172ead10f0d4193e46622fe62668f31"
+
+dest_files=[ "res://.import/objective-left.png-587402b851e8944e16eb289e1b058974.stex" ]
+dest_md5="09403856c115d79b27429585b5a7b0f5"
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/assets/objective-right.png.import b/assets/objective-right.png.import
new file mode 100644
index 0000000..bd48aba
--- /dev/null
+++ b/assets/objective-right.png.import
@@ -0,0 +1,32 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/objective-right.png-d18195ff55e7aff72b594ff190053ce4.stex"
+
+[deps]
+
+source_file="res://assets/objective-right.png"
+source_md5="2725f8f335a4f2ceb2562ef44343830d"
+
+dest_files=[ "res://.import/objective-right.png-d18195ff55e7aff72b594ff190053ce4.stex" ]
+dest_md5="c3fa2e979e2fdea8132655a82686526d"
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/assets/objective.obj.import b/assets/objective.obj.import
new file mode 100644
index 0000000..440fb60
--- /dev/null
+++ b/assets/objective.obj.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="wavefront_obj"
+type="Mesh"
+path="res://.import/objective.obj-d3a42ffdae3ada4606dd452051061b51.mesh"
+
+[deps]
+
+files=[ "res://.import/objective.obj-d3a42ffdae3ada4606dd452051061b51.mesh" ]
+
+source_file="res://assets/objective.obj"
+source_md5="a00bec3114f9259f2ae795c28f75e9c9"
+
+dest_files=[ "res://.import/objective.obj-d3a42ffdae3ada4606dd452051061b51.mesh", "res://.import/objective.obj-d3a42ffdae3ada4606dd452051061b51.mesh" ]
+dest_md5="56ab97d8a864679c68c7c7d9c1d38dd8"
+
+[params]
+
+generate_tangents=true
diff --git a/assets/player.obj.import b/assets/player.obj.import
new file mode 100644
index 0000000..24579a1
--- /dev/null
+++ b/assets/player.obj.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="wavefront_obj"
+type="Mesh"
+path="res://.import/player.obj-9552f61e8059d9a4dd660ae96ad6a512.mesh"
+
+[deps]
+
+files=[ "res://.import/player.obj-9552f61e8059d9a4dd660ae96ad6a512.mesh" ]
+
+source_file="res://assets/player.obj"
+source_md5="7074ae8221d3b083f4f95e70e1ee446f"
+
+dest_files=[ "res://.import/player.obj-9552f61e8059d9a4dd660ae96ad6a512.mesh", "res://.import/player.obj-9552f61e8059d9a4dd660ae96ad6a512.mesh" ]
+dest_md5="0ca70d88338dbaa80a9676388d516d63"
+
+[params]
+
+generate_tangents=true
diff --git a/docs/2018-02-14-heroes.png.import b/docs/2018-02-14-heroes.png.import
new file mode 100644
index 0000000..83d92e3
--- /dev/null
+++ b/docs/2018-02-14-heroes.png.import
@@ -0,0 +1,32 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/2018-02-14-heroes.png-66ce877f0c90a512065fa1c503625ab1.stex"
+
+[deps]
+
+source_file="res://docs/2018-02-14-heroes.png"
+source_md5="98144251e8505d9c53c0ba6cf451448e"
+
+dest_files=[ "res://.import/2018-02-14-heroes.png-66ce877f0c90a512065fa1c503625ab1.stex" ]
+dest_md5="b9c1300d34876a596a8e284f821d7bc9"
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/icon.png.import b/icon.png.import
new file mode 100644
index 0000000..b330e51
--- /dev/null
+++ b/icon.png.import
@@ -0,0 +1,32 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex"
+
+[deps]
+
+source_file="res://icon.png"
+source_md5="7febcf604d750bd224f744362be04a8a"
+
+dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ]
+dest_md5="e86d0a8edb5d32d9447a249948fdcf57"
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0

From 2685a8cb69319efc9d53f4e16e5c06d4964a31e8 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sat, 28 Apr 2018 16:04:43 -0400
Subject: [PATCH 02/40] Make changes that Silas needed to not crash

---
 scripts/heroes/5_portal.gd | 3 +++
 scripts/player.gd          | 1 +
 2 files changed, 4 insertions(+)

diff --git a/scripts/heroes/5_portal.gd b/scripts/heroes/5_portal.gd
index 79e9d94..9b4f547 100644
--- a/scripts/heroes/5_portal.gd
+++ b/scripts/heroes/5_portal.gd
@@ -2,6 +2,7 @@ extends "res://scripts/placeable.gd"
 
 var portal_charge = -5
 var other
+var index
 
 var enemy_colors = [
 	Color("#d14013"),
@@ -24,6 +25,8 @@ func _exit_tree():
 		other.queue_free()
 
 func init(maker):
+	
+	index = maker.placement.placed.size()
 
 	# If index is odd, we're the second (1, 3...), if even, first (0, 4...)
 	var second = index % 2 != 0
diff --git a/scripts/player.gd b/scripts/player.gd
index 3208b4a..628a220 100644
--- a/scripts/player.gd
+++ b/scripts/player.gd
@@ -54,6 +54,7 @@ func _ready():
 	if is_network_master():
 		get_node("TPCamera/Camera/Ray").add_exception(self)
 		get_node(tp_camera).set_enabled(true)
+		get_node(tp_camera).cam_view_sensitivity = 0.05
 		spawn()
 		if "is_ai" in player_info and player_info.is_ai and not ai_instanced:
 			add_child(preload("res://scenes/ai.tscn").instance())

From 0855689ae6b5ab6c7ad4832cd95409344db70154 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sat, 28 Apr 2018 17:15:06 -0400
Subject: [PATCH 03/40] Add currently bound button to ability_icon

---
 scenes/ability_icon.tscn | 31 +++++++++++++++++++++++++++----
 scenes/heroes/0.tscn     |  1 +
 scenes/heroes/4.tscn     |  1 +
 scenes/heroes/5.tscn     |  1 +
 scripts/ability_icon.gd  | 16 ++++++++++++++++
 5 files changed, 46 insertions(+), 4 deletions(-)

diff --git a/scenes/ability_icon.tscn b/scenes/ability_icon.tscn
index f1e4a25..3f2b208 100644
--- a/scenes/ability_icon.tscn
+++ b/scenes/ability_icon.tscn
@@ -77,6 +77,7 @@ script = ExtResource( 1 )
 cost = 1
 ability_name = "Ability"
 display_progress = true
+action = ""
 
 [node name="Bar" type="ProgressBar" parent="." index="0"]
 
@@ -127,10 +128,10 @@ anchor_left = 0.0
 anchor_top = 0.0
 anchor_right = 0.0
 anchor_bottom = 0.0
-margin_left = -65.0
-margin_top = 36.0
-margin_right = 77.0
-margin_bottom = 50.0
+margin_left = -68.0
+margin_top = -49.0
+margin_right = 74.0
+margin_bottom = -35.0
 rect_pivot_offset = Vector2( 0, 0 )
 mouse_filter = 2
 mouse_default_cursor_shape = 0
@@ -143,4 +144,26 @@ percent_visible = 1.0
 lines_skipped = 0
 max_lines_visible = -1
 
+[node name="Button" type="Label" parent="." index="3"]
+
+anchor_left = 0.0
+anchor_top = 0.0
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_left = -68.0
+margin_top = 38.0
+margin_right = 74.0
+margin_bottom = 52.0
+rect_pivot_offset = Vector2( 0, 0 )
+mouse_filter = 2
+mouse_default_cursor_shape = 0
+size_flags_horizontal = 1
+size_flags_vertical = 4
+custom_colors/font_color = Color( 0.00357056, 0.0703125, 0.04372, 1 )
+text = "<Button>"
+align = 1
+percent_visible = 1.0
+lines_skipped = 0
+max_lines_visible = -1
+
 
diff --git a/scenes/heroes/0.tscn b/scenes/heroes/0.tscn
index e43947c..ef679a0 100644
--- a/scenes/heroes/0.tscn
+++ b/scenes/heroes/0.tscn
@@ -37,5 +37,6 @@ margin_bottom = -109.0
 cost = 2
 ability_name = "Speed Boost"
 display_progress = false
+action = "hero_0_boost"
 
 
diff --git a/scenes/heroes/4.tscn b/scenes/heroes/4.tscn
index 02afe69..12e1db9 100644
--- a/scenes/heroes/4.tscn
+++ b/scenes/heroes/4.tscn
@@ -47,5 +47,6 @@ margin_right = -98.0
 margin_bottom = -91.0
 cost = 0
 ability_name = "Destroy"
+action = "primary_ability"
 
 
diff --git a/scenes/heroes/5.tscn b/scenes/heroes/5.tscn
index 6dfbdbc..1b5507c 100644
--- a/scenes/heroes/5.tscn
+++ b/scenes/heroes/5.tscn
@@ -32,6 +32,7 @@ margin_right = -149.0
 margin_bottom = -103.0
 cost = 20
 ability_name = "Build Portal"
+action = "hero_5_place_portal"
 
 [node name="Teleport" parent="MasterOnly" index="3" instance=ExtResource( 5 )]
 
diff --git a/scripts/ability_icon.gd b/scripts/ability_icon.gd
index 2ff1bc4..f83f230 100644
--- a/scripts/ability_icon.gd
+++ b/scripts/ability_icon.gd
@@ -6,11 +6,27 @@ onready var available = get_node("Available")
 export var cost = 1
 export var ability_name = "Ability"
 export var display_progress = true
+export var action = ""
 # This is intended to be public
 var disabled = false
 
 func _ready():
 	get_node("Name").text = ability_name
+	var description
+	if action:
+		var primary = InputMap.get_action_list(action)[0]
+		if primary is InputEventMouseButton:
+			if primary.button_index == BUTTON_LEFT:
+				description = "Click"
+			elif primary.button_index == BUTTON_RIGHT:
+				description = "Right Click"
+			else:
+				description = "Scroll Click"
+		else:
+			description = primary.as_text()
+	else:
+		description = ""
+	get_node("Button").text = description
 
 func _process(delta):
 	if disabled:

From c0345153e284d24081984dd6d8199e56a076a9b7 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sat, 28 Apr 2018 17:29:42 -0400
Subject: [PATCH 04/40] [Ability icon] Show when action is pressed

---
 scenes/ability_icon.tscn | 40 +++++++++++++++++++---------------------
 scenes/heroes/0.tscn     |  2 +-
 scripts/ability_icon.gd  |  7 +++++++
 scripts/heroes/0.gd      |  2 +-
 4 files changed, 28 insertions(+), 23 deletions(-)

diff --git a/scenes/ability_icon.tscn b/scenes/ability_icon.tscn
index 3f2b208..641ba31 100644
--- a/scenes/ability_icon.tscn
+++ b/scenes/ability_icon.tscn
@@ -8,7 +8,7 @@ content_margin_left = -1.0
 content_margin_right = -1.0
 content_margin_top = -1.0
 content_margin_bottom = -1.0
-bg_color = Color( 0.0297089, 0.230469, 0.206942, 1 )
+bg_color = Color( 0.042572, 0.351562, 0.315353, 1 )
 draw_center = true
 border_width_left = 0
 border_width_top = 0
@@ -64,10 +64,8 @@ anchor_left = 0.0
 anchor_top = 0.0
 anchor_right = 0.0
 anchor_bottom = 0.0
-margin_left = -6.0
-margin_top = -4.0
-margin_right = 5.0
-margin_bottom = 5.0
+margin_right = 30.0
+margin_bottom = 30.0
 rect_pivot_offset = Vector2( 0, 0 )
 mouse_filter = 0
 mouse_default_cursor_shape = 0
@@ -85,10 +83,10 @@ anchor_left = 0.0
 anchor_top = 0.0
 anchor_right = 0.0
 anchor_bottom = 0.0
-margin_left = -20.0
-margin_top = -21.0
-margin_right = 30.0
-margin_bottom = 28.0
+margin_left = -25.0
+margin_top = -25.0
+margin_right = 25.0
+margin_bottom = 25.0
 rect_pivot_offset = Vector2( 0, 0 )
 mouse_filter = 0
 mouse_default_cursor_shape = 0
@@ -111,10 +109,10 @@ anchor_left = 0.0
 anchor_top = 0.0
 anchor_right = 0.0
 anchor_bottom = 0.0
-margin_left = -25.0
-margin_top = -26.0
-margin_right = 25.0
-margin_bottom = 23.0
+margin_left = -30.0
+margin_top = -30.0
+margin_right = 20.0
+margin_bottom = 20.0
 rect_pivot_offset = Vector2( 0, 0 )
 mouse_filter = 0
 mouse_default_cursor_shape = 0
@@ -128,10 +126,10 @@ anchor_left = 0.0
 anchor_top = 0.0
 anchor_right = 0.0
 anchor_bottom = 0.0
-margin_left = -68.0
-margin_top = -49.0
-margin_right = 74.0
-margin_bottom = -35.0
+margin_left = -72.0
+margin_top = -44.0
+margin_right = 70.0
+margin_bottom = -30.0
 rect_pivot_offset = Vector2( 0, 0 )
 mouse_filter = 2
 mouse_default_cursor_shape = 0
@@ -150,10 +148,10 @@ anchor_left = 0.0
 anchor_top = 0.0
 anchor_right = 0.0
 anchor_bottom = 0.0
-margin_left = -68.0
-margin_top = 38.0
-margin_right = 74.0
-margin_bottom = 52.0
+margin_left = -72.0
+margin_top = 29.0
+margin_right = 70.0
+margin_bottom = 43.0
 rect_pivot_offset = Vector2( 0, 0 )
 mouse_filter = 2
 mouse_default_cursor_shape = 0
diff --git a/scenes/heroes/0.tscn b/scenes/heroes/0.tscn
index ef679a0..4119ca7 100644
--- a/scenes/heroes/0.tscn
+++ b/scenes/heroes/0.tscn
@@ -24,7 +24,7 @@ mesh = ExtResource( 4 )
 
 text = ""
 
-[node name="ProgressBar" parent="MasterOnly" index="1" instance=ExtResource( 5 )]
+[node name="Boost" parent="MasterOnly" index="1" instance=ExtResource( 5 )]
 
 anchor_left = 1.0
 anchor_top = 1.0
diff --git a/scripts/ability_icon.gd b/scripts/ability_icon.gd
index f83f230..886f9db 100644
--- a/scripts/ability_icon.gd
+++ b/scripts/ability_icon.gd
@@ -28,7 +28,14 @@ func _ready():
 		description = ""
 	get_node("Button").text = description
 
+func is_pressed():
+	return Input.is_action_pressed(action)
+
 func _process(delta):
+	if action and Input.is_action_pressed(action):
+		available.rect_position = Vector2(-25, -25) # Centered / not offset
+	else:
+		available.rect_position = Vector2(-30, -30)
 	if disabled:
 		available.hide()
 		bar.value = 0
diff --git a/scripts/heroes/0.gd b/scripts/heroes/0.gd
index d2c4110..8818133 100644
--- a/scripts/heroes/0.gd
+++ b/scripts/heroes/0.gd
@@ -24,7 +24,7 @@ func control_player(state):
 	var boost_strength = 2
 	var boost_drain = 25 # Recall increased charge must be factored in
 	var cost = boost_drain * state.step
-	if Input.is_action_pressed("hero_0_boost") and switch_charge > cost:
+	if get_node("MasterOnly/Boost").is_pressed() and switch_charge > cost:
 		walk_speed *= 2
 		air_accel *= 3
 		switch_charge -= cost

From 58a345b84a4f40e6b777a2ba0e1d6dd638eca0df Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sat, 28 Apr 2018 17:30:00 -0400
Subject: [PATCH 05/40] Make change hero into an ability icon

---
 scenes/player.tscn | 25 ++++++++++++++++++++-----
 scripts/player.gd  |  6 ++++--
 2 files changed, 24 insertions(+), 7 deletions(-)

diff --git a/scenes/player.tscn b/scenes/player.tscn
index e7cf436..436cfc6 100644
--- a/scenes/player.tscn
+++ b/scenes/player.tscn
@@ -1,8 +1,9 @@
-[gd_scene load_steps=14 format=2]
+[gd_scene load_steps=15 format=2]
 
 [ext_resource path="res://scripts/player.gd" type="Script" id=1]
-[ext_resource path="res://scripts/tp_camera.gd" type="Script" id=2]
-[ext_resource path="res://scripts/player_name.gd" type="Script" id=3]
+[ext_resource path="res://scenes/ability_icon.tscn" type="PackedScene" id=2]
+[ext_resource path="res://scripts/tp_camera.gd" type="Script" id=3]
+[ext_resource path="res://scripts/player_name.gd" type="Script" id=4]
 
 [sub_resource type="CapsuleShape" id=1]
 
@@ -365,10 +366,24 @@ percent_visible = 1.0
 lines_skipped = 0
 max_lines_visible = -1
 
+[node name="SwitchHero" parent="MasterOnly" index="2" instance=ExtResource( 2 )]
+
+visible = false
+anchor_left = 0.5
+anchor_top = 1.0
+anchor_right = 0.5
+anchor_bottom = 1.0
+margin_top = -107.0
+margin_bottom = -98.0
+cost = 100
+ability_name = "Switch Hero"
+display_progress = false
+action = "switch_hero"
+
 [node name="TPCamera" type="Spatial" parent="." index="5"]
 
 transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.56913, 0 )
-script = ExtResource( 2 )
+script = ExtResource( 3 )
 cam = NodePath("Camera")
 pivot = NodePath("Pivot")
 
@@ -434,6 +449,6 @@ align = 1
 percent_visible = 1.0
 lines_skipped = 0
 max_lines_visible = -1
-script = ExtResource( 3 )
+script = ExtResource( 4 )
 
 
diff --git a/scripts/player.gd b/scripts/player.gd
index 628a220..a85d604 100644
--- a/scripts/player.gd
+++ b/scripts/player.gd
@@ -20,6 +20,7 @@ var movement_charge = 0.15 # In percent per meter (except when heroes change tha
 onready var switch_text = get_node("MasterOnly/ChargeBar/ChargeText")
 onready var switch_bar = get_node("MasterOnly/ChargeBar")
 onready var switch_bar_extra = get_node("MasterOnly/ChargeBar/Extra")
+onready var switch_hero_action = get_node("MasterOnly/SwitchHero")
 
 var fall_height = -400 # This is essentially the respawn timer
 var switch_height = -150 # At this point, stop adding to switch_charge. This makes falls not charge you too much
@@ -85,8 +86,9 @@ func _process(delta):
 		switch_charge += movement_charge * vel.length() * delta
 		switch_text.set_text("%d%%" % int(switch_charge)) # We truncate, rather than round, so that switch is displayed AT 100%
 		if switch_charge >= 100:
-			# Let switch_charge keep building, because we use it for walk_speed and things
-			switch_text.set_text("100%% (%.f)\nQ - Switch hero" % switch_charge)
+			switch_hero_action.show()
+		else:
+			switch_hero_action.hide()
 		if switch_charge > switch_charge_cap:
 			# There is however a cap
 			switch_charge = switch_charge_cap

From 1154524958eb66751cf69a484dc9a036879b4efe Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sat, 28 Apr 2018 17:36:57 -0400
Subject: [PATCH 06/40] Fix warning on nickname change before connection

---
 scripts/lobby.gd | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index cf8000a..4f37dc1 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -188,8 +188,9 @@ sync func set_hero(peer, hero):
 	render_player_list()
 
 func resend_name():
-	var name = get_node("PlayerSettings/Username").get_text()
-	rpc("set_name", get_tree().get_network_unique_id(), 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

From d6a158a1ae8e0cd3921ee486ddbb3a06ee443574 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sat, 28 Apr 2018 17:53:51 -0400
Subject: [PATCH 07/40] Give losing or non-possessing team more charge!

Nice! I love this balance tweak! And it can be changed depending on how
strong we want it to be.
---
 plans.md                   |  2 --
 scenes/level.tscn          |  1 -
 scripts/heroes/1_wall.gd   |  2 +-
 scripts/heroes/2.gd        |  2 +-
 scripts/heroes/3.gd        |  2 +-
 scripts/heroes/4.gd        |  4 ++--
 scripts/heroes/5.gd        |  2 +-
 scripts/heroes/5_portal.gd |  2 +-
 scripts/player.gd          | 17 ++++++++++++++++-
 9 files changed, 23 insertions(+), 11 deletions(-)

diff --git a/plans.md b/plans.md
index 0fde1f4..e127ec1 100644
--- a/plans.md
+++ b/plans.md
@@ -25,8 +25,6 @@ Smaller TODOs:
 - Ira is OP?
   - Nerfed - 5 walls
 - Make motion more reactive?
-- Grab someone and draw an impulse on them
-  - Combine with SUPERBIA, use charge to build portals
 
 Bugs:
 
diff --git a/scenes/level.tscn b/scenes/level.tscn
index fc34b1a..16e1a7d 100644
--- a/scenes/level.tscn
+++ b/scenes/level.tscn
@@ -155,7 +155,6 @@ directional_shadow_max_distance = 200.0
 
 [node name="Players" type="Spatial" parent="." index="1"]
 
-
 [node name="Ball" type="RigidBody" parent="." index="2"]
 
 transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -14.4344, 6.09969, 4.40885 )
diff --git a/scripts/heroes/1_wall.gd b/scripts/heroes/1_wall.gd
index f359e33..ef78b48 100644
--- a/scripts/heroes/1_wall.gd
+++ b/scripts/heroes/1_wall.gd
@@ -5,7 +5,7 @@ var being_touched = 0
 
 func _process(delta):
 	if being_touched > 0:
-		maker_node.switch_charge += touch_charge * delta
+		maker_node.build_charge(touch_charge * delta)
 
 func init(maker):
 
diff --git a/scripts/heroes/2.gd b/scripts/heroes/2.gd
index 7f84167..3af3272 100644
--- a/scripts/heroes/2.gd
+++ b/scripts/heroes/2.gd
@@ -15,7 +15,7 @@ func _process(delta):
 				get_node("MasterOnly/Crosshair").set_text("\\/")
 
 		var overlapping = get_node("Area").get_overlapping_bodies().size()
-		switch_charge += delta * overlap_charge * overlapping
+		build_charge(delta * overlap_charge * overlapping)
 
 sync func switch_gravity():
 	var area = get_node("Area")
diff --git a/scripts/heroes/3.gd b/scripts/heroes/3.gd
index c904333..b5603af 100644
--- a/scripts/heroes/3.gd
+++ b/scripts/heroes/3.gd
@@ -39,7 +39,7 @@ func _process(delta):
 			# Subtract and then add, so we can continously add this
 			switch_charge -= boost_charge
 			boost_charge = merged.switch_charge - original_charge
-			switch_charge += boost_charge
+			build_charge(boost_charge)
 
 func control_player(state):
 	if !merged:
diff --git a/scripts/heroes/4.gd b/scripts/heroes/4.gd
index c25b00c..179792a 100644
--- a/scripts/heroes/4.gd
+++ b/scripts/heroes/4.gd
@@ -46,8 +46,8 @@ func _process(delta):
 			var player = pick_from(players)
 			if player != -1:
 				# We get charge for just stunning, plus charge for how much linear velocity we cut out
-				switch_charge += stun_charge * delta
-				switch_charge += velocity_charge * players[player].get_linear_velocity().length() * delta
+				build_charge(stun_charge * delta)
+				build_charge(velocity_charge * players[player].linear_velocity.length() * delta)
 				rpc("stun", players[player].get_name(), get_node("TPCamera/Camera/Ray").get_collision_point())
 				is_stunning = true
 
diff --git a/scripts/heroes/5.gd b/scripts/heroes/5.gd
index 3f7fb15..72fd2e8 100644
--- a/scripts/heroes/5.gd
+++ b/scripts/heroes/5.gd
@@ -63,7 +63,7 @@ func flick_input():
 		towards -= gravity
 		rpc("flick", flicking.get_name(), towards)
 		flicking = null
-		switch_charge += flick_charge
+		build_charge(flick_charge)
 
 sync func flick(player_id, towards):
 	var who = $"/root/Level/Players".get_node(player_id)
diff --git a/scripts/heroes/5_portal.gd b/scripts/heroes/5_portal.gd
index 9b4f547..da361b2 100644
--- a/scripts/heroes/5_portal.gd
+++ b/scripts/heroes/5_portal.gd
@@ -65,5 +65,5 @@ func portal(player):
 				# With both axes, gravity could never bring us to hit the portal
 				var to = other.to_global(Vector3(spawn_distance,0,-spawn_distance)) 
 				player.set_translation(to)
-				maker_node.switch_charge += portal_charge
+				maker_node.build_charge(portal_charge)
 
diff --git a/scripts/player.gd b/scripts/player.gd
index a85d604..2adddfa 100644
--- a/scripts/player.gd
+++ b/scripts/player.gd
@@ -83,7 +83,7 @@ func _process(delta):
 		var vel = get_linear_velocity()
 		if translation.y < switch_height:
 			vel.y = 0 # Don't gain charge from falling when below switch_height
-		switch_charge += movement_charge * vel.length() * delta
+		build_charge(movement_charge * vel.length() * delta)
 		switch_text.set_text("%d%%" % int(switch_charge)) # We truncate, rather than round, so that switch is displayed AT 100%
 		if switch_charge >= 100:
 			switch_hero_action.show()
@@ -118,6 +118,21 @@ func _exit_tree():
 # Functions
 # =========
 
+# Build all charge with a multiplier for ~~balance~~
+func build_charge(amount):
+	# If we used build_charge to cost charge, don't mess with it!
+	if amount > 0:
+		var losing_advantage = 1.2
+		var uncapped_advantage = 1.3
+		var obj = get_node("/root/Level/FullObjective/Objective")
+		if (obj.left > obj.right) == player_info.is_right_team:
+			# Is losing (left winning, we're on right or vice versa)
+			amount *= losing_advantage
+		if obj.right_active != player_info.is_right_team and obj.active:
+			# Point against us (right active and left, or vice versa)
+			amount *= uncapped_advantage
+	switch_charge += amount
+
 sync func spawn():
 	emit_signal("spawn")
 	if "record" in player_info:

From 535916ad825effc55e04ca944384353c92327b8e Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sun, 6 May 2018 22:39:27 -0400
Subject: [PATCH 08/40] [Hero 5] Increase portal cost to 75

---
 README.md            | 8 ++++++--
 plans.md             | 1 +
 scenes/heroes/5.tscn | 2 +-
 3 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index f0aa270..3fb58c8 100644
--- a/README.md
+++ b/README.md
@@ -27,14 +27,17 @@ Current heroes:
   - CARITAS (Margarine)
   - SUPERBIA (Build portals)
 
-Ideas for Heroes:
+Ideas for Heroes / Abilities:
 
 - More active
-  - Blink (Tracer) - no cooldown, but loses all speed on hitting walls
+  - Blink (like tracer) costs charge
   - Heavy guy - Slow, but very heavy for the see-saw
   - Climb and glide abilities
   - JUMPING
 - More supportive
+  - Switch places with a TEAMMATE
+    - This is awesomely self-regulating because it's zero-sum
+      - However, it can't really gain charge because it can still be abused
   - Boost - Area of effect or zarya-like cast, speeds people up
   - Flop - Changes side of see-saw for each team (should it be mechanic instead??)
   - Build - Build Zineth-like rails for anyone to use
@@ -42,6 +45,7 @@ Ideas for Heroes:
   - (Combine with LUSSURIA?) Hook and swing on terrain
 - More offensive
   - Lay traps
+  - Freeze a hero. A teammate of that hero must unfreeze them! (Or *n* seconds or whatever)
   - (Combine with PAZIENZA?) Destroy buildings
   - (Combine wit LUSSURIA?) Hold, then release to explode enemies away
 
diff --git a/plans.md b/plans.md
index e127ec1..88f1138 100644
--- a/plans.md
+++ b/plans.md
@@ -8,6 +8,7 @@ Big TODOs:
 - ^ I think you need more abilities for heroes, that combine well
   - Like, you could combine PAZIENZA with [destroys buildings]
 - Start making maps and figuring out what works / what you like
+  - Make just one new main level with the lessons you've learned from this one
   - I'd love to do a payload map!
   - More platforming for everyone (It's industria only rn tbh)
   - Give portals a tilted wall to fuck with acceleration
diff --git a/scenes/heroes/5.tscn b/scenes/heroes/5.tscn
index 1b5507c..3a57297 100644
--- a/scenes/heroes/5.tscn
+++ b/scenes/heroes/5.tscn
@@ -30,7 +30,7 @@ margin_left = -160.0
 margin_top = -112.0
 margin_right = -149.0
 margin_bottom = -103.0
-cost = 20
+cost = 75
 ability_name = "Build Portal"
 action = "hero_5_place_portal"
 

From 16aec4ae32d8848748985f8e849534f78b5299e0 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Mon, 7 May 2018 19:22:22 -0400
Subject: [PATCH 09/40] [WIP] Begin dummy matchmaking code

---
 scripts/lobby.gd       | 24 +++++++++++++++------
 scripts/matchmaking.gd | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++
 util/matchmaker.sh     |  1 +
 3 files changed, 76 insertions(+), 6 deletions(-)
 create mode 100644 scripts/matchmaking.gd
 create mode 100644 util/matchmaker.sh

diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index 4f37dc1..faca79f 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -3,8 +3,7 @@ extends "res://scripts/args.gd"
 # class member variables go here, for example:
 # var a = 2
 # var b = "textvar"
-var SERVER_PORT = 54672
-var MAX_PLAYERS = 10
+var port = null # Defined by command-line argument with default
 
 var player_info = {}
 var my_info = {}
@@ -14,13 +13,17 @@ 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
 
+onready var matchmaking = preload("res://scripts/matchmaking.gd").new()
+
 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')
@@ -39,6 +42,8 @@ func option_sel(button_name, option):
 
 func _ready():
 
+	add_child(matchmaking)
+
 	my_info.version = [0,0,0] # Semantic versioning: [0].[1].[2]
 
 	randomize()
@@ -66,8 +71,12 @@ func _ready():
 		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
 		call_deferred("_client_init")
@@ -94,14 +103,14 @@ func _client_init(ip=null):
 	if not ip:
 		ip = get_node("CustomGame/IP").get_text()
 	ip = IP.resolve_hostname(ip)
-	peer.create_client(ip, SERVER_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(SERVER_PORT, 1)
+	peer.create_server(port, 1)
 	get_tree().set_network_peer(peer)
 	player_info[1] = my_info
 	start_game()
@@ -109,7 +118,7 @@ func _singleplayer_init():
 func _server_init():
 	collect_info()
 	var peer = NetworkedMultiplayerENet.new()
-	peer.create_server(SERVER_PORT, MAX_PLAYERS)
+	peer.create_server(port, matchmaking.SERVER_SIZE)
 	get_tree().set_network_peer(peer)
 	is_connected = true
 	get_node("CustomGame/Server").set_text("Serving!")
@@ -117,6 +126,9 @@ func _server_init():
 	if server_playing:
 		player_info[1] = my_info
 
+func _matchmaker_init():
+	matchmaking.run_matchmaker()
+
 func _player_connected(id):
 	pass
 
@@ -167,7 +179,7 @@ remote func register_player(new_peer, info):
 			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:
+		if not begun and player_info.size() == matchmaking.SERVER_SIZE:
 			start_game()
 		if begun:
 			rpc_id(new_peer, "pre_configure_game", my_info.level)
diff --git a/scripts/matchmaking.gd b/scripts/matchmaking.gd
new file mode 100644
index 0000000..f4e5916
--- /dev/null
+++ b/scripts/matchmaking.gd
@@ -0,0 +1,57 @@
+extends Node
+
+var SERVER_TO_SERVER_PORT = 54671
+var MATCHMAKING_PORT = 54672
+var SERVER_SIZE = 6
+
+var next_port = 54673
+
+# Filled with queue info which contains
+# { "netid" }
+var queue = []
+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
+
+func _ready():
+	set_process(false)
+
+func run_matchmaker():
+	skirmish = spawn_server()
+	matchmaker_to_games = TCP_Server.new()
+	if matchmaker_to_games.listen(SERVER_TO_SERVER_PORT):
+		print("Error, could not listen")
+	set_process(true)
+
+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
+		print("Server has requested connection")
+
+master func _queue(info):
+	queue.push(info)
+	check_queue()
+
+func check_queue():
+	if queue.size() >= SERVER_SIZE:
+		var port = spawn_server()
+		games.push(port)
+		for p in queue:
+			rpc_id(p.netid, "join_game", port)
+
+func spawn_server():
+	OS.execute("util/server.sh", [], false)
+	next_port += 1
+	return (next_port - 1) # Return original port
+
diff --git a/util/matchmaker.sh b/util/matchmaker.sh
new file mode 100644
index 0000000..1fb5897
--- /dev/null
+++ b/util/matchmaker.sh
@@ -0,0 +1 @@
+godot-server -matchmaker

From 97b09048a977947a5d53fa512bd7a58107d4fd87 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Mon, 7 May 2018 19:49:33 -0400
Subject: [PATCH 10/40] Make -start-game work on server as well

---
 scripts/lobby.gd | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index 4f37dc1..c2e5b47 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -70,7 +70,6 @@ func _ready():
 		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"):
@@ -116,6 +115,8 @@ func _server_init():
 	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 _player_connected(id):
 	pass

From 1443eb110767b3927aea75ca3321530f045ef5ce Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Mon, 7 May 2018 19:51:36 -0400
Subject: [PATCH 11/40] Make matchmaker start true server without errors

i.e., it still doesn't actually do any matchmaking. But it runs the
actual server without errors.
---
 scripts/matchmaking.gd | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/scripts/matchmaking.gd b/scripts/matchmaking.gd
index f4e5916..d0d2653 100644
--- a/scripts/matchmaking.gd
+++ b/scripts/matchmaking.gd
@@ -8,7 +8,7 @@ var next_port = 54673
 
 # Filled with queue info which contains
 # { "netid" }
-var queue = []
+var skirmishing_players = []
 var skirmish
 # To avoid the confusion of the gameSERVERS being CLIENTS,
 # we just call them games whenever possible
@@ -39,15 +39,17 @@ func _process(delta):
 		game_streams.append(stream) # make new data transfer object for game
 		print("Server has requested connection")
 
-master func _queue(info):
-	queue.push(info)
+master func queue(info):
+	var netid = get_tree().get_rpc_sender_id()
+	rpc_id(netid, "join_game", skirmish)
+	skirmishing_players.push(netid)
 	check_queue()
 
 func check_queue():
-	if queue.size() >= SERVER_SIZE:
+	if skirmishing_players.size() >= SERVER_SIZE:
 		var port = spawn_server()
 		games.push(port)
-		for p in queue:
+		for p in skirmishing_players:
 			rpc_id(p.netid, "join_game", port)
 
 func spawn_server():

From 8f5a8417e1313efc932cf7c0d9f841431e897f49 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Wed, 9 May 2018 13:18:17 -0400
Subject: [PATCH 12/40] Add player to skirmish on connect to matchmaker

---
 scripts/lobby.gd       | 24 ++++++++++++++++--------
 scripts/matchmaking.gd | 43 ++++++++++++++++++++++++++++++++++---------
 util/server.sh         |  2 +-
 3 files changed, 51 insertions(+), 18 deletions(-)

diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index 6a068fe..3c13183 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -10,6 +10,7 @@ var my_info = {}
 var begun = false
 var server_playing = true
 var global_server_ip = "nv.cosinegaming.com"
+var ip = null
 var players_done = []
 var is_connected = false # Technically this can be done with ENetcetera but it's easier this way
 
@@ -47,6 +48,7 @@ func _ready():
 	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")
@@ -57,6 +59,11 @@ func _ready():
 	get_node("CustomGame/Singleplayer").connect("pressed", self, "_singleplayer_init")
 	get_node("CustomGame/LevelSelect").connect("item_selected", self, "select_level")
 
+	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 parse_args():
 	var o = setup_options()
 	o.parse()
 
@@ -89,19 +96,19 @@ func _ready():
 		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)
+	ip = global_server_ip
+	_client_init()
 
-func _client_init(ip=null):
+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!")
@@ -117,7 +124,8 @@ func _singleplayer_init():
 func _server_init():
 	collect_info()
 	var peer = NetworkedMultiplayerENet.new()
-	peer.create_server(port, matchmaking.SERVER_SIZE)
+	print("Starting server on port " + str(port))
+	peer.create_server(port, matchmaking.GAME_SIZE)
 	get_tree().set_network_peer(peer)
 	is_connected = true
 	get_node("CustomGame/Server").set_text("Serving!")
@@ -180,7 +188,7 @@ remote func register_player(new_peer, info):
 			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.SERVER_SIZE:
+		if not begun and player_info.size() == matchmaking.GAME_SIZE:
 			start_game()
 		if begun:
 			rpc_id(new_peer, "pre_configure_game", my_info.level)
diff --git a/scripts/matchmaking.gd b/scripts/matchmaking.gd
index d0d2653..a758313 100644
--- a/scripts/matchmaking.gd
+++ b/scripts/matchmaking.gd
@@ -2,7 +2,9 @@ extends Node
 
 var SERVER_TO_SERVER_PORT = 54671
 var MATCHMAKING_PORT = 54672
-var SERVER_SIZE = 6
+var GAME_SIZE = 6
+# Number of games we can make without blowing up the computer
+var MAX_GAMES = 50 # Totally random guess
 
 var next_port = 54673
 
@@ -19,15 +21,33 @@ var game_streams = []
 # Matchmaker to game servers
 var matchmaker_to_games
 
+onready var lobby = get_node("..")
+
 func _ready():
+	# By default, having this node doesn't do naything
+	# You must call run_matchmaker to enable it
+	# If not called, don't call _process (= don't matchmake)
 	set_process(false)
 
 func run_matchmaker():
+	# Actually run the matchmaker
+	set_process(true)
+
+	# Setup skirmish server
 	skirmish = spawn_server()
+
+	# 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")
-	set_process(true)
+
+	# 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)
@@ -39,21 +59,26 @@ func _process(delta):
 		game_streams.append(stream) # make new data transfer object for game
 		print("Server has requested connection")
 
-master func queue(info):
-	var netid = get_tree().get_rpc_sender_id()
-	rpc_id(netid, "join_game", skirmish)
-	skirmishing_players.push(netid)
+func queue(netid):
+	print("Player " + str(netid) + " connected.")
+	$"..".rpc_id(netid, "_client_init", skirmish)
+	skirmishing_players.append(netid)
 	check_queue()
 
+# # This is only for clients, but it's in here so we can rpc it easily
+# slave func join_game(port):
+# 	#
+
 func check_queue():
-	if skirmishing_players.size() >= SERVER_SIZE:
+	if skirmishing_players.size() >= GAME_SIZE:
 		var port = spawn_server()
 		games.push(port)
-		for p in skirmishing_players:
+		for i in range(GAME_SIZE):
+			var p = skirmishing_players[i]
 			rpc_id(p.netid, "join_game", port)
 
 func spawn_server():
-	OS.execute("util/server.sh", [], false)
+	OS.execute("util/server.sh", ['-port='+str(next_port)], false)
 	next_port += 1
 	return (next_port - 1) # Return original port
 
diff --git a/util/server.sh b/util/server.sh
index 9959782..d08c8bb 100644
--- a/util/server.sh
+++ b/util/server.sh
@@ -1,2 +1,2 @@
-godot-server -level=2 -silent -server -start-game
+godot-server -level=2 -silent -server -start-game "$@"
 

From 37cf9e2fcfeb2eaf39b74dd2e10eb013faa9fd53 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Wed, 9 May 2018 14:08:28 -0400
Subject: [PATCH 13/40] Fix syntax errors in check_queue

---
 scripts/matchmaking.gd | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/scripts/matchmaking.gd b/scripts/matchmaking.gd
index a758313..cd851af 100644
--- a/scripts/matchmaking.gd
+++ b/scripts/matchmaking.gd
@@ -61,7 +61,7 @@ func _process(delta):
 
 func queue(netid):
 	print("Player " + str(netid) + " connected.")
-	$"..".rpc_id(netid, "_client_init", skirmish)
+	add_to_game(netid, skirmish)
 	skirmishing_players.append(netid)
 	check_queue()
 
@@ -69,13 +69,17 @@ func queue(netid):
 # slave func join_game(port):
 # 	#
 
+func add_to_game(netid, port):
+	lobby.rpc_id(netid, "_client_init", port)
+
 func check_queue():
 	if skirmishing_players.size() >= GAME_SIZE:
 		var port = spawn_server()
-		games.push(port)
+		games.append(port)
 		for i in range(GAME_SIZE):
 			var p = skirmishing_players[i]
-			rpc_id(p.netid, "join_game", port)
+			print("Moving player " + str(p) + " to game " + str(port))
+			add_to_game(p, port)
 
 func spawn_server():
 	OS.execute("util/server.sh", ['-port='+str(next_port)], false)

From 955927569c1c7059085a2bd72a40e65ccd0bcbab Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Thu, 10 May 2018 14:20:04 -0400
Subject: [PATCH 14/40] [WIP] Network games, begin move players to game

The problem I just encountered is that I have to tell the players who
are currently skirmishing that they have a game. This means either:
 1. Telling the skirmish server to move them
 2. Keeping a connection with players
 3. Running the skirmish server on the matchmaker!
Three sounds nice because the matchmaker is sitting on this godot
instance, it might as well do something with it, right? But it's
essentially equivalent to 1, it just doesn't have to think about
networking because they're the same instance. So moving from 3 to 1 if I
realize it's a bad idea is easy, but 2 becomes pretty entrenched.

You're making some pretty big decisions here, take it slow.
---
 scripts/lobby.gd       | 10 ++++++++++
 scripts/matchmaking.gd | 34 ++++++++++++++++++++++++++--------
 2 files changed, 36 insertions(+), 8 deletions(-)

diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index 3c13183..e39ac1a 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -16,6 +16,8 @@ var is_connected = false # Technically this can be done with ENetcetera but it's
 
 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'))
@@ -127,6 +129,14 @@ func _server_init():
 	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()
diff --git a/scripts/matchmaking.gd b/scripts/matchmaking.gd
index cd851af..d5cd778 100644
--- a/scripts/matchmaking.gd
+++ b/scripts/matchmaking.gd
@@ -14,13 +14,17 @@ var skirmishing_players = []
 var skirmish
 # To avoid the confusion of the gameSERVERS being CLIENTS,
 # we just call them games whenever possible
-var games = []
+# 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():
@@ -57,7 +61,13 @@ func _process(delta):
 		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
-		print("Server has requested connection")
+	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.")
@@ -72,14 +82,22 @@ func queue(netid):
 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
+		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:
-		var port = spawn_server()
-		games.append(port)
-		for i in range(GAME_SIZE):
-			var p = skirmishing_players[i]
-			print("Moving player " + str(p) + " to game " + str(port))
-			add_to_game(p, port)
+		spawn_server()
+		# games.append(port)
 
 func spawn_server():
 	OS.execute("util/server.sh", ['-port='+str(next_port)], false)

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 15/40] [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):

From 04f75a8ca71f6ceecd6a6585a7f0cf948888edc5 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Thu, 10 May 2018 19:48:29 -0400
Subject: [PATCH 16/40] Fix all /errors/ on networking and custom_game

But not all bugs. Only errors. We're taking this a step at a time.
---
 scenes/custom_game.tscn |  26 +++----
 scripts/custom_game.gd  |   5 +-
 scripts/lobby.gd        |  11 ++-
 scripts/matchmaking.gd  |   4 +-
 scripts/menu.gd         |   2 +-
 scripts/networking.gd   | 197 ++++++++++++++++++++++++------------------------
 6 files changed, 128 insertions(+), 117 deletions(-)

diff --git a/scenes/custom_game.tscn b/scenes/custom_game.tscn
index 70b1d21..67599ff 100644
--- a/scenes/custom_game.tscn
+++ b/scenes/custom_game.tscn
@@ -70,10 +70,10 @@ 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
+margin_left = 73.0
+margin_top = 253.0
+margin_right = 366.0
+margin_bottom = 310.0
 rect_pivot_offset = Vector2( 0, 0 )
 focus_mode = 2
 mouse_filter = 0
@@ -94,10 +94,10 @@ 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
+margin_left = 74.0
+margin_top = 197.0
+margin_right = 364.0
+margin_bottom = 232.0
 rect_pivot_offset = Vector2( 0, 0 )
 focus_mode = 2
 mouse_filter = 0
@@ -123,8 +123,8 @@ anchor_right = 0.0
 anchor_bottom = 0.0
 margin_left = 588.0
 margin_top = 258.0
-margin_right = 812.0
-margin_bottom = 332.0
+margin_right = 890.0
+margin_bottom = 315.0
 rect_pivot_offset = Vector2( 0, 0 )
 focus_mode = 2
 mouse_filter = 0
@@ -166,9 +166,9 @@ 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
+margin_top = 203.0
+margin_right = 891.0
+margin_bottom = 232.0
 rect_pivot_offset = Vector2( 0, 0 )
 focus_mode = 2
 mouse_filter = 0
diff --git a/scripts/custom_game.gd b/scripts/custom_game.gd
index 6cda66e..4cacc5a 100644
--- a/scripts/custom_game.gd
+++ b/scripts/custom_game.gd
@@ -5,5 +5,6 @@ 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)
+	get_node("Server").connect("pressed", networking, "start_server")
+	get_node("Client").connect("pressed", networking, "start_client")
+
diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index 809c945..e3aa839 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -2,9 +2,7 @@ extends Control
 
 var port = null # Defined by command-line argument with default
 
-var player_info = {}
 var my_info = {}
-var begun = false
 var server_playing = true
 var global_server_ip = "nv.cosinegaming.com"
 var ip = null
@@ -73,3 +71,12 @@ func render_player_list():
 			list += "\n"
 		get_node("JoinedGameLobby/PlayerList").set_text(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()
+
diff --git a/scripts/matchmaking.gd b/scripts/matchmaking.gd
index 8e03394..f137556 100644
--- a/scripts/matchmaking.gd
+++ b/scripts/matchmaking.gd
@@ -29,11 +29,11 @@ onready var lobby = get_node("..")
 
 func _ready():
 	# By default, having this node doesn't do naything
-	# You must call run_matchmaker to enable it
+	# You must call start_matchmaker to enable it
 	# If not called, don't call _process (= don't matchmake)
 	set_process(false)
 
-func run_matchmaker():
+func start_matchmaker():
 	# Actually run the matchmaker
 	set_process(true)
 
diff --git a/scripts/menu.gd b/scripts/menu.gd
index e7dc6e9..303ae64 100644
--- a/scripts/menu.gd
+++ b/scripts/menu.gd
@@ -49,7 +49,7 @@ func _option_sel(button_name, option):
 	button.select(option)
 
 func _parse_args():
-	var o = setup_options()
+	var o = _set_up_options()
 	o.parse()
 
 	# if o.get_value("-silent"):
diff --git a/scripts/networking.gd b/scripts/networking.gd
index 5011cc7..d7e836b 100644
--- a/scripts/networking.gd
+++ b/scripts/networking.gd
@@ -1,132 +1,129 @@
 extends Node
 
-func connect_global_server():
-	ip = global_server_ip
-	_client_init()
+onready var matchmaking = preload("res://scripts/matchmaking.gd").new()
 
-slave func client_init(given_port=null):
-	collect_info()
+var players = []
+# TODO: Should we abstract server so variables like this aren't cluttering everything up?
+var players_done = []
+var begun = false
+# TODO: This needs to go. It carries nothing of value
+# ALL server negotiation should happen before ANY data is investigated (in lobby)
+var my_info = {}
+
+func _ready():
+	add_child(matchmaking)
+
+# func connect_global_server(): TODO
+# 	ip = global_server_ip
+# 	_client_init()
+
+func start_client(ip, port):
+	# collect_info() TODO
 	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
+	# if not ip: TODO
+	# 	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!")
+	# get_node("CustomGame/Client").set_text("Clienting!") TODO
 
-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 singleplayer_init(): TODO
+# 	# collect_info() TODO
+# 	var peer = NetworkedMultiplayerENet.new()
+# 	peer.create_server(port, 1)
+# 	get_tree().set_network_peer(peer)
+# 	players[1] = my_info
+# 	start_game()
 
-func server_init():
-	collect_info()
+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)
+
+func start_server(port):
+	# collect_info() TODO
 	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):
+	_connect_to_matchmaker(port)
+	# is_connected = true TODO
+	# get_node("CustomGame/Server").set_text("Serving!")
+	# get_node("JoinedGameLobby").show()
+	# if server_playing:
+	# 	players[1] = my_info
+	# if "start_game" in my_info and my_info.start_game: TODO
+	# 	start_game()
+
+# sync func start_game(): TODO
+	# my_info.level = get_node("CustomGame/LevelSelect").get_selected_id() TODO
+	# rpc("_pre_configure_game", my_info.level)
+
+func disconnect_player(id):
 	if get_tree().is_network_server():
 		rpc("unregister_player", id)
-	call_deferred("render_player_list")
+	# call_deferred("render_player_list") TODO
 
-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
+# func connect_player():
+	# 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() TODO
+	# is_connected = true
 
-remote func register_player(new_peer, info):
-	player_info[new_peer] = info
+remote func _register_player(new_peer, info):
+	players[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:
+		for old_peer in players:
 			# Send new player, old player's info
-			rpc_id(new_peer, "register_player", old_peer, player_info[old_peer])
+			rpc_id(new_peer, "_register_player", old_peer, players[old_peer])
 			if old_peer != new_peer:
 				# We need to assign team later, so count current
-				if player_info[old_peer].is_right_team:
+				if players[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)
+					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_id(old_peer, "_spawn_player", new_peer)
+					rpc_id(old_peer, "_begin_player_deferred", new_peer) # Spawning is deferred
+		# if not server_playing: TODO
+		# 	# We didn't catch this in players
+		# 	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 < players.size()
 		rpc("assign_team", new_peer, assign_right_team)
-		if not begun and player_info.size() == matchmaking.GAME_SIZE:
+		if not begun and players.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")
+			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)
+sync func _unregister_player(peer):
+	players.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
+sync func _spawn_player(p):
+	var 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 = player_info[p]
+	player.players = players[p]
 	get_node("/root/Level/Players").call_deferred("add_child", player)
 
-sync func pre_configure_game(level):
+sync func _pre_configure_game(level):
 	begun = true
 	my_info.level = level # Remember the level for future player registration
 
@@ -141,24 +138,30 @@ sync func pre_configure_game(level):
 	get_node("/root").add_child(world)
 
 	# Load all players (including self)
-	for p in player_info:
-		player_info[p].level = level
+	for p in players:
+		players[p].level = level
 		spawn_player(p)
 
 	rpc_id(1, "done_preconfiguring", self_peer_id)
 
-func begin_player(peer):
+sync func _done_preconfiguring(who):
+	players_done.append(who)
+	if players_done.size() == players.size():
+		# We call deferred in case singleplayer has placing the player in queue still
+		call_deferred("rpc", "post_configure_game")
+
+sync func _post_configure_game():
+	# Begin all players (including self)
+	for p in players:
+		_begin_player_deferred(p)
+
+func _begin_player(peer):
 	get_node("/root/Level/Players/%d" % peer).begin()
 
-remote func begin_player_deferred(peer):
-	call_deferred("begin_player", peer)
+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)
-

From d265fcde1bc519342fb68592e9aba4d4f2e649be Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sun, 13 May 2018 20:56:56 -0400
Subject: [PATCH 17/40] Add full functionality to custom_game (untested)

Can't test because I haven't added the "start game" button back yet.
---
 scripts/custom_game.gd | 15 +++++++++++++--
 scripts/menu.gd        | 24 +-----------------------
 scripts/networking.gd  |  5 -----
 scripts/util.gd        | 25 +++++++++++++++++++++++++
 4 files changed, 39 insertions(+), 30 deletions(-)

diff --git a/scripts/custom_game.gd b/scripts/custom_game.gd
index 4cacc5a..881d02d 100644
--- a/scripts/custom_game.gd
+++ b/scripts/custom_game.gd
@@ -5,6 +5,17 @@ onready var networking = preload("res://scripts/networking.gd").new()
 func _ready():
 	add_child(networking)
 
-	get_node("Server").connect("pressed", networking, "start_server")
-	get_node("Client").connect("pressed", networking, "start_client")
+	get_node("Server").connect("pressed", self, "_start_server")
+	get_node("Client").connect("pressed", self, "_start_client")
+
+func _start_server():
+	networking.start_server(_get_port())
+
+func _start_client():
+	var ip = get_node("IP").text
+	networking.start_client(ip, _get_port())
+
+func _get_port():
+	var port = util.args.get_value("-port")
+	return port
 
diff --git a/scripts/menu.gd b/scripts/menu.gd
index 303ae64..2e5f122 100644
--- a/scripts/menu.gd
+++ b/scripts/menu.gd
@@ -1,8 +1,7 @@
-extends "res://scripts/args.gd"
+extends Control
 
 func _ready():
 	randomize()
-	_parse_args()
 	_gui_setup()
 
 # GUI
@@ -23,23 +22,6 @@ func _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":
@@ -48,10 +30,6 @@ func _option_sel(button_name, option):
 		option = int(option)
 	button.select(option)
 
-func _parse_args():
-	var o = _set_up_options()
-	o.parse()
-
 	# if o.get_value("-silent"):
 	# 	server_playing = false
 	# if o.get_value("-hero"):
diff --git a/scripts/networking.gd b/scripts/networking.gd
index d7e836b..b43c4f3 100644
--- a/scripts/networking.gd
+++ b/scripts/networking.gd
@@ -20,11 +20,6 @@ func _ready():
 func start_client(ip, port):
 	# collect_info() TODO
 	var peer = NetworkedMultiplayerENet.new()
-	# if not ip: TODO
-	# 	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)
diff --git a/scripts/util.gd b/scripts/util.gd
index 3e29d0c..3d894df 100644
--- a/scripts/util.gd
+++ b/scripts/util.gd
@@ -2,6 +2,13 @@ extends Node
 
 var version = [0,0,0] # Semantic versioning: [0].[1].[2]
 
+var args
+
+onready var Options = preload("res://scripts/args.gd").new().Options
+
+func _ready():
+	args = _get_args()
+
 func get_master_player():
 	var path = "/root/Level/Players/%d" % get_tree().get_network_unique_id()
 	if has_node(path):
@@ -16,3 +23,21 @@ func is_friendly(player):
 	else:
 		return true # Doesn't matter, we're headless
 
+func _get_args():
+	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', 54673, '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")
+	opts.parse()
+	return opts
+

From f06eca96f9035d0649f2c729f1822c8aa68f1acb Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sun, 13 May 2018 21:13:38 -0400
Subject: [PATCH 18/40] Fix errors from lobby (not bugs / functionality)

---
 project.godot          |  2 +-
 scenes/lobby.tscn      | 50 +++++++++++++++++---------------------------------
 scripts/custom_game.gd |  5 +++++
 scripts/lobby.gd       | 16 ++++------------
 scripts/networking.gd  | 15 +++++++++------
 5 files changed, 36 insertions(+), 52 deletions(-)

diff --git a/project.godot b/project.godot
index 29645c9..9e4b16b 100644
--- a/project.godot
+++ b/project.godot
@@ -20,7 +20,7 @@ util="*res://scripts/util.gd"
 
 [display]
 
-window/stretch/mode="viewport"
+window/stretch/mode="2d"
 
 [input]
 
diff --git a/scenes/lobby.tscn b/scenes/lobby.tscn
index a1a6f2b..50ccad5 100644
--- a/scenes/lobby.tscn
+++ b/scenes/lobby.tscn
@@ -5,23 +5,23 @@
 [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="DynamicFont" id=5]
+[sub_resource type="DynamicFont" id=1]
 
 size = 30
 use_mipmaps = false
 use_filter = false
 font_data = ExtResource( 4 )
 
-[sub_resource type="DynamicFontData" id=3]
+[sub_resource type="DynamicFontData" id=2]
 
 font_path = "res://assets/DejaVuSansMono.ttf"
 
-[sub_resource type="DynamicFont" id=4]
+[sub_resource type="DynamicFont" id=3]
 
 size = 16
 use_mipmaps = false
 use_filter = false
-font_data = SubResource( 3 )
+font_data = SubResource( 2 )
 
 [node name="Lobby" type="Control"]
 
@@ -82,7 +82,7 @@ mouse_filter = 2
 mouse_default_cursor_shape = 0
 size_flags_horizontal = 1
 size_flags_vertical = 4
-custom_fonts/font = SubResource( 5 )
+custom_fonts/font = SubResource( 1 )
 text = "Select your hero"
 percent_visible = 1.0
 lines_skipped = 0
@@ -160,53 +160,37 @@ percent_visible = 1.0
 lines_skipped = 0
 max_lines_visible = -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 = 494.0
-margin_top = 128.0
-margin_right = 534.0
-margin_bottom = 168.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="PlayerList" type="Label" parent="Players" index="0"]
+[node name="PlayerList" type="Label" parent="." index="4"]
 
 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
+margin_left = 480.0
+margin_top = 130.0
+margin_right = 981.0
+margin_bottom = 408.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 )
+custom_fonts/font = SubResource( 3 )
 text = "Waiting for players to connect...."
 percent_visible = 1.0
 lines_skipped = 0
 max_lines_visible = -1
 
-[node name="StartGame" type="Button" parent="Players" index="1"]
+[node name="StartGame" type="Button" parent="." index="5"]
 
 anchor_left = 0.0
 anchor_top = 0.0
 anchor_right = 0.0
 anchor_bottom = 0.0
-margin_left = 2.0
-margin_top = 325.0
-margin_right = 124.0
-margin_bottom = 365.0
+margin_left = 496.0
+margin_top = 453.0
+margin_right = 618.0
+margin_bottom = 493.0
 rect_pivot_offset = Vector2( 0, 0 )
 focus_mode = 2
 mouse_filter = 0
@@ -221,7 +205,7 @@ text = "Ready!"
 flat = false
 align = 1
 
-[node name="VSeparator" type="VSeparator" parent="." index="5"]
+[node name="VSeparator" type="VSeparator" parent="." index="6"]
 
 anchor_left = 0.0
 anchor_top = 0.0
diff --git a/scripts/custom_game.gd b/scripts/custom_game.gd
index 881d02d..653ce27 100644
--- a/scripts/custom_game.gd
+++ b/scripts/custom_game.gd
@@ -10,10 +10,15 @@ func _ready():
 
 func _start_server():
 	networking.start_server(_get_port())
+	_show_lobby()
 
 func _start_client():
 	var ip = get_node("IP").text
 	networking.start_client(ip, _get_port())
+	_show_lobby()
+
+func _show_lobby():
+	get_tree().change_scene("res://scenes/lobby.tscn")
 
 func _get_port():
 	var port = util.args.get_value("-port")
diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index e3aa839..5056288 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -2,6 +2,7 @@ extends Control
 
 var port = null # Defined by command-line argument with default
 
+var player_info = []
 var my_info = {}
 var server_playing = true
 var global_server_ip = "nv.cosinegaming.com"
@@ -17,18 +18,9 @@ var matchmaker_tcp
 func _ready():
 	add_child(matchmaking)
 
-	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")
-
-	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_node("Username").connect("text_changed", self, "resend_name")
+	get_node("StartGame").connect("pressed", self, "start_game")
+	# get_node("CustomGame/LevelSelect").connect("item_selected", self, "select_level") TODO
 
 func collect_info():
 	if not "username" in my_info:
diff --git a/scripts/networking.gd b/scripts/networking.gd
index b43c4f3..2abab75 100644
--- a/scripts/networking.gd
+++ b/scripts/networking.gd
@@ -13,6 +13,10 @@ var my_info = {}
 func _ready():
 	add_child(matchmaking)
 
+	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(): TODO
 # 	ip = global_server_ip
 # 	_client_init()
@@ -66,12 +70,11 @@ func disconnect_player(id):
 		rpc("unregister_player", id)
 	# call_deferred("render_player_list") TODO
 
-# func connect_player():
-	# 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() TODO
-	# is_connected = true
+func connect_player():
+	rpc("_register_player", get_tree().get_network_unique_id(), {})
+	if util.args.get_value("-start-game"):
+		rpc_id(1, "start_game")
+	# is_connected = true TODO
 
 remote func _register_player(new_peer, info):
 	players[new_peer] = info

From 8d7e4e23520e45cd48e80349e6f7ac8a610142ab Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sun, 13 May 2018 21:31:55 -0400
Subject: [PATCH 19/40] Move level select to lobby, [WIP] fix start_game

---
 scenes/custom_game.tscn | 41 ++++++-------------------------
 scenes/lobby.tscn       | 64 ++++++++++++++++++++++++++++++++++++++++++-------
 scripts/lobby.gd        | 11 ++++++++-
 scripts/networking.gd   | 21 ++++++++--------
 4 files changed, 83 insertions(+), 54 deletions(-)

diff --git a/scenes/custom_game.tscn b/scenes/custom_game.tscn
index 67599ff..799f501 100644
--- a/scenes/custom_game.tscn
+++ b/scenes/custom_game.tscn
@@ -71,9 +71,9 @@ anchor_top = 0.0
 anchor_right = 0.0
 anchor_bottom = 0.0
 margin_left = 73.0
-margin_top = 253.0
+margin_top = 195.0
 margin_right = 366.0
-margin_bottom = 310.0
+margin_bottom = 252.0
 rect_pivot_offset = Vector2( 0, 0 )
 focus_mode = 2
 mouse_filter = 0
@@ -88,34 +88,7 @@ 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 = 74.0
-margin_top = 197.0
-margin_right = 364.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
-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"]
+[node name="Client" type="Button" parent="." index="3"]
 
 anchor_left = 0.0
 anchor_top = 0.0
@@ -139,7 +112,7 @@ text = "Join Game"
 flat = false
 align = 1
 
-[node name="IPLabel" type="Label" parent="." index="5"]
+[node name="IPLabel" type="Label" parent="." index="4"]
 
 anchor_left = 0.0
 anchor_top = 0.0
@@ -159,7 +132,7 @@ percent_visible = 1.0
 lines_skipped = 0
 max_lines_visible = -1
 
-[node name="IP" type="TextEdit" parent="." index="6"]
+[node name="IP" type="TextEdit" parent="." index="5"]
 
 anchor_left = 0.0
 anchor_top = 0.0
@@ -192,7 +165,7 @@ caret_blink = false
 caret_blink_speed = 0.65
 caret_moving_by_right_click = true
 
-[node name="Label2" type="Label" parent="." index="7"]
+[node name="Label2" type="Label" parent="." index="6"]
 
 anchor_left = 0.0
 anchor_top = 0.0
@@ -212,7 +185,7 @@ percent_visible = 1.0
 lines_skipped = 0
 max_lines_visible = -1
 
-[node name="Label3" type="Label" parent="." index="8"]
+[node name="Label3" type="Label" parent="." index="7"]
 
 anchor_left = 0.0
 anchor_top = 0.0
diff --git a/scenes/lobby.tscn b/scenes/lobby.tscn
index 50ccad5..1399c46 100644
--- a/scenes/lobby.tscn
+++ b/scenes/lobby.tscn
@@ -42,10 +42,10 @@ script = ExtResource( 2 )
 [node name="HeroSelect" parent="." index="0" instance=ExtResource( 3 )]
 
 editor/display_folded = true
-margin_left = 28.0
-margin_top = 98.0
-margin_right = 28.0
-margin_bottom = 98.0
+margin_left = 30.0
+margin_top = 69.0
+margin_right = 30.0
+margin_bottom = 69.0
 color = Color( 0.097229, 0.104696, 0.105469, 0 )
 
 [node name="Hero" parent="HeroSelect" index="0"]
@@ -94,10 +94,10 @@ anchor_left = 0.0
 anchor_top = 0.0
 anchor_right = 0.0
 anchor_bottom = 0.0
-margin_left = 42.0
-margin_top = 423.0
-margin_right = 383.0
-margin_bottom = 456.0
+margin_left = 44.0
+margin_top = 394.0
+margin_right = 385.0
+margin_bottom = 427.0
 rect_pivot_offset = Vector2( 0, 0 )
 focus_mode = 2
 mouse_filter = 0
@@ -221,5 +221,53 @@ mouse_default_cursor_shape = 0
 size_flags_horizontal = 1
 size_flags_vertical = 1
 
+[node name="LevelSelect" type="OptionButton" parent="." index="7"]
+
+visible = false
+anchor_left = 0.0
+anchor_top = 0.0
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_left = 45.0
+margin_top = 486.0
+margin_right = 411.0
+margin_bottom = 527.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="Label" type="Label" parent="LevelSelect" index="1"]
+
+anchor_left = 0.0
+anchor_top = 0.0
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_left = -5.0
+margin_top = -37.0
+margin_right = 39.0
+margin_bottom = -15.0
+rect_pivot_offset = Vector2( 0, 0 )
+mouse_filter = 2
+mouse_default_cursor_shape = 0
+size_flags_horizontal = 1
+size_flags_vertical = 4
+text = "Map:"
+percent_visible = 1.0
+lines_skipped = 0
+max_lines_visible = -1
+
 
 [editable path="HeroSelect"]
diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index 5056288..7feb999 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -11,15 +11,20 @@ var players_done = []
 var is_connected = false # Technically this can be done with ENetcetera but it's easier this way
 
 onready var matchmaking = preload("res://scripts/matchmaking.gd").new()
+onready var networking = preload("res://scripts/networking.gd").new()
 
 var matchmaker_tcp
 
 
 func _ready():
 	add_child(matchmaking)
+	add_child(networking)
+
+	if get_tree().is_network_server():
+		get_node("LevelSelect").show()
 
 	get_node("Username").connect("text_changed", self, "resend_name")
-	get_node("StartGame").connect("pressed", self, "start_game")
+	get_node("StartGame").connect("pressed", self, "_start_game")
 	# get_node("CustomGame/LevelSelect").connect("item_selected", self, "select_level") TODO
 
 func collect_info():
@@ -72,3 +77,7 @@ sync func assign_team(peer, is_right_team):
 			get_node("PlayerSettings/Team").set_text("Left Team")
 	render_player_list()
 
+func _start_game():
+	var level = 2 # TODO
+	networking.rpc_id(1, "start_game", level)
+
diff --git a/scripts/networking.gd b/scripts/networking.gd
index 2abab75..d5bb9fd 100644
--- a/scripts/networking.gd
+++ b/scripts/networking.gd
@@ -2,7 +2,7 @@ extends Node
 
 onready var matchmaking = preload("res://scripts/matchmaking.gd").new()
 
-var players = []
+var players = {}
 # TODO: Should we abstract server so variables like this aren't cluttering everything up?
 var players_done = []
 var begun = false
@@ -13,9 +13,9 @@ var my_info = {}
 func _ready():
 	add_child(matchmaking)
 
-	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("network_peer_connected", self, "connect_player")
+	get_tree().connect("network_peer_disconnected", self, "disconnect_player")
+	get_tree().connect("connected_to_server", self, "_on_connect")
 
 # func connect_global_server(): TODO
 # 	ip = global_server_ip
@@ -61,24 +61,23 @@ func start_server(port):
 	# if "start_game" in my_info and my_info.start_game: TODO
 	# 	start_game()
 
-# sync func start_game(): TODO
-	# my_info.level = get_node("CustomGame/LevelSelect").get_selected_id() TODO
-	# rpc("_pre_configure_game", my_info.level)
+sync func start_game(level):
+	rpc("_pre_configure_game", level)
 
 func disconnect_player(id):
 	if get_tree().is_network_server():
 		rpc("unregister_player", id)
 	# call_deferred("render_player_list") TODO
 
-func connect_player():
-	rpc("_register_player", get_tree().get_network_unique_id(), {})
+func _on_connect():
+	rpc("_register_player", get_tree().get_network_unique_id(), my_info)
 	if util.args.get_value("-start-game"):
 		rpc_id(1, "start_game")
 	# is_connected = true TODO
 
 remote func _register_player(new_peer, info):
 	players[new_peer] = info
-	render_player_list()
+	# render_player_list() TODO
 	if (get_tree().is_network_server()):
 		var right_team_count = 0
 		# Send current players' info to new player
@@ -138,7 +137,7 @@ sync func _pre_configure_game(level):
 	# Load all players (including self)
 	for p in players:
 		players[p].level = level
-		spawn_player(p)
+		_spawn_player(p)
 
 	rpc_id(1, "done_preconfiguring", self_peer_id)
 

From 5ffb7541850e9e89521486657d24e9f563da492d Mon Sep 17 00:00:00 2001
From: Luna <lucidlyluna@gmail.com>
Date: Tue, 15 May 2018 10:34:32 -0400
Subject: [PATCH 20/40] Make start_game work for the first time!

---
 scripts/lobby.gd      | 52 +++++++++++++++++++++++++++------------------------
 scripts/networking.gd |  2 +-
 2 files changed, 29 insertions(+), 25 deletions(-)

diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index 7feb999..f126de1 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -2,8 +2,7 @@ extends Control
 
 var port = null # Defined by command-line argument with default
 
-var player_info = []
-var my_info = {}
+# var my_info = {}
 var server_playing = true
 var global_server_ip = "nv.cosinegaming.com"
 var ip = null
@@ -23,45 +22,49 @@ func _ready():
 	if get_tree().is_network_server():
 		get_node("LevelSelect").show()
 
-	get_node("Username").connect("text_changed", self, "resend_name")
+	get_node("Username").connect("text_changed", self, "_send_name")
 	get_node("StartGame").connect("pressed", self, "_start_game")
 	# get_node("CustomGame/LevelSelect").connect("item_selected", self, "select_level") TODO
+	_send_name()
 
-func collect_info():
+func _collect_info():
+	var my_id = get_tree().get_network_unique_id()
+	var my_info = networking.players[my_id]
 	if not "username" in my_info:
-		my_info.username = get_node("PlayerSettings/Username").get_text()
+		my_info.username = get_node("Username").get_text()
 	if not "hero" in my_info:
-		my_info.hero = get_node("PlayerSettings/HeroSelect").get_selected_id()
+		my_info.hero = get_node("HeroSelect/Hero").get_selected_id()
 	if not "is_right_team" in my_info:
 		my_info.is_right_team = false # Server assigns team, wait for that
 
 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)
+	var description = get_node("HeroSelect").hero_text[hero]
+	get_node("HeroDescription").set_text(description)
+	var my_id = get_tree().get_network_unique_id()
+	networking.players[my_id].hero = hero
+	rpc("set_hero", get_tree().get_network_unique_id(), hero)
 
 sync func set_hero(peer, hero):
-	player_info[peer].hero = hero
+	networking.players[peer].hero = hero
 	render_player_list()
 
-func resend_name():
+func _send_name():
 	if is_connected:
-		var name = get_node("PlayerSettings/Username").get_text()
-		rpc("set_name", get_tree().get_network_unique_id(), name)
+		var name = get_node("Username").text
+		rpc("_set_name", get_tree().get_network_unique_id(), name)
 
-sync func set_name(peer, name):
-	player_info[peer].username = name
+sync func _set_name(peer, name):
+	networking.players[peer].username = name
 	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:
+		var hero_names = get_node("HeroSelect").hero_names
+		for p in networking.players:
+			list += "%-15s" % networking.players[p].username
+			list += "%-20s" % hero_names[networking.players[p].hero]
+			if networking.players[p].is_right_team:
 				list += "Right Team"
 			else:
 				list += "Left Team"
@@ -69,15 +72,16 @@ func render_player_list():
 		get_node("JoinedGameLobby/PlayerList").set_text(list)
 
 sync func assign_team(peer, is_right_team):
-	player_info[peer].is_right_team = is_right_team
+	networking.players[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")
+			get_node("Team").set_text("Right Team")
 		else:
-			get_node("PlayerSettings/Team").set_text("Left Team")
+			get_node("Team").set_text("Left Team")
 	render_player_list()
 
 func _start_game():
+	_collect_info()
 	var level = 2 # TODO
 	networking.rpc_id(1, "start_game", level)
 
diff --git a/scripts/networking.gd b/scripts/networking.gd
index d5bb9fd..f06bb4a 100644
--- a/scripts/networking.gd
+++ b/scripts/networking.gd
@@ -117,7 +117,7 @@ sync func _spawn_player(p):
 	var player = load("res://scenes/heroes/" + str(hero) + ".tscn").instance()
 	player.set_name(str(p))
 	player.set_network_master(p)
-	player.players = players[p]
+	player.player_info = players[p]
 	get_node("/root/Level/Players").call_deferred("add_child", player)
 
 sync func _pre_configure_game(level):

From a2dabbc37aa72efaebed95186eeda280efd340af Mon Sep 17 00:00:00 2001
From: Luna <lucidlyluna@gmail.com>
Date: Tue, 15 May 2018 23:45:19 -0400
Subject: [PATCH 21/40] [WIP] Figure out how the hell I'm gonna do this

---
 project.godot          |  5 +--
 scripts/custom_game.gd |  7 ++---
 scripts/lobby.gd       | 12 ++++++--
 scripts/menu.gd        | 31 +++++++++++--------
 scripts/networking.gd  | 82 +++++++++++++++++++++++++-------------------------
 5 files changed, 71 insertions(+), 66 deletions(-)

diff --git a/project.godot b/project.godot
index 9e4b16b..7ba129f 100644
--- a/project.godot
+++ b/project.godot
@@ -17,10 +17,7 @@ config/icon="res://icon.png"
 [autoload]
 
 util="*res://scripts/util.gd"
-
-[display]
-
-window/stretch/mode="2d"
+networking="*res://scripts/networking.gd"
 
 [input]
 
diff --git a/scripts/custom_game.gd b/scripts/custom_game.gd
index 653ce27..57cce07 100644
--- a/scripts/custom_game.gd
+++ b/scripts/custom_game.gd
@@ -1,15 +1,12 @@
 extends Control
 
-onready var networking = preload("res://scripts/networking.gd").new()
-
 func _ready():
-	add_child(networking)
-
 	get_node("Server").connect("pressed", self, "_start_server")
 	get_node("Client").connect("pressed", self, "_start_client")
 
 func _start_server():
-	networking.start_server(_get_port())
+	# Custom Game can assume we're playing as well
+	networking.start_server(_get_port(), true)
 	_show_lobby()
 
 func _start_client():
diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index f126de1..906b5f4 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -3,30 +3,36 @@ extends Control
 var port = null # Defined by command-line argument with default
 
 # var my_info = {}
-var server_playing = true
+remote var players = {}
 var global_server_ip = "nv.cosinegaming.com"
 var ip = null
 var players_done = []
 var is_connected = false # Technically this can be done with ENetcetera but it's easier this way
 
 onready var matchmaking = preload("res://scripts/matchmaking.gd").new()
-onready var networking = preload("res://scripts/networking.gd").new()
 
 var matchmaker_tcp
 
+var right_team_next = false
 
 func _ready():
 	add_child(matchmaking)
-	add_child(networking)
 
 	if get_tree().is_network_server():
 		get_node("LevelSelect").show()
 
+	get_tree().connect("network_peer_connected", self, "_register_player")
+
 	get_node("Username").connect("text_changed", self, "_send_name")
 	get_node("StartGame").connect("pressed", self, "_start_game")
 	# get_node("CustomGame/LevelSelect").connect("item_selected", self, "select_level") TODO
 	_send_name()
 
+func _register_player(peer):
+	players[peer] = {}
+	if is_network_server():
+		rset(peer, "players", players)
+
 func _collect_info():
 	var my_id = get_tree().get_network_unique_id()
 	var my_info = networking.players[my_id]
diff --git a/scripts/menu.gd b/scripts/menu.gd
index 2e5f122..578da9f 100644
--- a/scripts/menu.gd
+++ b/scripts/menu.gd
@@ -3,6 +3,7 @@ extends Control
 func _ready():
 	randomize()
 	_gui_setup()
+	_arg_actions()
 
 # GUI
 
@@ -30,6 +31,8 @@ func _option_sel(button_name, option):
 		option = int(option)
 	button.select(option)
 
+func _arg_actions():
+	var o = util.args
 	# if o.get_value("-silent"):
 	# 	server_playing = false
 	# if o.get_value("-hero"):
@@ -39,23 +42,25 @@ func _option_sel(button_name, option):
 	# 	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()
+	if o.get_value("-server"):
+		networking.call_deferred("start_server")
+		get_tree().change_scene("res://scenes/lobby.tscn")
+	# if o.get_value("-matchmaker"):
+	# 	call_deferred("_matchmaker_init")
+	if o.get_value("-client"):
+		networking.call_deferred("start_client")
+		get_tree().change_scene("res://scenes/lobby.tscn")
+	if o.get_value("-start-game"):
+		networking.call_deferred("start_game")
+	# if o.get_value("-singleplayer"):
+	# 	networking.call_deferred("start_singleplayer")
+	if o.get_value('-h'):
+		o.print_help()
+		quit()
 
diff --git a/scripts/networking.gd b/scripts/networking.gd
index f06bb4a..28bf2a8 100644
--- a/scripts/networking.gd
+++ b/scripts/networking.gd
@@ -2,20 +2,23 @@ extends Node
 
 onready var matchmaking = preload("res://scripts/matchmaking.gd").new()
 
-var players = {}
+remote var players = []
 # TODO: Should we abstract server so variables like this aren't cluttering everything up?
 var players_done = []
 var begun = false
 # TODO: This needs to go. It carries nothing of value
 # ALL server negotiation should happen before ANY data is investigated (in lobby)
-var my_info = {}
+var my_info = {
+	hero: 0,
+	username: "Nickname",
+}
 
 func _ready():
 	add_child(matchmaking)
 
-	# get_tree().connect("network_peer_connected", self, "connect_player")
+	get_tree().connect("network_peer_connected", self, "_register_player")
 	get_tree().connect("network_peer_disconnected", self, "disconnect_player")
-	get_tree().connect("connected_to_server", self, "_on_connect")
+	# get_tree().connect("connected_to_server", self, "_on_connect")
 
 # func connect_global_server(): TODO
 # 	ip = global_server_ip
@@ -45,7 +48,7 @@ func _connect_to_matchmaker(game_port):
 	matchmaker_tcp.put_var(matchmaking.messages.ready_to_connect)
 	matchmaker_tcp.put_var(game_port)
 
-func start_server(port):
+func start_server(port, server_playing=false):
 	# collect_info() TODO
 	var peer = NetworkedMultiplayerENet.new()
 	print("Starting server on port " + str(port))
@@ -53,15 +56,16 @@ func start_server(port):
 	get_tree().set_network_peer(peer)
 	# As soon as we're listening, let the matchmaker know
 	_connect_to_matchmaker(port)
+	if server_playing:
+		players[1] = my_info
 	# is_connected = true TODO
 	# get_node("CustomGame/Server").set_text("Serving!")
 	# get_node("JoinedGameLobby").show()
-	# if server_playing:
-	# 	players[1] = my_info
 	# if "start_game" in my_info and my_info.start_game: TODO
 	# 	start_game()
 
 sync func start_game(level):
+	print(var2str(players))
 	rpc("_pre_configure_game", level)
 
 func disconnect_player(id):
@@ -69,51 +73,45 @@ func disconnect_player(id):
 		rpc("unregister_player", id)
 	# call_deferred("render_player_list") TODO
 
-func _on_connect():
-	rpc("_register_player", get_tree().get_network_unique_id(), my_info)
-	if util.args.get_value("-start-game"):
-		rpc_id(1, "start_game")
+# func _on_connect():
+# 	rpc("_register_player", get_tree().get_network_unique_id(), my_info)
+# 	if util.args.get_value("-start-game"):
+# 		rpc_id(1, "start_game")
 	# is_connected = true TODO
 
-remote func _register_player(new_peer, info):
-	players[new_peer] = info
+remote func _register_player(new_peer):
+	players.push(new_peer)
+	if get_tree().is_network_server():
+		# I tell new player about all the existing people
+		rset_id(new_peer, "players", players)
 	# render_player_list() TODO
-	if (get_tree().is_network_server()):
-		var right_team_count = 0
+		# var right_team_count = 0
 		# Send current players' info to new player
-		for old_peer in players:
 			# Send new player, old player's info
-			rpc_id(new_peer, "_register_player", old_peer, players[old_peer])
-			if old_peer != new_peer:
-				# We need to assign team later, so count current
-				if players[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: TODO
-		# 	# We didn't catch this in players
-		# 	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 < players.size()
-		rpc("assign_team", new_peer, assign_right_team)
-		if not begun and players.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")
+			# rpc_id(new_peer, "_register_player", old_peer, players[old_peer])
+			# if old_peer != new_peer:
+			# 	# We need to assign team later, so count current
+			# 	if players[old_peer].is_right_team:
+			# 		right_team_count += 1
+				# if begun: TODO this should belong to lobby
+				# 	rpc_id(old_peer, "_spawn_player", new_peer)
+				# 	rpc_id(old_peer, "_begin_player_deferred", new_peer) # Spawning is deferred
+		# var assign_right_team = right_team_count * 2 < players.size()
+		# rpc("assign_team", new_peer, assign_right_team)
+		# if not begun and players.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):
 	players.erase(peer)
 	get_node("/root/Level/Players/%d" % peer).queue_free()
 
 sync func _spawn_player(p):
-	var hero = players[p].hero
+	var hero = 0
+	if players[p].has("hero"): # TODO: Rethink how we do this whole shenanigan
+		hero = players[p].hero
 	var player = load("res://scenes/heroes/" + str(hero) + ".tscn").instance()
 	player.set_name(str(p))
 	player.set_network_master(p)
@@ -121,6 +119,7 @@ sync func _spawn_player(p):
 	get_node("/root/Level/Players").call_deferred("add_child", player)
 
 sync func _pre_configure_game(level):
+	level = 2 # TODO: Remove this!!
 	begun = true
 	my_info.level = level # Remember the level for future player registration
 
@@ -133,6 +132,7 @@ sync func _pre_configure_game(level):
 
 	var world = load("res://scenes/levels/%d.tscn" % level).instance()
 	get_node("/root").add_child(world)
+	print("added level!")
 
 	# Load all players (including self)
 	for p in players:

From 1868d03be0486b0eb4ff930466f8ef0e0e7b2ffb Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Fri, 18 May 2018 19:02:32 -0400
Subject: [PATCH 22/40] [WIP] Add spectator button

---
 scenes/lobby.tscn     | 139 +++++++++++++++++++++++++++++---------------------
 scripts/lobby.gd      |  32 +++---------
 scripts/networking.gd |  16 +++---
 3 files changed, 96 insertions(+), 91 deletions(-)

diff --git a/scenes/lobby.tscn b/scenes/lobby.tscn
index 1399c46..9eb171b 100644
--- a/scenes/lobby.tscn
+++ b/scenes/lobby.tscn
@@ -23,7 +23,7 @@ use_mipmaps = false
 use_filter = false
 font_data = SubResource( 2 )
 
-[node name="Lobby" type="Control"]
+[node name="Lobby" type="Control" index="0"]
 
 anchor_left = 0.0
 anchor_top = 0.0
@@ -94,10 +94,10 @@ anchor_left = 0.0
 anchor_top = 0.0
 anchor_right = 0.0
 anchor_bottom = 0.0
-margin_left = 44.0
-margin_top = 394.0
-margin_right = 385.0
-margin_bottom = 427.0
+margin_left = 43.0
+margin_top = 348.0
+margin_right = 384.0
+margin_bottom = 381.0
 rect_pivot_offset = Vector2( 0, 0 )
 focus_mode = 2
 mouse_filter = 0
@@ -121,7 +121,78 @@ caret_blink = false
 caret_blink_speed = 0.65
 caret_moving_by_right_click = true
 
-[node name="TeamLabel" type="Label" parent="." index="2"]
+[node name="Spectating" type="CheckButton" parent="." index="2"]
+
+anchor_left = 0.0
+anchor_top = 0.0
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_left = 36.0
+margin_top = 396.0
+margin_right = 226.0
+margin_bottom = 436.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 = true
+enabled_focus_mode = 2
+shortcut = null
+group = null
+text = "Spectating"
+flat = false
+align = 0
+
+[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 = 45.0
+margin_top = 486.0
+margin_right = 411.0
+margin_bottom = 527.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="Label" type="Label" parent="LevelSelect" index="1"]
+
+anchor_left = 0.0
+anchor_top = 0.0
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_left = -5.0
+margin_top = -37.0
+margin_right = 39.0
+margin_bottom = -15.0
+rect_pivot_offset = Vector2( 0, 0 )
+mouse_filter = 2
+mouse_default_cursor_shape = 0
+size_flags_horizontal = 1
+size_flags_vertical = 4
+text = "Map:"
+percent_visible = 1.0
+lines_skipped = 0
+max_lines_visible = -1
+
+[node name="TeamLabel" type="Label" parent="." index="4"]
 
 anchor_left = 0.0
 anchor_top = 0.0
@@ -141,7 +212,7 @@ percent_visible = 1.0
 lines_skipped = 0
 max_lines_visible = -1
 
-[node name="Team" type="Label" parent="." index="3"]
+[node name="Team" type="Label" parent="." index="5"]
 
 anchor_left = 0.0
 anchor_top = 0.0
@@ -160,7 +231,7 @@ percent_visible = 1.0
 lines_skipped = 0
 max_lines_visible = -1
 
-[node name="PlayerList" type="Label" parent="." index="4"]
+[node name="PlayerList" type="Label" parent="." index="6"]
 
 anchor_left = 0.5
 anchor_top = 0.5
@@ -181,7 +252,7 @@ percent_visible = 1.0
 lines_skipped = 0
 max_lines_visible = -1
 
-[node name="StartGame" type="Button" parent="." index="5"]
+[node name="StartGame" type="Button" parent="." index="7"]
 
 anchor_left = 0.0
 anchor_top = 0.0
@@ -205,7 +276,7 @@ text = "Ready!"
 flat = false
 align = 1
 
-[node name="VSeparator" type="VSeparator" parent="." index="6"]
+[node name="VSeparator" type="VSeparator" parent="." index="8"]
 
 anchor_left = 0.0
 anchor_top = 0.0
@@ -221,53 +292,5 @@ mouse_default_cursor_shape = 0
 size_flags_horizontal = 1
 size_flags_vertical = 1
 
-[node name="LevelSelect" type="OptionButton" parent="." index="7"]
-
-visible = false
-anchor_left = 0.0
-anchor_top = 0.0
-anchor_right = 0.0
-anchor_bottom = 0.0
-margin_left = 45.0
-margin_top = 486.0
-margin_right = 411.0
-margin_bottom = 527.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="Label" type="Label" parent="LevelSelect" index="1"]
-
-anchor_left = 0.0
-anchor_top = 0.0
-anchor_right = 0.0
-anchor_bottom = 0.0
-margin_left = -5.0
-margin_top = -37.0
-margin_right = 39.0
-margin_bottom = -15.0
-rect_pivot_offset = Vector2( 0, 0 )
-mouse_filter = 2
-mouse_default_cursor_shape = 0
-size_flags_horizontal = 1
-size_flags_vertical = 4
-text = "Map:"
-percent_visible = 1.0
-lines_skipped = 0
-max_lines_visible = -1
-
 
 [editable path="HeroSelect"]
diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index 906b5f4..f183605 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -2,36 +2,17 @@ extends Control
 
 var port = null # Defined by command-line argument with default
 
-# var my_info = {}
-remote var players = {}
-var global_server_ip = "nv.cosinegaming.com"
-var ip = null
-var players_done = []
-var is_connected = false # Technically this can be done with ENetcetera but it's easier this way
-
-onready var matchmaking = preload("res://scripts/matchmaking.gd").new()
-
-var matchmaker_tcp
-
-var right_team_next = false
-
 func _ready():
-	add_child(matchmaking)
-
 	if get_tree().is_network_server():
 		get_node("LevelSelect").show()
 
-	get_tree().connect("network_peer_connected", self, "_register_player")
-
 	get_node("Username").connect("text_changed", self, "_send_name")
 	get_node("StartGame").connect("pressed", self, "_start_game")
-	# get_node("CustomGame/LevelSelect").connect("item_selected", self, "select_level") TODO
-	_send_name()
 
-func _register_player(peer):
-	players[peer] = {}
-	if is_network_server():
-		rset(peer, "players", players)
+	get_node("Spectating").pressed = util.args.get_value("-silent")
+	get_node("Spectating").connect("pressed", self, "_change_spectating") # TODO
+	# get_node("CustomGame/LevelSelect").connect("item_selected", self, "select_level") TODO
+	# _send_name()
 
 func _collect_info():
 	var my_id = get_tree().get_network_unique_id()
@@ -55,9 +36,8 @@ sync func set_hero(peer, hero):
 	render_player_list()
 
 func _send_name():
-	if is_connected:
-		var name = get_node("Username").text
-		rpc("_set_name", get_tree().get_network_unique_id(), name)
+	var name = get_node("Username").text
+	rpc("_set_name", get_tree().get_network_unique_id(), name)
 
 sync func _set_name(peer, name):
 	networking.players[peer].username = name
diff --git a/scripts/networking.gd b/scripts/networking.gd
index 28bf2a8..068e596 100644
--- a/scripts/networking.gd
+++ b/scripts/networking.gd
@@ -2,16 +2,20 @@ extends Node
 
 onready var matchmaking = preload("res://scripts/matchmaking.gd").new()
 
-remote var players = []
-# TODO: Should we abstract server so variables like this aren't cluttering everything up?
+remote var players = {}
 var players_done = []
+var is_connected = false # Technically this can be done with ENetcetera but it's easier this way
+# TODO: Should we abstract server so variables like this aren't cluttering everything up?
 var begun = false
 # TODO: This needs to go. It carries nothing of value
 # ALL server negotiation should happen before ANY data is investigated (in lobby)
 var my_info = {
-	hero: 0,
-	username: "Nickname",
+	"hero": 0,
+	"username": "Nickname",
 }
+var global_server_ip = "nv.cosinegaming.com"
+var matchmaker_tcp
+var right_team_next = false
 
 func _ready():
 	add_child(matchmaking)
@@ -56,8 +60,6 @@ func start_server(port, server_playing=false):
 	get_tree().set_network_peer(peer)
 	# As soon as we're listening, let the matchmaker know
 	_connect_to_matchmaker(port)
-	if server_playing:
-		players[1] = my_info
 	# is_connected = true TODO
 	# get_node("CustomGame/Server").set_text("Serving!")
 	# get_node("JoinedGameLobby").show()
@@ -80,7 +82,7 @@ func disconnect_player(id):
 	# is_connected = true TODO
 
 remote func _register_player(new_peer):
-	players.push(new_peer)
+	players[new_peer] = {}
 	if get_tree().is_network_server():
 		# I tell new player about all the existing people
 		rset_id(new_peer, "players", players)

From 10d760bbf43c77c7db60242336b3688247efc0a5 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Fri, 18 May 2018 19:19:14 -0400
Subject: [PATCH 23/40] Fix server not playing when not spectating

---
 scenes/lobby.tscn     |  2 +-
 scripts/lobby.gd      |  6 ++++--
 scripts/networking.gd | 31 +++++++++++++++++++++++--------
 3 files changed, 28 insertions(+), 11 deletions(-)

diff --git a/scenes/lobby.tscn b/scenes/lobby.tscn
index 9eb171b..10f49eb 100644
--- a/scenes/lobby.tscn
+++ b/scenes/lobby.tscn
@@ -23,7 +23,7 @@ use_mipmaps = false
 use_filter = false
 font_data = SubResource( 2 )
 
-[node name="Lobby" type="Control" index="0"]
+[node name="Lobby" type="Control"]
 
 anchor_left = 0.0
 anchor_top = 0.0
diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index f183605..ec59d82 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -9,8 +9,10 @@ func _ready():
 	get_node("Username").connect("text_changed", self, "_send_name")
 	get_node("StartGame").connect("pressed", self, "_start_game")
 
-	get_node("Spectating").pressed = util.args.get_value("-silent")
-	get_node("Spectating").connect("pressed", self, "_change_spectating") # TODO
+	var spectating = util.args.get_value("-silent")
+	get_node("Spectating").pressed = spectating
+	# 
+	get_node("Spectating").connect("toggled", networking, "set_spectating") # TODO
 	# get_node("CustomGame/LevelSelect").connect("item_selected", self, "select_level") TODO
 	# _send_name()
 
diff --git a/scripts/networking.gd b/scripts/networking.gd
index 068e596..8cb125c 100644
--- a/scripts/networking.gd
+++ b/scripts/networking.gd
@@ -20,8 +20,8 @@ var right_team_next = false
 func _ready():
 	add_child(matchmaking)
 
-	get_tree().connect("network_peer_connected", self, "_register_player")
-	get_tree().connect("network_peer_disconnected", self, "disconnect_player")
+	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")
 
 # func connect_global_server(): TODO
@@ -60,11 +60,13 @@ func start_server(port, server_playing=false):
 	get_tree().set_network_peer(peer)
 	# As soon as we're listening, let the matchmaker know
 	_connect_to_matchmaker(port)
+	if not util.args.get_value("-silent"):
+		register_player(get_tree().get_network_unique_id())
 	# is_connected = true TODO
 	# get_node("CustomGame/Server").set_text("Serving!")
 	# get_node("JoinedGameLobby").show()
-	# if "start_game" in my_info and my_info.start_game: TODO
-	# 	start_game()
+	if util.args.get_value("-start-game"):
+		start_game()
 
 sync func start_game(level):
 	print(var2str(players))
@@ -81,8 +83,11 @@ func disconnect_player(id):
 # 		rpc_id(1, "start_game")
 	# is_connected = true TODO
 
-remote func _register_player(new_peer):
-	players[new_peer] = {}
+remote func register_player(new_peer):
+	var info = {}
+	info.is_right_team = right_team_next
+	right_team_next = not right_team_next
+	players[new_peer] = info
 	if get_tree().is_network_server():
 		# I tell new player about all the existing people
 		rset_id(new_peer, "players", players)
@@ -106,9 +111,19 @@ remote func _register_player(new_peer):
 		# 	rpc_id(new_peer, "_pre_configure_game", my_info.level)
 		# 	rpc_id(new_peer, "_post_configure_game")
 
-sync func _unregister_player(peer):
+sync func unregister_player(peer):
 	players.erase(peer)
-	get_node("/root/Level/Players/%d" % peer).queue_free()
+	if begun:
+		get_node("/root/Level/Players/%d" % peer).queue_free()
+
+func set_spectating(spectating):
+	var id = get_tree().get_network_unique_id()
+	if spectating:
+		if players[id]:
+			unregister_player(id)
+	else:
+		if not players[id]:
+			register_player(id)
 
 sync func _spawn_player(p):
 	var hero = 0

From 6524455822863b0579b425c4e1f9ad762c95b6bb Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Fri, 18 May 2018 21:05:49 -0400
Subject: [PATCH 24/40] Properly networking `players`, render player list

---
 scripts/hero_select.gd | 11 +++++++++
 scripts/lobby.gd       | 63 ++++++++++++++++++--------------------------------
 scripts/networking.gd  | 54 +++++++++++++++++++++++++++++++------------
 3 files changed, 73 insertions(+), 55 deletions(-)

diff --git a/scripts/hero_select.gd b/scripts/hero_select.gd
index 5fd34df..eeb433a 100644
--- a/scripts/hero_select.gd
+++ b/scripts/hero_select.gd
@@ -22,3 +22,14 @@ func _ready():
 	for hero_index in range(hero_names.size()):
 		add_item(hero_names[hero_index], hero_index)
 
+	connect("item_selected", self, "set_hero")
+
+func set_hero(hero):
+	networking.set_info("hero", hero)
+
+func random_hero():
+	var hero = randi() % hero_names.size()
+	select(hero)
+	set_hero(hero)
+	return hero
+
diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index ec59d82..c3e7622 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -2,6 +2,8 @@ extends Control
 
 var port = null # Defined by command-line argument with default
 
+onready var hero_select = get_node("HeroSelect/Hero")
+
 func _ready():
 	if get_tree().is_network_server():
 		get_node("LevelSelect").show()
@@ -15,23 +17,17 @@ func _ready():
 	get_node("Spectating").connect("toggled", networking, "set_spectating") # TODO
 	# get_node("CustomGame/LevelSelect").connect("item_selected", self, "select_level") TODO
 	# _send_name()
+	# hero_select.set_hero(0)
 
-func _collect_info():
-	var my_id = get_tree().get_network_unique_id()
-	var my_info = networking.players[my_id]
-	if not "username" in my_info:
-		my_info.username = get_node("Username").get_text()
-	if not "hero" in my_info:
-		my_info.hero = get_node("HeroSelect/Hero").get_selected_id()
-	if not "is_right_team" in my_info:
-		my_info.is_right_team = false # Server assigns team, wait for that
+	networking.connect("info_updated", self, "render_player_list")
+	get_tree().connect("connected_to_server", self, "_send_settings")
+	if get_tree().is_network_server():
+		_send_settings()
 
-func select_hero(hero):
-	var description = get_node("HeroSelect").hero_text[hero]
-	get_node("HeroDescription").set_text(description)
-	var my_id = get_tree().get_network_unique_id()
-	networking.players[my_id].hero = hero
-	rpc("set_hero", get_tree().get_network_unique_id(), hero)
+func _send_settings():
+	print("sending")
+	_send_name()
+	hero_select.random_hero()
 
 sync func set_hero(peer, hero):
 	networking.players[peer].hero = hero
@@ -39,34 +35,21 @@ sync func set_hero(peer, hero):
 
 func _send_name():
 	var name = get_node("Username").text
-	rpc("_set_name", get_tree().get_network_unique_id(), name)
-
-sync func _set_name(peer, name):
-	networking.players[peer].username = name
-	render_player_list()
+	networking.set_info("username", name)
 
 func render_player_list():
-	if has_node("PlayerSettings"):
-		var list = ""
-		var hero_names = get_node("HeroSelect").hero_names
-		for p in networking.players:
-			list += "%-15s" % networking.players[p].username
-			list += "%-20s" % hero_names[networking.players[p].hero]
-			if networking.players[p].is_right_team:
-				list += "Right Team"
-			else:
-				list += "Left Team"
-			list += "\n"
-		get_node("JoinedGameLobby/PlayerList").set_text(list)
-
-sync func assign_team(peer, is_right_team):
-	networking.players[peer].is_right_team = is_right_team
-	if peer == get_tree().get_network_unique_id():
-		if is_right_team:
-			get_node("Team").set_text("Right Team")
+	print(JSON.print(networking.players))
+	var list = ""
+	var hero_names = hero_select.hero_names
+	for p in networking.players:
+		list += "%-15s" % networking.players[p].username
+		list += "%-20s" % hero_names[networking.players[p].hero]
+		if networking.players[p].is_right_team:
+			list += "Right Team"
 		else:
-			get_node("Team").set_text("Left Team")
-	render_player_list()
+			list += "Left Team"
+		list += "\n"
+	get_node("PlayerList").set_text(list)
 
 func _start_game():
 	_collect_info()
diff --git a/scripts/networking.gd b/scripts/networking.gd
index 8cb125c..b49de6c 100644
--- a/scripts/networking.gd
+++ b/scripts/networking.gd
@@ -9,20 +9,18 @@ var is_connected = false # Technically this can be done with ENetcetera but it's
 var begun = false
 # TODO: This needs to go. It carries nothing of value
 # ALL server negotiation should happen before ANY data is investigated (in lobby)
-var my_info = {
-	"hero": 0,
-	"username": "Nickname",
-}
 var global_server_ip = "nv.cosinegaming.com"
 var matchmaker_tcp
 var right_team_next = false
 
+signal info_updated
+
 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")
+	get_tree().connect("connected_to_server", self, "_on_connect")
 
 # func connect_global_server(): TODO
 # 	ip = global_server_ip
@@ -69,20 +67,27 @@ func start_server(port, server_playing=false):
 		start_game()
 
 sync func start_game(level):
-	print(var2str(players))
 	rpc("_pre_configure_game", level)
 
-func disconnect_player(id):
-	if get_tree().is_network_server():
-		rpc("unregister_player", id)
-	# call_deferred("render_player_list") TODO
-
 # func _on_connect():
 # 	rpc("_register_player", get_tree().get_network_unique_id(), my_info)
 # 	if util.args.get_value("-start-game"):
 # 		rpc_id(1, "start_game")
 	# is_connected = true TODO
 
+# remote func _set_players(json):
+# 	print(json)
+# 	players = JSON.parse(json).result
+# 	print("setted")
+# 	print(JSON.print(players))
+
+func send_all_info(new_peer):
+	for p in players:
+		if p != new_peer:
+			for key in players[p]:
+				var val = players[p][key]
+				set_info(key, val, p)
+
 remote func register_player(new_peer):
 	var info = {}
 	info.is_right_team = right_team_next
@@ -90,7 +95,9 @@ remote func register_player(new_peer):
 	players[new_peer] = info
 	if get_tree().is_network_server():
 		# I tell new player about all the existing people
-		rset_id(new_peer, "players", players)
+		send_all_info(new_peer)
+		# rset_id(new_peer, "players", players)
+	emit_signal("info_updated")
 	# render_player_list() TODO
 		# var right_team_count = 0
 		# Send current players' info to new player
@@ -115,15 +122,32 @@ sync func unregister_player(peer):
 	players.erase(peer)
 	if begun:
 		get_node("/root/Level/Players/%d" % peer).queue_free()
+	emit_signal("info_updated")
 
 func set_spectating(spectating):
 	var id = get_tree().get_network_unique_id()
 	if spectating:
 		if players[id]:
-			unregister_player(id)
+			rpc("unregister_player", id)
 	else:
 		if not players[id]:
-			register_player(id)
+			rpc("register_player", id)
+
+sync func _set_info(key, value, peer=0):
+	if not peer:
+		peer = get_tree().get_rpc_sender_id()
+		if peer == 0:
+			# Was self. See https://github.com/godotengine/godot/issues/19026
+			peer = get_tree().get_network_unique_id()
+	players[peer][key] = value
+	emit_signal("info_updated")
+
+func set_info(key, value, peer=0):
+	rpc("_set_info", str(key), value, peer)
+
+func _on_connect():
+	emit_signal("info_updated")
+	register_player(get_tree().get_network_unique_id())
 
 sync func _spawn_player(p):
 	var hero = 0
@@ -138,7 +162,7 @@ sync func _spawn_player(p):
 sync func _pre_configure_game(level):
 	level = 2 # TODO: Remove this!!
 	begun = true
-	my_info.level = level # Remember the level for future player registration
+	# my_info.level = level # Remember the level for future player registration
 
 	var self_peer_id = get_tree().get_network_unique_id()
 

From 5ca1fe9a9d6f06f78808ef330fa0a4c02e9cdef8 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Fri, 18 May 2018 21:17:13 -0400
Subject: [PATCH 25/40] Fix -server and -client flags

---
 scripts/custom_game.gd |  8 ++------
 scripts/lobby.gd       |  2 ++
 scripts/menu.gd        |  4 ++--
 scripts/networking.gd  | 10 ++++++++--
 scripts/util.gd        |  1 +
 5 files changed, 15 insertions(+), 10 deletions(-)

diff --git a/scripts/custom_game.gd b/scripts/custom_game.gd
index 57cce07..15db1f6 100644
--- a/scripts/custom_game.gd
+++ b/scripts/custom_game.gd
@@ -6,18 +6,14 @@ func _ready():
 
 func _start_server():
 	# Custom Game can assume we're playing as well
-	networking.start_server(_get_port(), true)
+	networking.start_server()
 	_show_lobby()
 
 func _start_client():
 	var ip = get_node("IP").text
-	networking.start_client(ip, _get_port())
+	networking.start_client(ip)
 	_show_lobby()
 
 func _show_lobby():
 	get_tree().change_scene("res://scenes/lobby.tscn")
 
-func _get_port():
-	var port = util.args.get_value("-port")
-	return port
-
diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index c3e7622..a181761 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -7,6 +7,8 @@ onready var hero_select = get_node("HeroSelect/Hero")
 func _ready():
 	if get_tree().is_network_server():
 		get_node("LevelSelect").show()
+	else:
+		get_node("LevelSelect").hide()
 
 	get_node("Username").connect("text_changed", self, "_send_name")
 	get_node("StartGame").connect("pressed", self, "_start_game")
diff --git a/scripts/menu.gd b/scripts/menu.gd
index 578da9f..18eed84 100644
--- a/scripts/menu.gd
+++ b/scripts/menu.gd
@@ -49,12 +49,12 @@ func _arg_actions():
 	# if not o.get_value("-no-record") and not o.get_value("-ai"):
 	# 	my_info.record = true
 	if o.get_value("-server"):
-		networking.call_deferred("start_server")
+		networking.start_server()
 		get_tree().change_scene("res://scenes/lobby.tscn")
 	# if o.get_value("-matchmaker"):
 	# 	call_deferred("_matchmaker_init")
 	if o.get_value("-client"):
-		networking.call_deferred("start_client")
+		networking.start_client()
 		get_tree().change_scene("res://scenes/lobby.tscn")
 	if o.get_value("-start-game"):
 		networking.call_deferred("start_game")
diff --git a/scripts/networking.gd b/scripts/networking.gd
index b49de6c..3575425 100644
--- a/scripts/networking.gd
+++ b/scripts/networking.gd
@@ -26,8 +26,12 @@ func _ready():
 # 	ip = global_server_ip
 # 	_client_init()
 
-func start_client(ip, port):
+func start_client(ip="", port=0):
 	# collect_info() TODO
+	if not ip:
+		ip = util.args.get_value("-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)
@@ -50,8 +54,10 @@ func _connect_to_matchmaker(game_port):
 	matchmaker_tcp.put_var(matchmaking.messages.ready_to_connect)
 	matchmaker_tcp.put_var(game_port)
 
-func start_server(port, server_playing=false):
+func start_server(port=0):
 	# collect_info() TODO
+	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)
diff --git a/scripts/util.gd b/scripts/util.gd
index 3d894df..318a2fa 100644
--- a/scripts/util.gd
+++ b/scripts/util.gd
@@ -31,6 +31,7 @@ func _get_args():
 	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('-ip', '127.0.0.1', 'The ip to connect to (client only!)')
 	opts.add('-port', 54673, '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!')

From dc095e66fc20438f0b8f8109669cefed1db50ddb Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sun, 20 May 2018 16:27:12 -0400
Subject: [PATCH 26/40] Add level select functionality; remove dead code

---
 scripts/lobby.gd       | 45 +++++++++++++++++++++++++--------------------
 scripts/menu.gd        |  2 --
 scripts/networking.gd  | 39 +++++++++------------------------------
 util/start-multiple.sh |  2 +-
 4 files changed, 35 insertions(+), 53 deletions(-)

diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index a181761..9dd6aae 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -3,34 +3,45 @@ extends Control
 var port = null # Defined by command-line argument with default
 
 onready var hero_select = get_node("HeroSelect/Hero")
+onready var level_select = get_node("LevelSelect")
 
 func _ready():
-	if get_tree().is_network_server():
-		get_node("LevelSelect").show()
-	else:
-		get_node("LevelSelect").hide()
 
 	get_node("Username").connect("text_changed", self, "_send_name")
-	get_node("StartGame").connect("pressed", self, "_start_game")
+	get_node("StartGame").connect("pressed", networking, "start_game")
 
 	var spectating = util.args.get_value("-silent")
 	get_node("Spectating").pressed = spectating
-	# 
-	get_node("Spectating").connect("toggled", networking, "set_spectating") # TODO
-	# get_node("CustomGame/LevelSelect").connect("item_selected", self, "select_level") TODO
-	# _send_name()
-	# hero_select.set_hero(0)
+	get_node("Spectating").connect("toggled", networking, "set_spectating")
+
+	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()
+		_set_level(level)
+
+		level_select.show()
+		level_select.select(level)
+		level_select.connect("item_selected", self, "_set_level")
+	else:
+		level_select.hide()
 
 	networking.connect("info_updated", self, "render_player_list")
-	get_tree().connect("connected_to_server", self, "_send_settings")
+	get_tree().connect("connected_to_server", self, "_connected")
 	if get_tree().is_network_server():
-		_send_settings()
+		_connected()
 
-func _send_settings():
-	print("sending")
+func _connected():
 	_send_name()
 	hero_select.random_hero()
 
+	if util.args.get_value("-start-game"):
+		networking.start_game()
+
+func _set_level(level):
+	networking.set_info("level", level)
+
 sync func set_hero(peer, hero):
 	networking.players[peer].hero = hero
 	render_player_list()
@@ -40,7 +51,6 @@ func _send_name():
 	networking.set_info("username", name)
 
 func render_player_list():
-	print(JSON.print(networking.players))
 	var list = ""
 	var hero_names = hero_select.hero_names
 	for p in networking.players:
@@ -53,8 +63,3 @@ func render_player_list():
 		list += "\n"
 	get_node("PlayerList").set_text(list)
 
-func _start_game():
-	_collect_info()
-	var level = 2 # TODO
-	networking.rpc_id(1, "start_game", level)
-
diff --git a/scripts/menu.gd b/scripts/menu.gd
index 18eed84..c004be3 100644
--- a/scripts/menu.gd
+++ b/scripts/menu.gd
@@ -56,8 +56,6 @@ func _arg_actions():
 	if o.get_value("-client"):
 		networking.start_client()
 		get_tree().change_scene("res://scenes/lobby.tscn")
-	if o.get_value("-start-game"):
-		networking.call_deferred("start_game")
 	# if o.get_value("-singleplayer"):
 	# 	networking.call_deferred("start_singleplayer")
 	if o.get_value('-h'):
diff --git a/scripts/networking.gd b/scripts/networking.gd
index 3575425..bb5329f 100644
--- a/scripts/networking.gd
+++ b/scripts/networking.gd
@@ -4,10 +4,8 @@ onready var matchmaking = preload("res://scripts/matchmaking.gd").new()
 
 remote var players = {}
 var players_done = []
-var is_connected = false # Technically this can be done with ENetcetera but it's easier this way
 # TODO: Should we abstract server so variables like this aren't cluttering everything up?
 var begun = false
-# TODO: This needs to go. It carries nothing of value
 # ALL server negotiation should happen before ANY data is investigated (in lobby)
 var global_server_ip = "nv.cosinegaming.com"
 var matchmaker_tcp
@@ -27,7 +25,6 @@ func _ready():
 # 	_client_init()
 
 func start_client(ip="", port=0):
-	# collect_info() TODO
 	if not ip:
 		ip = util.args.get_value("-ip")
 	if not port:
@@ -36,7 +33,6 @@ func start_client(ip="", port=0):
 	print("Connecting to " + ip + ":" + str(port))
 	peer.create_client(ip, port)
 	get_tree().set_network_peer(peer)
-	# get_node("CustomGame/Client").set_text("Clienting!") TODO
 
 # func singleplayer_init(): TODO
 # 	# collect_info() TODO
@@ -55,7 +51,6 @@ func _connect_to_matchmaker(game_port):
 	matchmaker_tcp.put_var(game_port)
 
 func start_server(port=0):
-	# collect_info() TODO
 	if not port:
 		port = util.args.get_value("-port")
 	var peer = NetworkedMultiplayerENet.new()
@@ -66,26 +61,15 @@ func start_server(port=0):
 	_connect_to_matchmaker(port)
 	if not util.args.get_value("-silent"):
 		register_player(get_tree().get_network_unique_id())
-	# is_connected = true TODO
-	# get_node("CustomGame/Server").set_text("Serving!")
-	# get_node("JoinedGameLobby").show()
 	if util.args.get_value("-start-game"):
 		start_game()
 
-sync func start_game(level):
+master func _start_game():
+	var level = players[1].level # TODO: Can we guarantee this will have level?
 	rpc("_pre_configure_game", level)
 
-# func _on_connect():
-# 	rpc("_register_player", get_tree().get_network_unique_id(), my_info)
-# 	if util.args.get_value("-start-game"):
-# 		rpc_id(1, "start_game")
-	# is_connected = true TODO
-
-# remote func _set_players(json):
-# 	print(json)
-# 	players = JSON.parse(json).result
-# 	print("setted")
-# 	print(JSON.print(players))
+func start_game():
+	rpc_id(1, "_start_game")
 
 func send_all_info(new_peer):
 	for p in players:
@@ -102,22 +86,18 @@ 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)
-		# rset_id(new_peer, "players", players)
 	emit_signal("info_updated")
-	# render_player_list() TODO
 		# var right_team_count = 0
 		# Send current players' info to new player
-			# Send new player, old player's info
-			# rpc_id(new_peer, "_register_player", old_peer, players[old_peer])
 			# if old_peer != new_peer:
 			# 	# We need to assign team later, so count current
 			# 	if players[old_peer].is_right_team:
 			# 		right_team_count += 1
-				# if begun: TODO this should belong to lobby
+				# if begun: TODO this should belong to lobby?
 				# 	rpc_id(old_peer, "_spawn_player", new_peer)
 				# 	rpc_id(old_peer, "_begin_player_deferred", new_peer) # Spawning is deferred
 		# var assign_right_team = right_team_count * 2 < players.size()
-		# rpc("assign_team", new_peer, assign_right_team)
+		# set_info("is_right_team", assign_right_team, new_peer)
 		# if not begun and players.size() == matchmaking.GAME_SIZE:
 		# 	start_game()
 		# if begun:
@@ -166,7 +146,6 @@ sync func _spawn_player(p):
 	get_node("/root/Level/Players").call_deferred("add_child", player)
 
 sync func _pre_configure_game(level):
-	level = 2 # TODO: Remove this!!
 	begun = true
 	# my_info.level = level # Remember the level for future player registration
 
@@ -174,12 +153,12 @@ sync func _pre_configure_game(level):
 
 	# 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()
+	# for element in get_node("/root/Lobby").get_children():
+	# 	element.queue_free()
+	get_node("/root/Lobby").hide()
 
 	var world = load("res://scenes/levels/%d.tscn" % level).instance()
 	get_node("/root").add_child(world)
-	print("added level!")
 
 	# Load all players (including self)
 	for p in players:
diff --git a/util/start-multiple.sh b/util/start-multiple.sh
index 1213138..8e4d3d7 100644
--- a/util/start-multiple.sh
+++ b/util/start-multiple.sh
@@ -10,5 +10,5 @@ fi
 
 util/open-multiple.sh $count "$@"
 sleep 1
-godot -start-game "$@" &
+godot -client -start-game "$@" &
 

From 2d93fd4d1e10504e4d7dc1cefd78a9c04536e857 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sun, 20 May 2018 16:40:45 -0400
Subject: [PATCH 27/40] Begin players properly

---
 scripts/networking.gd | 9 ++-------
 1 file changed, 2 insertions(+), 7 deletions(-)

diff --git a/scripts/networking.gd b/scripts/networking.gd
index bb5329f..b092f9c 100644
--- a/scripts/networking.gd
+++ b/scripts/networking.gd
@@ -147,14 +147,9 @@ sync func _spawn_player(p):
 
 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()
 	get_node("/root/Lobby").hide()
 
 	var world = load("res://scenes/levels/%d.tscn" % level).instance()
@@ -165,13 +160,13 @@ sync func _pre_configure_game(level):
 		players[p].level = level
 		_spawn_player(p)
 
-	rpc_id(1, "done_preconfiguring", self_peer_id)
+	rpc_id(1, "_done_preconfiguring", self_peer_id)
 
 sync func _done_preconfiguring(who):
 	players_done.append(who)
 	if players_done.size() == players.size():
 		# We call deferred in case singleplayer has placing the player in queue still
-		call_deferred("rpc", "post_configure_game")
+		call_deferred("rpc", "_post_configure_game")
 
 sync func _post_configure_game():
 	# Begin all players (including self)

From 257346db5a1c2f9665810bfc4f88839cb2286694 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sun, 20 May 2018 16:52:34 -0400
Subject: [PATCH 28/40] Add singleplayer functionality; fix -hero arg

---
 scenes/menu.tscn               |  2 +-
 scenes/singleplayer_lobby.tscn | 82 ++++++++++++++++++++++++++++++++++++++++++
 scripts/hero_select.gd         |  2 +-
 scripts/lobby.gd               |  7 +++-
 scripts/menu.gd                | 18 +++-------
 5 files changed, 94 insertions(+), 17 deletions(-)
 create mode 100644 scenes/singleplayer_lobby.tscn

diff --git a/scenes/menu.tscn b/scenes/menu.tscn
index fb0d0f4..830d17b 100644
--- a/scenes/menu.tscn
+++ b/scenes/menu.tscn
@@ -135,7 +135,7 @@ toggle_mode = false
 enabled_focus_mode = 2
 shortcut = null
 group = null
-text = "Singleplayer"
+text = "Practice Range"
 flat = false
 align = 1
 
diff --git a/scenes/singleplayer_lobby.tscn b/scenes/singleplayer_lobby.tscn
new file mode 100644
index 0000000..7cc9ac1
--- /dev/null
+++ b/scenes/singleplayer_lobby.tscn
@@ -0,0 +1,82 @@
+[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="StartGame" parent="." index="8"]
+
+margin_left = 41.0
+margin_top = 486.0
+margin_right = 163.0
+margin_bottom = 526.0
+text = "Play!"
+
+[node name="VSeparator" parent="." index="9"]
+
+visible = false
+
+
+[editable path="HeroSelect"]
diff --git a/scripts/hero_select.gd b/scripts/hero_select.gd
index eeb433a..38b2726 100644
--- a/scripts/hero_select.gd
+++ b/scripts/hero_select.gd
@@ -25,11 +25,11 @@ func _ready():
 	connect("item_selected", self, "set_hero")
 
 func set_hero(hero):
+	select(hero)
 	networking.set_info("hero", hero)
 
 func random_hero():
 	var hero = randi() % hero_names.size()
-	select(hero)
 	set_hero(hero)
 	return hero
 
diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index 9dd6aae..b443e86 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -19,6 +19,7 @@ func _ready():
 		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()
@@ -34,7 +35,11 @@ func _ready():
 
 func _connected():
 	_send_name()
-	hero_select.random_hero()
+	if util.args.get_value("-hero") == "r":
+		hero_select.random_hero()
+	else:
+		print(util.args.get_value("-hero"))
+		hero_select.set_hero(int(util.args.get_value("-hero")))
 
 	if util.args.get_value("-start-game"):
 		networking.start_game()
diff --git a/scripts/menu.gd b/scripts/menu.gd
index c004be3..55cab18 100644
--- a/scripts/menu.gd
+++ b/scripts/menu.gd
@@ -19,7 +19,8 @@ func _custom_game():
 	get_tree().change_scene("res://scenes/custom_game.tscn")
 
 func _singleplayer():
-	print("still refactoring singleplayer")
+	networking.start_server()
+	get_tree().change_scene("res://scenes/singleplayer_lobby.tscn")
 
 # Command line
 
@@ -33,17 +34,6 @@ func _option_sel(button_name, option):
 
 func _arg_actions():
 	var o = util.args
-	# 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("-port"):
-	# 	port = o.get_value("-port")
 	# if o.get_value("-ai"):
 	# 	my_info.is_ai = true
 	# if not o.get_value("-no-record") and not o.get_value("-ai"):
@@ -56,8 +46,8 @@ func _arg_actions():
 	if o.get_value("-client"):
 		networking.start_client()
 		get_tree().change_scene("res://scenes/lobby.tscn")
-	# if o.get_value("-singleplayer"):
-	# 	networking.call_deferred("start_singleplayer")
+	if o.get_value("-singleplayer"):
+		_singleplayer()
 	if o.get_value('-h'):
 		o.print_help()
 		quit()

From 7b12180695f4a170b86a3b32cc068d2c4a5a3a63 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sun, 20 May 2018 16:56:25 -0400
Subject: [PATCH 29/40] [WIP] Add matchmaking

---
 scripts/menu.gd       | 4 +++-
 scripts/networking.gd | 9 +--------
 2 files changed, 4 insertions(+), 9 deletions(-)

diff --git a/scripts/menu.gd b/scripts/menu.gd
index 55cab18..2d4d1d6 100644
--- a/scripts/menu.gd
+++ b/scripts/menu.gd
@@ -13,7 +13,9 @@ func _gui_setup():
 	get_node("Center/Singleplayer").connect("pressed", self, "_singleplayer")
 
 func _find_game():
-	print("still refactoring matchmaker")
+	var ip = networking.global_server_ip
+	var port = networking.matchmaking.MATCHMAKING_PORT
+	networking.start_client(ip, port)
 
 func _custom_game():
 	get_tree().change_scene("res://scenes/custom_game.tscn")
diff --git a/scripts/networking.gd b/scripts/networking.gd
index b092f9c..878bb35 100644
--- a/scripts/networking.gd
+++ b/scripts/networking.gd
@@ -27,6 +27,7 @@ func _ready():
 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()
@@ -34,14 +35,6 @@ func start_client(ip="", port=0):
 	peer.create_client(ip, port)
 	get_tree().set_network_peer(peer)
 
-# func singleplayer_init(): TODO
-# 	# collect_info() TODO
-# 	var peer = NetworkedMultiplayerENet.new()
-# 	peer.create_server(port, 1)
-# 	get_tree().set_network_peer(peer)
-# 	players[1] = my_info
-# 	start_game()
-
 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)

From 054b240c4f20dc6d272ea5e9cdfdbe863cc9d1d0 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sun, 20 May 2018 17:55:44 -0400
Subject: [PATCH 30/40] Fix spectating mode by making it a player_info

---
 scripts/lobby.gd      | 26 ++++++++++++++++++--------
 scripts/networking.gd | 22 +++++++---------------
 2 files changed, 25 insertions(+), 23 deletions(-)

diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index b443e86..cc606bf 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -12,7 +12,7 @@ func _ready():
 
 	var spectating = util.args.get_value("-silent")
 	get_node("Spectating").pressed = spectating
-	get_node("Spectating").connect("toggled", networking, "set_spectating")
+	get_node("Spectating").connect("toggled", self, "_set_spectating")
 
 	if get_tree().is_network_server():
 		# We put level in our players dict because it's automatically broadcast to other players
@@ -47,6 +47,9 @@ func _connected():
 func _set_level(level):
 	networking.set_info("level", level)
 
+func _set_spectating(is_spectating):
+	networking.set_info("spectating", is_spectating)
+
 sync func set_hero(peer, hero):
 	networking.players[peer].hero = hero
 	render_player_list()
@@ -59,12 +62,19 @@ func render_player_list():
 	var list = ""
 	var hero_names = hero_select.hero_names
 	for p in networking.players:
-		list += "%-15s" % networking.players[p].username
-		list += "%-20s" % hero_names[networking.players[p].hero]
-		if networking.players[p].is_right_team:
-			list += "Right Team"
-		else:
-			list += "Left Team"
-		list += "\n"
+		var player = networking.players[p]
+		var spectating = player.has("spectating") and player.spectating
+		# A spectating server is just a dedicated server, ignore it
+		if not (spectating and p == 1):
+			list += "%-15s " % player.username
+			list += "%-20s " % hero_names[player.hero]
+			var team_format = "%-14s"
+			if player.is_right_team:
+				list += team_format % "Right Team"
+			else:
+				list += team_format % "Left Team"
+			if spectating:
+				list += "Spectating"
+			list += "\n"
 	get_node("PlayerList").set_text(list)
 
diff --git a/scripts/networking.gd b/scripts/networking.gd
index 878bb35..a37779a 100644
--- a/scripts/networking.gd
+++ b/scripts/networking.gd
@@ -52,10 +52,9 @@ func start_server(port=0):
 	get_tree().set_network_peer(peer)
 	# As soon as we're listening, let the matchmaker know
 	_connect_to_matchmaker(port)
-	if not util.args.get_value("-silent"):
-		register_player(get_tree().get_network_unique_id())
-	if util.args.get_value("-start-game"):
-		start_game()
+	register_player(get_tree().get_network_unique_id())
+	if util.args.get_value("-silent"):
+		set_info("spectating", true)
 
 master func _start_game():
 	var level = players[1].level # TODO: Can we guarantee this will have level?
@@ -103,15 +102,6 @@ sync func unregister_player(peer):
 		get_node("/root/Level/Players/%d" % peer).queue_free()
 	emit_signal("info_updated")
 
-func set_spectating(spectating):
-	var id = get_tree().get_network_unique_id()
-	if spectating:
-		if players[id]:
-			rpc("unregister_player", id)
-	else:
-		if not players[id]:
-			rpc("register_player", id)
-
 sync func _set_info(key, value, peer=0):
 	if not peer:
 		peer = get_tree().get_rpc_sender_id()
@@ -151,7 +141,8 @@ sync func _pre_configure_game(level):
 	# Load all players (including self)
 	for p in players:
 		players[p].level = level
-		_spawn_player(p)
+		if not (players[p].has("spectating") and players[p].spectating):
+			_spawn_player(p)
 
 	rpc_id(1, "_done_preconfiguring", self_peer_id)
 
@@ -164,7 +155,8 @@ sync func _done_preconfiguring(who):
 sync func _post_configure_game():
 	# Begin all players (including self)
 	for p in players:
-		_begin_player_deferred(p)
+		if not (players[p].has("spectating") and players[p].spectating):
+			_begin_player_deferred(p)
 
 func _begin_player(peer):
 	get_node("/root/Level/Players/%d" % peer).begin()

From 996e4e24cc422d9406460557902762d2186b22c4 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sun, 20 May 2018 18:08:37 -0400
Subject: [PATCH 31/40] [WIP] Add back matchmaking!

One problem remains: start_game behaves the same whether a game has
started or not.
---
 scripts/custom_game.gd |  5 -----
 scripts/lobby.gd       |  2 +-
 scripts/matchmaking.gd |  2 +-
 scripts/menu.gd        |  9 ++++-----
 scripts/networking.gd  | 17 +++++++++++++----
 5 files changed, 19 insertions(+), 16 deletions(-)

diff --git a/scripts/custom_game.gd b/scripts/custom_game.gd
index 15db1f6..ec77fb5 100644
--- a/scripts/custom_game.gd
+++ b/scripts/custom_game.gd
@@ -7,13 +7,8 @@ func _ready():
 func _start_server():
 	# Custom Game can assume we're playing as well
 	networking.start_server()
-	_show_lobby()
 
 func _start_client():
 	var ip = get_node("IP").text
 	networking.start_client(ip)
-	_show_lobby()
-
-func _show_lobby():
-	get_tree().change_scene("res://scenes/lobby.tscn")
 
diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index cc606bf..390a257 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -45,7 +45,7 @@ func _connected():
 		networking.start_game()
 
 func _set_level(level):
-	networking.set_info("level", level)
+	networking.level = level
 
 func _set_spectating(is_spectating):
 	networking.set_info("spectating", is_spectating)
diff --git a/scripts/matchmaking.gd b/scripts/matchmaking.gd
index f137556..30e3e4f 100644
--- a/scripts/matchmaking.gd
+++ b/scripts/matchmaking.gd
@@ -80,7 +80,7 @@ func queue(netid):
 # 	#
 
 func add_to_game(netid, port):
-	lobby.rpc_id(netid, "_client_init", port)
+	networking.rpc_id(netid, "reconnect", port)
 
 func skirmish_to_game(port, count=1):
 	for i in range(count):
diff --git a/scripts/menu.gd b/scripts/menu.gd
index 2d4d1d6..95b6299 100644
--- a/scripts/menu.gd
+++ b/scripts/menu.gd
@@ -13,7 +13,8 @@ func _gui_setup():
 	get_node("Center/Singleplayer").connect("pressed", self, "_singleplayer")
 
 func _find_game():
-	var ip = networking.global_server_ip
+	# var ip = networking.global_server_ip
+	var ip = util.args.get_value("-ip")
 	var port = networking.matchmaking.MATCHMAKING_PORT
 	networking.start_client(ip, port)
 
@@ -42,12 +43,10 @@ func _arg_actions():
 	# 	my_info.record = true
 	if o.get_value("-server"):
 		networking.start_server()
-		get_tree().change_scene("res://scenes/lobby.tscn")
-	# if o.get_value("-matchmaker"):
-	# 	call_deferred("_matchmaker_init")
+	if o.get_value("-matchmaker"):
+		networking.matchmaking.start_matchmaker()
 	if o.get_value("-client"):
 		networking.start_client()
-		get_tree().change_scene("res://scenes/lobby.tscn")
 	if o.get_value("-singleplayer"):
 		_singleplayer()
 	if o.get_value('-h'):
diff --git a/scripts/networking.gd b/scripts/networking.gd
index a37779a..d92c63b 100644
--- a/scripts/networking.gd
+++ b/scripts/networking.gd
@@ -11,6 +11,8 @@ var global_server_ip = "nv.cosinegaming.com"
 var matchmaker_tcp
 var right_team_next = false
 
+var level
+
 signal info_updated
 
 func _ready():
@@ -34,6 +36,12 @@ func start_client(ip="", port=0):
 	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(port):
+	# Reset previously known players
+	players = {}
+	start_client("", port)
 
 func _connect_to_matchmaker(game_port):
 	var matchmaker_peer = StreamPeerTCP.new()
@@ -55,9 +63,9 @@ func start_server(port=0):
 	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 _start_game():
-	var level = players[1].level # TODO: Can we guarantee this will have level?
 	rpc("_pre_configure_game", level)
 
 func start_game():
@@ -108,8 +116,9 @@ sync func _set_info(key, value, peer=0):
 		if peer == 0:
 			# Was self. See https://github.com/godotengine/godot/issues/19026
 			peer = get_tree().get_network_unique_id()
-	players[peer][key] = value
-	emit_signal("info_updated")
+	if players.has(peer):
+		players[peer][key] = value
+		emit_signal("info_updated")
 
 func set_info(key, value, peer=0):
 	rpc("_set_info", str(key), value, peer)
@@ -140,7 +149,6 @@ sync func _pre_configure_game(level):
 
 	# Load all players (including self)
 	for p in players:
-		players[p].level = level
 		if not (players[p].has("spectating") and players[p].spectating):
 			_spawn_player(p)
 
@@ -149,6 +157,7 @@ sync func _pre_configure_game(level):
 sync func _done_preconfiguring(who):
 	players_done.append(who)
 	if players_done.size() == players.size():
+		print("done")
 		# We call deferred in case singleplayer has placing the player in queue still
 		call_deferred("rpc", "_post_configure_game")
 

From a3036de2bb0c8f5736562bce07ccd335a961d71e Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sun, 20 May 2018 18:50:18 -0400
Subject: [PATCH 32/40] Add "ready" feature

---
 scenes/lobby.tscn     | 35 ++++++++++++++++++++++++++++++-----
 scripts/lobby.gd      | 27 ++++++++++++++++++---------
 scripts/networking.gd | 26 ++++++++++++++++++--------
 util/server.sh        |  2 +-
 4 files changed, 67 insertions(+), 23 deletions(-)

diff --git a/scenes/lobby.tscn b/scenes/lobby.tscn
index 10f49eb..8381d61 100644
--- a/scenes/lobby.tscn
+++ b/scenes/lobby.tscn
@@ -141,7 +141,7 @@ toggle_mode = true
 enabled_focus_mode = 2
 shortcut = null
 group = null
-text = "Spectating"
+text = "Spectating "
 flat = false
 align = 0
 
@@ -252,16 +252,41 @@ percent_visible = 1.0
 lines_skipped = 0
 max_lines_visible = -1
 
-[node name="StartGame" type="Button" parent="." index="7"]
+[node name="Ready" type="CheckButton" parent="." index="7"]
 
 anchor_left = 0.0
 anchor_top = 0.0
 anchor_right = 0.0
 anchor_bottom = 0.0
+margin_left = 493.0
+margin_top = 436.0
+margin_right = 628.0
+margin_bottom = 476.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 = true
+enabled_focus_mode = 2
+shortcut = null
+group = null
+text = "Ready "
+flat = false
+align = 0
+
+[node name="StartGame" type="Button" parent="." index="8"]
+
+visible = false
+anchor_left = 0.0
+anchor_top = 0.0
+anchor_right = 0.0
+anchor_bottom = 0.0
 margin_left = 496.0
-margin_top = 453.0
+margin_top = 491.0
 margin_right = 618.0
-margin_bottom = 493.0
+margin_bottom = 531.0
 rect_pivot_offset = Vector2( 0, 0 )
 focus_mode = 2
 mouse_filter = 0
@@ -276,7 +301,7 @@ text = "Ready!"
 flat = false
 align = 1
 
-[node name="VSeparator" type="VSeparator" parent="." index="8"]
+[node name="VSeparator" type="VSeparator" parent="." index="9"]
 
 anchor_left = 0.0
 anchor_top = 0.0
diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index 390a257..fa2a277 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -4,15 +4,21 @@ var port = null # Defined by command-line argument with default
 
 onready var hero_select = get_node("HeroSelect/Hero")
 onready var level_select = get_node("LevelSelect")
+onready var start_game_button = get_node("StartGame")
 
 func _ready():
 
 	get_node("Username").connect("text_changed", self, "_send_name")
-	get_node("StartGame").connect("pressed", networking, "start_game")
 
 	var spectating = util.args.get_value("-silent")
 	get_node("Spectating").pressed = spectating
-	get_node("Spectating").connect("toggled", self, "_set_spectating")
+	get_node("Spectating").connect("toggled", self, "_set_info_callback", ["spectating"])
+	get_node("Ready").connect("toggled", self, "_set_info_callback", ["ready"])
+	start_game_button.connect("pressed", networking, "start_game")
+	if get_tree().is_network_server():
+		start_game_button.show()
+	else:
+		start_game_button.hide()
 
 	if get_tree().is_network_server():
 		# We put level in our players dict because it's automatically broadcast to other players
@@ -47,8 +53,10 @@ func _connected():
 func _set_level(level):
 	networking.level = level
 
-func _set_spectating(is_spectating):
-	networking.set_info("spectating", is_spectating)
+# 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(key, value)
 
 sync func set_hero(peer, hero):
 	networking.players[peer].hero = hero
@@ -63,17 +71,18 @@ func render_player_list():
 	var hero_names = hero_select.hero_names
 	for p in networking.players:
 		var player = networking.players[p]
-		var spectating = player.has("spectating") and player.spectating
 		# A spectating server is just a dedicated server, ignore it
-		if not (spectating and p == 1):
+		if not (player.spectating and p == 1):
 			list += "%-15s " % player.username
-			list += "%-20s " % hero_names[player.hero]
-			var team_format = "%-14s"
+			list += "%-10s " % hero_names[player.hero]
+			var team_format = "%-11s"
 			if player.is_right_team:
 				list += team_format % "Right Team"
 			else:
 				list += team_format % "Left Team"
-			if spectating:
+			var ready_text = "Ready" if player.ready else ""
+			list += "%-6s" % ready_text
+			if player.spectating:
 				list += "Spectating"
 			list += "\n"
 	get_node("PlayerList").set_text(list)
diff --git a/scripts/networking.gd b/scripts/networking.gd
index d92c63b..6971a16 100644
--- a/scripts/networking.gd
+++ b/scripts/networking.gd
@@ -4,9 +4,7 @@ onready var matchmaking = preload("res://scripts/matchmaking.gd").new()
 
 remote var players = {}
 var players_done = []
-# TODO: Should we abstract server so variables like this aren't cluttering everything up?
 var begun = false
-# ALL server negotiation should happen before ANY data is investigated (in lobby)
 var global_server_ip = "nv.cosinegaming.com"
 var matchmaker_tcp
 var right_team_next = false
@@ -22,9 +20,7 @@ func _ready():
 	get_tree().connect("network_peer_connected", self, "register_player")
 	get_tree().connect("connected_to_server", self, "_on_connect")
 
-# func connect_global_server(): TODO
-# 	ip = global_server_ip
-# 	_client_init()
+	connect("info_updated", self, "_check_info")
 
 func start_client(ip="", port=0):
 	if not ip:
@@ -81,6 +77,8 @@ func send_all_info(new_peer):
 remote func register_player(new_peer):
 	var info = {}
 	info.is_right_team = right_team_next
+	info.ready = false
+	info.spectating = false
 	right_team_next = not right_team_next
 	players[new_peer] = info
 	if get_tree().is_network_server():
@@ -127,9 +125,21 @@ func _on_connect():
 	emit_signal("info_updated")
 	register_player(get_tree().get_network_unique_id())
 
+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
+		for p in players:
+			if not players[p].spectating:
+				if not players[p].ready:
+					ready = false
+		if ready:
+			start_game()
+
 sync func _spawn_player(p):
 	var hero = 0
-	if players[p].has("hero"): # TODO: Rethink how we do this whole shenanigan
+	if players[p].has("hero"):
 		hero = players[p].hero
 	var player = load("res://scenes/heroes/" + str(hero) + ".tscn").instance()
 	player.set_name(str(p))
@@ -149,7 +159,7 @@ sync func _pre_configure_game(level):
 
 	# Load all players (including self)
 	for p in players:
-		if not (players[p].has("spectating") and players[p].spectating):
+		if not players[p].spectating:
 			_spawn_player(p)
 
 	rpc_id(1, "_done_preconfiguring", self_peer_id)
@@ -164,7 +174,7 @@ sync func _done_preconfiguring(who):
 sync func _post_configure_game():
 	# Begin all players (including self)
 	for p in players:
-		if not (players[p].has("spectating") and players[p].spectating):
+		if not players[p].spectating:
 			_begin_player_deferred(p)
 
 func _begin_player(peer):
diff --git a/util/server.sh b/util/server.sh
index d08c8bb..3f24370 100644
--- a/util/server.sh
+++ b/util/server.sh
@@ -1,2 +1,2 @@
-godot-server -level=2 -silent -server -start-game "$@"
+godot-server -level=2 -silent -server "$@"
 

From 345ca3d6fe10d9fe9f1e4c9f93ce4aa52c9821c9 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sun, 20 May 2018 19:49:06 -0400
Subject: [PATCH 33/40] Fix joining late!

---
 scenes/lobby.tscn          |  2 +-
 scripts/heroes/3.gd        |  2 +-
 scripts/heroes/4.gd        |  2 +-
 scripts/heroes/5.gd        |  2 +-
 scripts/heroes/5_portal.gd |  2 +-
 scripts/lobby.gd           | 16 +++++++++++++---
 scripts/networking.gd      | 22 +++++++++++++---------
 scripts/player.gd          |  3 ++-
 scripts/util.gd            |  6 +++++-
 9 files changed, 38 insertions(+), 19 deletions(-)

diff --git a/scenes/lobby.tscn b/scenes/lobby.tscn
index 8381d61..66f376c 100644
--- a/scenes/lobby.tscn
+++ b/scenes/lobby.tscn
@@ -297,7 +297,7 @@ toggle_mode = false
 enabled_focus_mode = 2
 shortcut = null
 group = null
-text = "Ready!"
+text = "Start!"
 flat = false
 align = 1
 
diff --git a/scripts/heroes/3.gd b/scripts/heroes/3.gd
index b5603af..7f5229e 100644
--- a/scripts/heroes/3.gd
+++ b/scripts/heroes/3.gd
@@ -90,7 +90,7 @@ func set_boosted(node, is_boosted):
 
 sync func merge(node_name):
 	set_boosting(true)
-	var other = $"/root/Level/Players".get_node(node_name)
+	var other = util.get_player(node_name)
 	set_boosted(other, true)
 	merged = other
 
diff --git a/scripts/heroes/4.gd b/scripts/heroes/4.gd
index 179792a..b76049c 100644
--- a/scripts/heroes/4.gd
+++ b/scripts/heroes/4.gd
@@ -60,7 +60,7 @@ func _process(delta):
 
 sync func stun(net_id, position):
 	# Stun the thing!
-	var player = get_node("/root/Level/Players/%s" % net_id)
+	var player = util.get_player(net_id)
 	player.set_linear_velocity(Vector3())
 
 	# Show the beam!
diff --git a/scripts/heroes/5.gd b/scripts/heroes/5.gd
index 72fd2e8..7697230 100644
--- a/scripts/heroes/5.gd
+++ b/scripts/heroes/5.gd
@@ -66,7 +66,7 @@ func flick_input():
 		build_charge(flick_charge)
 
 sync func flick(player_id, towards):
-	var who = $"/root/Level/Players".get_node(player_id)
+	var who = util.get_player(player_id)
 	if who.is_network_master():
 		var direction = towards - who.translation
 		var impulse = direction.normalized() * flick_strength * who.get_mass()
diff --git a/scripts/heroes/5_portal.gd b/scripts/heroes/5_portal.gd
index da361b2..757f7d9 100644
--- a/scripts/heroes/5_portal.gd
+++ b/scripts/heroes/5_portal.gd
@@ -25,7 +25,7 @@ func _exit_tree():
 		other.queue_free()
 
 func init(maker):
-	
+
 	index = maker.placement.placed.size()
 
 	# If index is odd, we're the second (1, 3...), if even, first (0, 4...)
diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index fa2a277..31fff35 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -5,6 +5,7 @@ var port = null # Defined by command-line argument with default
 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():
 
@@ -13,12 +14,12 @@ func _ready():
 	var spectating = util.args.get_value("-silent")
 	get_node("Spectating").pressed = spectating
 	get_node("Spectating").connect("toggled", self, "_set_info_callback", ["spectating"])
-	get_node("Ready").connect("toggled", self, "_set_info_callback", ["ready"])
+	ready_button.connect("toggled", self, "_set_info_callback", ["ready"])
 	start_game_button.connect("pressed", networking, "start_game")
+	# Shown, maybe, in _check_begun
+	start_game_button.hide()
 	if get_tree().is_network_server():
 		start_game_button.show()
-	else:
-		start_game_button.hide()
 
 	if get_tree().is_network_server():
 		# We put level in our players dict because it's automatically broadcast to other players
@@ -40,6 +41,7 @@ func _ready():
 		_connected()
 
 func _connected():
+
 	_send_name()
 	if util.args.get_value("-hero") == "r":
 		hero_select.random_hero()
@@ -66,7 +68,15 @@ func _send_name():
 	var name = get_node("Username").text
 	networking.set_info("username", name)
 
+func _check_begun():
+	var game_started = networking.players[1].begun
+	if game_started:
+		start_game_button.show()
+		# 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:
diff --git a/scripts/networking.gd b/scripts/networking.gd
index 6971a16..9e97536 100644
--- a/scripts/networking.gd
+++ b/scripts/networking.gd
@@ -4,7 +4,6 @@ onready var matchmaking = preload("res://scripts/matchmaking.gd").new()
 
 remote var players = {}
 var players_done = []
-var begun = false
 var global_server_ip = "nv.cosinegaming.com"
 var matchmaker_tcp
 var right_team_next = false
@@ -79,6 +78,7 @@ remote func register_player(new_peer):
 	info.is_right_team = right_team_next
 	info.ready = false
 	info.spectating = false
+	info.begun = false
 	right_team_next = not right_team_next
 	players[new_peer] = info
 	if get_tree().is_network_server():
@@ -104,8 +104,9 @@ remote func register_player(new_peer):
 
 sync func unregister_player(peer):
 	players.erase(peer)
-	if begun:
-		get_node("/root/Level/Players/%d" % peer).queue_free()
+	var p = util.get_player(peer)
+	if p:
+		p.queue_free()
 	emit_signal("info_updated")
 
 sync func _set_info(key, value, peer=0):
@@ -148,20 +149,23 @@ sync func _spawn_player(p):
 	get_node("/root/Level/Players").call_deferred("add_child", player)
 
 sync func _pre_configure_game(level):
-	begun = true
 
 	var self_peer_id = get_tree().get_network_unique_id()
 
-	get_node("/root/Lobby").hide()
+	if not players[self_peer_id].begun:
+		get_node("/root/Lobby").hide()
 
-	var world = load("res://scenes/levels/%d.tscn" % level).instance()
-	get_node("/root").add_child(world)
+		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:
-			_spawn_player(p)
+			var existing_player = util.get_player(p)
+			if not players[self_peer_id].begun or not existing_player:
+				_spawn_player(p)
 
+	set_info("begun", true)
 	rpc_id(1, "_done_preconfiguring", self_peer_id)
 
 sync func _done_preconfiguring(who):
@@ -178,7 +182,7 @@ sync func _post_configure_game():
 			_begin_player_deferred(p)
 
 func _begin_player(peer):
-	get_node("/root/Level/Players/%d" % peer).begin()
+	util.get_player(peer).begin()
 
 remote func _begin_player_deferred(peer):
 	call_deferred("_begin_player", peer)
diff --git a/scripts/player.gd b/scripts/player.gd
index 2adddfa..887a7cb 100644
--- a/scripts/player.gd
+++ b/scripts/player.gd
@@ -56,7 +56,6 @@ func _ready():
 		get_node("TPCamera/Camera/Ray").add_exception(self)
 		get_node(tp_camera).set_enabled(true)
 		get_node(tp_camera).cam_view_sensitivity = 0.05
-		spawn()
 		if "is_ai" in player_info and player_info.is_ai and not ai_instanced:
 			add_child(preload("res://scenes/ai.tscn").instance())
 			ai_instanced = true
@@ -193,6 +192,8 @@ func begin():
 	for mesh in colored_meshes:
 		get_node(mesh).set_surface_material(0, mat)
 
+	spawn()
+
 func toggle_mouse_capture():
 	if (Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED):
 		Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
diff --git a/scripts/util.gd b/scripts/util.gd
index 318a2fa..4a3b62a 100644
--- a/scripts/util.gd
+++ b/scripts/util.gd
@@ -10,7 +10,11 @@ func _ready():
 	args = _get_args()
 
 func get_master_player():
-	var path = "/root/Level/Players/%d" % get_tree().get_network_unique_id()
+	return get_player(get_tree().get_network_unique_id())
+
+func get_player(netid):
+	# We not %d? Because sometimes we need to do get_player(thing.get_name())
+	var path = "/root/Level/Players/%s" % str(netid)
 	if has_node(path):
 		return get_node(path)
 	else:

From fd7b1b9c5eaa0329547c3e3fe0172cb097e73edd Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Tue, 22 May 2018 20:04:36 -0400
Subject: [PATCH 34/40] Add back buttons to all submenus

---
 scenes/custom_game.tscn | 26 +++++++++++++++++++++++++-
 scenes/lobby.tscn       | 24 ++++++++++++++++++++++++
 scenes/menu.tscn        | 24 ++++++++++++++++++++++++
 scripts/custom_game.gd  |  2 +-
 scripts/lobby.gd        |  5 +++++
 scripts/menu.gd         |  1 +
 6 files changed, 80 insertions(+), 2 deletions(-)

diff --git a/scenes/custom_game.tscn b/scenes/custom_game.tscn
index 799f501..1bc0d28 100644
--- a/scenes/custom_game.tscn
+++ b/scenes/custom_game.tscn
@@ -36,7 +36,7 @@ anchor_bottom = 0.0
 margin_left = 498.0
 margin_top = 140.0
 margin_right = 518.0
-margin_bottom = 563.0
+margin_bottom = 481.0
 rect_pivot_offset = Vector2( 0, 0 )
 mouse_filter = 0
 mouse_default_cursor_shape = 0
@@ -205,4 +205,28 @@ 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
+
 
diff --git a/scenes/lobby.tscn b/scenes/lobby.tscn
index 66f376c..2458770 100644
--- a/scenes/lobby.tscn
+++ b/scenes/lobby.tscn
@@ -317,5 +317,29 @@ mouse_default_cursor_shape = 0
 size_flags_horizontal = 1
 size_flags_vertical = 1
 
+[node name="Back" type="Button" parent="." index="10"]
+
+anchor_left = 0.0
+anchor_top = 0.0
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_left = 834.0
+margin_top = 443.0
+margin_right = 978.0
+margin_bottom = 471.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 = "Exit to menu"
+flat = false
+align = 1
+
 
 [editable path="HeroSelect"]
diff --git a/scenes/menu.tscn b/scenes/menu.tscn
index 830d17b..ce87646 100644
--- a/scenes/menu.tscn
+++ b/scenes/menu.tscn
@@ -139,4 +139,28 @@ 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
+
 
diff --git a/scripts/custom_game.gd b/scripts/custom_game.gd
index ec77fb5..996d758 100644
--- a/scripts/custom_game.gd
+++ b/scripts/custom_game.gd
@@ -3,6 +3,7 @@ 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
@@ -11,4 +12,3 @@ func _start_server():
 func _start_client():
 	var ip = get_node("IP").text
 	networking.start_client(ip)
-
diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index 31fff35..d96f8d8 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -16,6 +16,7 @@ func _ready():
 	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")
+	get_node("Back").connect("pressed", self, "_exit_to_menu")
 	# Shown, maybe, in _check_begun
 	start_game_button.hide()
 	if get_tree().is_network_server():
@@ -97,3 +98,7 @@ func render_player_list():
 			list += "\n"
 	get_node("PlayerList").set_text(list)
 
+func _exit_to_menu():
+	get_tree().network_peer.close_connection()
+	get_tree().change_scene("res://scenes/menu.tscn")
+
diff --git a/scripts/menu.gd b/scripts/menu.gd
index 95b6299..e11b912 100644
--- a/scripts/menu.gd
+++ b/scripts/menu.gd
@@ -11,6 +11,7 @@ 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

From 58b2fdf39fdcb9dd7ddc8b07cb76eafbed63fae1 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sat, 26 May 2018 12:31:15 -0400
Subject: [PATCH 35/40] Fix peers not properly sharing other peer's info

---
 scripts/hero_select.gd |  2 +-
 scripts/lobby.gd       | 22 ++++++++++++++--------
 scripts/networking.gd  | 26 ++++++++++++++------------
 3 files changed, 29 insertions(+), 21 deletions(-)

diff --git a/scripts/hero_select.gd b/scripts/hero_select.gd
index 38b2726..ebb7b3c 100644
--- a/scripts/hero_select.gd
+++ b/scripts/hero_select.gd
@@ -26,7 +26,7 @@ func _ready():
 
 func set_hero(hero):
 	select(hero)
-	networking.set_info("hero", hero)
+	networking.set_info_from_server("hero", hero)
 
 func random_hero():
 	var hero = randi() % hero_names.size()
diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index d96f8d8..7aab4cb 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -9,14 +9,20 @@ onready var ready_button = get_node("Ready")
 
 func _ready():
 
+	# Connect (to networking)
 	get_node("Username").connect("text_changed", self, "_send_name")
-
-	var spectating = util.args.get_value("-silent")
-	get_node("Spectating").pressed = spectating
 	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():
@@ -36,18 +42,18 @@ func _ready():
 	else:
 		level_select.hide()
 
-	networking.connect("info_updated", self, "render_player_list")
-	get_tree().connect("connected_to_server", self, "_connected")
 	if get_tree().is_network_server():
 		_connected()
 
 func _connected():
 
 	_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:
-		print(util.args.get_value("-hero"))
 		hero_select.set_hero(int(util.args.get_value("-hero")))
 
 	if util.args.get_value("-start-game"):
@@ -59,7 +65,7 @@ func _set_level(level):
 # 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(key, value)
+	networking.set_info_from_server(key, value)
 
 sync func set_hero(peer, hero):
 	networking.players[peer].hero = hero
@@ -67,7 +73,7 @@ sync func set_hero(peer, hero):
 
 func _send_name():
 	var name = get_node("Username").text
-	networking.set_info("username", name)
+	networking.set_info_from_server("username", name)
 
 func _check_begun():
 	var game_started = networking.players[1].begun
diff --git a/scripts/networking.gd b/scripts/networking.gd
index 9e97536..08eda14 100644
--- a/scripts/networking.gd
+++ b/scripts/networking.gd
@@ -74,13 +74,6 @@ func send_all_info(new_peer):
 				set_info(key, val, p)
 
 remote func register_player(new_peer):
-	var info = {}
-	info.is_right_team = right_team_next
-	info.ready = false
-	info.spectating = false
-	info.begun = false
-	right_team_next = not right_team_next
-	players[new_peer] = info
 	if get_tree().is_network_server():
 		# I tell new player about all the existing people
 		send_all_info(new_peer)
@@ -115,16 +108,25 @@ sync func _set_info(key, value, peer=0):
 		if peer == 0:
 			# Was self. See https://github.com/godotengine/godot/issues/19026
 			peer = get_tree().get_network_unique_id()
-	if players.has(peer):
-		players[peer][key] = value
-		emit_signal("info_updated")
+	if not players.has(peer):
+		players[peer] = {}
+	players[peer][key] = value
+	emit_signal("info_updated")
 
-func set_info(key, value, peer=0):
+master func set_info(key, value, peer=0):
 	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 _on_connect():
-	emit_signal("info_updated")
 	register_player(get_tree().get_network_unique_id())
+	emit_signal("info_updated")
 
 func _check_info():
 	# Check for "everyone is ready"

From 088e18b03c22a3fa549cc31a70c248ba32208815 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sat, 26 May 2018 12:40:53 -0400
Subject: [PATCH 36/40] Assign is_right_team correctly

---
 scripts/networking.gd | 28 +++++++++++-----------------
 1 file changed, 11 insertions(+), 17 deletions(-)

diff --git a/scripts/networking.gd b/scripts/networking.gd
index 08eda14..77a8bba 100644
--- a/scripts/networking.gd
+++ b/scripts/networking.gd
@@ -71,29 +71,23 @@ func send_all_info(new_peer):
 		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)
+
 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)
-	emit_signal("info_updated")
-		# var right_team_count = 0
-		# Send current players' info to new player
-			# if old_peer != new_peer:
-			# 	# We need to assign team later, so count current
-			# 	if players[old_peer].is_right_team:
-			# 		right_team_count += 1
-				# if begun: TODO this should belong to lobby?
-				# 	rpc_id(old_peer, "_spawn_player", new_peer)
-				# 	rpc_id(old_peer, "_begin_player_deferred", new_peer) # Spawning is deferred
-		# var assign_right_team = right_team_count * 2 < players.size()
-		# set_info("is_right_team", assign_right_team, new_peer)
-		# if not begun and players.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")
+		set_info("is_right_team", _right_team_next(), new_peer)
 
 sync func unregister_player(peer):
 	players.erase(peer)

From 39544768f2ff0ba889d6185d54f3ff5198c2b9e1 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sat, 26 May 2018 15:24:54 -0400
Subject: [PATCH 37/40] Fix non-blocking errors that clutter console

---
 scripts/lobby.gd      | 36 +++++++++++++++++++++---------------
 scripts/networking.gd |  4 ++--
 2 files changed, 23 insertions(+), 17 deletions(-)

diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index 7aab4cb..fc00dd2 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -76,11 +76,13 @@ func _send_name():
 	networking.set_info_from_server("username", name)
 
 func _check_begun():
-	var game_started = networking.players[1].begun
-	if game_started:
-		start_game_button.show()
-		# The "Ready" toggle doesn't really make sense on a started game
-		ready_button.hide()
+	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()
@@ -89,17 +91,21 @@ func render_player_list():
 	for p in networking.players:
 		var player = networking.players[p]
 		# A spectating server is just a dedicated server, ignore it
-		if not (player.spectating and p == 1):
-			list += "%-15s " % player.username
-			list += "%-10s " % hero_names[player.hero]
-			var team_format = "%-11s"
-			if player.is_right_team:
-				list += team_format % "Right Team"
-			else:
-				list += team_format % "Left Team"
-			var ready_text = "Ready" if player.ready else ""
+		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.spectating:
+			if player.has("spectating") and player.spectating:
 				list += "Spectating"
 			list += "\n"
 	get_node("PlayerList").set_text(list)
diff --git a/scripts/networking.gd b/scripts/networking.gd
index 77a8bba..955098d 100644
--- a/scripts/networking.gd
+++ b/scripts/networking.gd
@@ -128,8 +128,8 @@ func _check_info():
 	if get_tree().is_network_server():
 		var ready = true
 		for p in players:
-			if not players[p].spectating:
-				if not players[p].ready:
+			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 ready:
 			start_game()

From 8462031d552cf10427cbef7b9c78951b7b8a8c44 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sat, 26 May 2018 15:25:49 -0400
Subject: [PATCH 38/40] Fix spawning infinite players on start

Infinite loop in start_game caused by set_info setting the wrong peer's
info. Making the RPC more explicit about who we're setting resolves
this.
---
 scripts/networking.gd | 32 ++++++++++++++++----------------
 1 file changed, 16 insertions(+), 16 deletions(-)

diff --git a/scripts/networking.gd b/scripts/networking.gd
index 955098d..a53fba3 100644
--- a/scripts/networking.gd
+++ b/scripts/networking.gd
@@ -96,18 +96,15 @@ sync func unregister_player(peer):
 		p.queue_free()
 	emit_signal("info_updated")
 
-sync func _set_info(key, value, peer=0):
-	if not peer:
-		peer = get_tree().get_rpc_sender_id()
-		if peer == 0:
-			# Was self. See https://github.com/godotengine/godot/issues/19026
-			peer = get_tree().get_network_unique_id()
+sync func _set_info(key, value, peer):
 	if not players.has(peer):
 		players[peer] = {}
 	players[peer][key] = value
 	emit_signal("info_updated")
 
 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
@@ -142,13 +139,14 @@ sync func _spawn_player(p):
 	player.set_name(str(p))
 	player.set_network_master(p)
 	player.player_info = players[p]
-	get_node("/root/Level/Players").call_deferred("add_child", player)
+	get_node("/root/Level/Players").add_child(player)
 
 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 players[self_peer_id].begun:
+	if not self_begun:
 		get_node("/root/Lobby").hide()
 
 		var world = load("res://scenes/levels/%d.tscn" % level).instance()
@@ -158,31 +156,33 @@ sync func _pre_configure_game(level):
 	for p in players:
 		if not players[p].spectating:
 			var existing_player = util.get_player(p)
-			if not players[self_peer_id].begun or not existing_player:
+			if not self_begun or not existing_player:
 				_spawn_player(p)
 
-	set_info("begun", true)
+	# 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:
+		print("setting my begun to true")
+		set_info("begun", true)
 	rpc_id(1, "_done_preconfiguring", self_peer_id)
 
 sync func _done_preconfiguring(who):
 	players_done.append(who)
 	if players_done.size() == players.size():
 		print("done")
-		# We call deferred in case singleplayer has placing the player in queue still
-		call_deferred("rpc", "_post_configure_game")
+		rpc("_post_configure_game")
 
 sync func _post_configure_game():
 	# Begin all players (including self)
 	for p in players:
 		if not players[p].spectating:
-			_begin_player_deferred(p)
+			_begin_player(p)
 
 func _begin_player(peer):
 	util.get_player(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()

From 85f6fd897b7837024a564a3b6273fdf0d5bf8358 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sat, 26 May 2018 15:26:58 -0400
Subject: [PATCH 39/40] Make hero description bigger on lobby

---
 scenes/lobby.tscn | 21 ++++++++++-----------
 1 file changed, 10 insertions(+), 11 deletions(-)

diff --git a/scenes/lobby.tscn b/scenes/lobby.tscn
index 2458770..d590f9b 100644
--- a/scenes/lobby.tscn
+++ b/scenes/lobby.tscn
@@ -41,7 +41,6 @@ script = ExtResource( 2 )
 
 [node name="HeroSelect" parent="." index="0" instance=ExtResource( 3 )]
 
-editor/display_folded = true
 margin_left = 30.0
 margin_top = 69.0
 margin_right = 30.0
@@ -62,9 +61,9 @@ visible = false
 [node name="HeroDescription" parent="HeroSelect" index="2"]
 
 margin_left = -2.0
-margin_top = 133.0
+margin_top = 125.0
 margin_right = 366.0
-margin_bottom = 250.0
+margin_bottom = 288.0
 text = "L Let s put oeuntahoeu nthaoeu ntaoheunt aoheunt hoaenth oaentuh Let s put oeuntahoeu nthaoeu ntaoheunt aoheunt hoaenth "
 
 [node name="Title" type="Label" parent="HeroSelect" index="3"]
@@ -95,9 +94,9 @@ anchor_top = 0.0
 anchor_right = 0.0
 anchor_bottom = 0.0
 margin_left = 43.0
-margin_top = 348.0
+margin_top = 391.0
 margin_right = 384.0
-margin_bottom = 381.0
+margin_bottom = 424.0
 rect_pivot_offset = Vector2( 0, 0 )
 focus_mode = 2
 mouse_filter = 0
@@ -128,9 +127,9 @@ anchor_top = 0.0
 anchor_right = 0.0
 anchor_bottom = 0.0
 margin_left = 36.0
-margin_top = 396.0
-margin_right = 226.0
-margin_bottom = 436.0
+margin_top = 439.0
+margin_right = 237.0
+margin_bottom = 479.0
 rect_pivot_offset = Vector2( 0, 0 )
 focus_mode = 2
 mouse_filter = 0
@@ -152,9 +151,9 @@ anchor_top = 0.0
 anchor_right = 0.0
 anchor_bottom = 0.0
 margin_left = 45.0
-margin_top = 486.0
+margin_top = 529.0
 margin_right = 411.0
-margin_bottom = 527.0
+margin_bottom = 570.0
 rect_pivot_offset = Vector2( 0, 0 )
 focus_mode = 2
 mouse_filter = 0
@@ -310,7 +309,7 @@ anchor_bottom = 0.0
 margin_left = 453.0
 margin_top = 50.0
 margin_right = 471.0
-margin_bottom = 538.0
+margin_bottom = 566.0
 rect_pivot_offset = Vector2( 0, 0 )
 mouse_filter = 0
 mouse_default_cursor_shape = 0

From 725b6166577bfe69a7c8155ea6afef8501ea0486 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sat, 26 May 2018 16:11:36 -0400
Subject: [PATCH 40/40] Fix player's color not getting set

---
 scripts/networking.gd | 16 ++++++++++------
 scripts/player.gd     | 10 ++++++----
 2 files changed, 16 insertions(+), 10 deletions(-)

diff --git a/scripts/networking.gd b/scripts/networking.gd
index a53fba3..a1c12e8 100644
--- a/scripts/networking.gd
+++ b/scripts/networking.gd
@@ -141,6 +141,10 @@ sync func _spawn_player(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()
@@ -158,6 +162,10 @@ sync func _pre_configure_game(level):
 			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
@@ -176,12 +184,8 @@ sync func _done_preconfiguring(who):
 
 sync func _post_configure_game():
 	# Begin all players (including self)
-	for p in players:
-		if not players[p].spectating:
-			_begin_player(p)
-
-func _begin_player(peer):
-	util.get_player(peer).begin()
+	# TODO: What do? Maybe, unpause game?
+	pass
 
 sync func reset_state():
 	players_done = []
diff --git a/scripts/player.gd b/scripts/player.gd
index 887a7cb..4d0d66b 100644
--- a/scripts/player.gd
+++ b/scripts/player.gd
@@ -52,6 +52,7 @@ func _ready():
 
 	set_process_input(true)
 	debug_node = get_node("/root/Level/Debug")
+	_set_color()
 	if is_network_master():
 		get_node("TPCamera/Camera/Ray").add_exception(self)
 		get_node(tp_camera).set_enabled(true)
@@ -59,6 +60,7 @@ func _ready():
 		if "is_ai" in player_info and player_info.is_ai and not ai_instanced:
 			add_child(preload("res://scenes/ai.tscn").instance())
 			ai_instanced = true
+		spawn()
 	else:
 		get_node("PlayerName").set_text(player_info.username)
 		# Remove HUD
@@ -175,10 +177,13 @@ func event_to_obj(event):
 	return d
 
 func begin():
+	_set_color()
+
+func _set_color():
 	master_player = util.get_master_player()
 	# Set color to blue (teammate) or red (enemy)
 	var color
-	if master_player and master_player.player_info.is_right_team == player_info.is_right_team:
+	if master_player.player_info.is_right_team == player_info.is_right_team:
 		color = friend_color
 	else:
 		color = enemy_color
@@ -192,8 +197,6 @@ func begin():
 	for mesh in colored_meshes:
 		get_node(mesh).set_surface_material(0, mat)
 
-	spawn()
-
 func toggle_mouse_capture():
 	if (Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED):
 		Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
@@ -305,7 +308,6 @@ sync func switch_hero(hero):
 	get_node("/root/Level/Players").call_deferred("add_child", new_hero)
 	# We must wait until after _ready is called, so that we don't end up at spawn
 	new_hero.call_deferred("set_status", get_status())
-	new_hero.call_deferred("begin")
 	queue_free()
 
 func write_recording():