From c53b01701988e37811dadfd19f7194bc4b015cf1 Mon Sep 17 00:00:00 2001
From: Luna <judahiii@gmail.com>
Date: Sun, 28 Jan 2018 04:03:10 -0500
Subject: [PATCH] Allow shell arguments, add start-multiple.sh

This will make testing MUCH better, I do believe.
---
 scenes/lobby.tscn      |   3 +-
 scripts/args.gd        | 181 +++++++++++++++++++++++++++++++++++++++++++++++++
 scripts/lobby.gd       |  64 +++++++++++++++--
 util/start-multiple.sh |  17 +++++
 4 files changed, 259 insertions(+), 6 deletions(-)
 create mode 100644 scripts/args.gd
 create mode 100644 util/start-multiple.sh

diff --git a/scenes/lobby.tscn b/scenes/lobby.tscn
index 1e98712..deb5ee5 100644
--- a/scenes/lobby.tscn
+++ b/scenes/lobby.tscn
@@ -60,6 +60,7 @@ align = 1
 
 [node name="ServerStart" type="Button" parent="." index="1"]
 
+visible = false
 anchor_left = 0.0
 anchor_top = 0.0
 anchor_right = 0.0
@@ -104,7 +105,7 @@ action_mode = 0
 enabled_focus_mode = 2
 shortcut = null
 group = null
-text = "Platforms"
+text = "Platform map"
 flat = false
 align = 0
 selected = 0
diff --git a/scripts/args.gd b/scripts/args.gd
new file mode 100644
index 0000000..1b3943b
--- /dev/null
+++ b/scripts/args.gd
@@ -0,0 +1,181 @@
+# Adapted from: https://gist.github.com/bitwes/fe1e2aef8da0940104c8
+# Example of a script that can be run from the command line and parses out options.  This is adapted
+# from the Gut command line interface.  The first 2 classes are not to be used directly, they are used
+# by the Options class and can be ignored.  Start reading after the Options class to get a feel for
+# how it is used, then work backwards.
+#
+# This could be easily extracted out into a class itself, but in the interest of how it is being used
+# I wanted it all in one file.  It is yours to do with what you please, but if you make something out
+# of it, I'd love to hear about it.  I'm bitwes on godot forums, github, and bitbucket.
+extends Control
+
+#-------------------------------------------------------------------------------
+# Parses the command line arguments supplied into an array that can then be
+# examined and parsed based on how the gut options work.
+#-------------------------------------------------------------------------------
+class CmdLineParser:
+	var _opts = []
+	
+	func _init():
+		for i in range(OS.get_cmdline_args().size()):
+			_opts.append(OS.get_cmdline_args()[i])
+			
+	# Search _opts for an element that starts with the option name
+	# specified.
+	func find_option(name):
+		var found = false
+		var idx = 0
+		
+		while(idx < _opts.size() and !found):
+			if(_opts[idx].find(name) == 0):
+				found = true
+			else:
+				idx += 1
+				
+		if(found):
+			return idx
+		else:
+			return -1
+
+	# Parse out the value of an option.  Values are seperated from
+	# the option name with "="
+	func get_option_value(full_option):
+		var split = full_option.split('=')
+	
+		if(split.size() > 1):
+			return split[1]
+		else:
+			return null
+	
+	# Parse out multiple comma delimited values from a command line
+	# option.  Values are separated from option name with "=" and 
+	# additional values are comma separated.
+	func get_option_array_value(full_option):
+		var value = get_option_value(full_option)
+		var split = value.split(',')
+		return split
+	
+	func get_array_value(option):
+		var to_return = []
+		var opt_loc = find_option(option)
+		if(opt_loc != -1):
+			to_return = get_option_array_value(_opts[opt_loc])
+			_opts.remove(opt_loc)
+	
+		return to_return
+	
+	# returns the value of an option if it was specfied, otherwise
+	# it returns the default.
+	func get_value(option, default):
+		var to_return = default
+		var opt_loc = find_option(option)
+		if(opt_loc != -1):
+			to_return = get_option_value(_opts[opt_loc])
+			_opts.remove(opt_loc)
+	
+		return to_return
+	
+	# returns true if it finds the option, false if not.
+	func was_specified(option):
+		var opt_loc = find_option(option)
+		if(opt_loc != -1):
+			_opts.remove(opt_loc)
+		
+		return opt_loc != -1	
+
+#-------------------------------------------------------------------------------
+# Simple class to hold a command line option
+#-------------------------------------------------------------------------------
+class Option:
+	var value = null
+	var option_name = ''
+	var default = null
+	var description = ''
+	
+	func _init(name, default_value, desc=''):
+		option_name = name
+		default = default_value
+		description = desc
+		value = default_value
+	
+	func pad(value, size, pad_with=' '):
+		var to_return = value
+		for i in range(value.length(), size):
+			to_return += pad_with
+		
+		return to_return
+		
+	func to_s(min_space=0):
+		var subbed_desc = description
+		if(subbed_desc.find('[default]') != -1):
+			subbed_desc = subbed_desc.replace('[default]', str(default))
+		return pad(option_name, min_space) + subbed_desc
+		
+		
+#-------------------------------------------------------------------------------
+# The high level interface between this script and the command line options 
+# supplied.  Uses Option class and CmdLineParser to extract information from
+# the command line and make it easily accessible.
+#-------------------------------------------------------------------------------
+class Options:
+	var options = []
+	var _opts = []
+	var _banner = ''
+	
+	func add(name, default, desc):
+		options.append(Option.new(name, default, desc))
+	
+	func get_value(name):
+		var found = false
+		var idx = 0
+		
+		while(idx < options.size() and !found):
+			if(options[idx].option_name == name):
+				found = true
+			else:
+				idx += 1
+		
+		if(found):
+			return options[idx].value
+		else:
+			print("COULD NOT FIND OPTION " + name)
+			return null
+	
+	func set_banner(banner):
+		_banner = banner
+		
+	func print_help():
+		var longest = 0
+		for i in range(options.size()):
+			if(options[i].option_name.length() > longest):
+				longest = options[i].option_name.length()
+	
+		print('---------------------------------------------------------')
+		print(_banner)
+		
+		print("\nOptions\n-------")
+		for i in range(options.size()):
+			print('  ' + options[i].to_s(longest + 2))
+		print('---------------------------------------------------------')
+	
+	func print_options():
+		for i in range(options.size()):
+			print(options[i].option_name + '=' + str(options[i].value))
+
+	func parse():
+		var parser = CmdLineParser.new()
+		
+		for i in range(options.size()):
+			var t = typeof(options[i].default)
+			if(t == TYPE_INT):
+				options[i].value = int(parser.get_value(options[i].option_name, options[i].default))
+			elif(t == TYPE_STRING):
+				options[i].value = parser.get_value(options[i].option_name, options[i].default)
+			elif(t == TYPE_ARRAY):
+				options[i].value = parser.get_array_value(options[i].option_name)
+			elif(t == TYPE_BOOL):
+				options[i].value = parser.was_specified(options[i].option_name)
+			elif(t == TYPE_NIL):
+				print(options[i].option_name + ' cannot be processed, it has a nil datatype')
+			else:
+				print(options[i].option_name + ' cannot be processsed, it has unknown datatype:' + str(t))
diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index 6f274f3..f486e9d 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -1,4 +1,4 @@
-extends Control
+extends "res://scripts/args.gd"
 
 # class member variables go here, for example:
 # var a = 2
@@ -10,7 +10,56 @@ var SERVER_PLAYING = true
 var player_info = {}
 var my_info = {}
 
+func setup_options():
+	var opts = Options.new()
+	opts.set_banner(('A non-violent MOBA inspired by Overwatch and Zineth'))
+	opts.add('-singleplayer', false, 'Whether to run singeplayer, starting immediately')
+	opts.add('-server', false, 'Whether to run as server')
+	opts.add('-client', false, 'Immediately connect as client')
+	opts.add('-silent-server', false, 'If the server is not playing, merely serving')
+	opts.add('-hero', 'r', 'Your choice of hero (index)')
+	opts.add('-level', 'r', 'Your choice of level (index) - server only!')
+	opts.add('-start-game', false, 'Join as a client and immediately start the game')
+	opts.add('-h', false, "Print help")
+	return opts
+
+func option_sel(button_name, option):
+	var button = get_node(button_name)
+	print("-->")
+	if option == "r":
+		option = randi() % button.get_item_count()
+		print(randi() % 3)
+	else:
+		option = int(option)
+	button.select(option)
+
 func _ready():
+
+	var o = setup_options()
+	o.parse()
+	
+	randomize()
+
+	if o.get_value("-silent-server"):
+		SERVER_PLAYING = false # TODO: Uncaps :(
+	if o.get_value("-hero"):
+		option_sel("HeroSelect", o.get_value("-hero"))
+		print(get_node("HeroSelect").get_selected_id())
+	if o.get_value("-level"):
+		option_sel("ServerStart/LevelSelect", o.get_value("-level"))
+	if o.get_value("-server"):
+		call_deferred("_server_init")
+	if o.get_value("-client"):
+		call_deferred("_client_init")
+	if o.get_value("-start-game"):
+		my_info.start_game = true
+		call_deferred("_client_init")
+	if o.get_value("-singleplayer"):
+		call_deferred("_singleplayer_init")
+	if o.get_value('-h'):
+		o.print_help()
+		quit()
+
 	# Called every time the node is added to the scene.
 	# Initialization here
 	get_node("Server").connect("pressed", self, "_server_init")
@@ -54,11 +103,16 @@ func _player_connected(id):
 
 func _connected_ok():
 	rpc("register_player", get_tree().get_network_unique_id(), my_info)
+	if "start_game" in my_info:
+		rpc_id(1, "start_game")
 
 func collect_info():
-	my_info.username = get_node("Username").get_text()
-	my_info.hero = get_node("HeroSelect").get_selected_id()
-	my_info.is_right_team = false # Server assigns team, wait for that
+	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").get_selected_id()
+	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
@@ -113,7 +167,7 @@ func render_player_list():
 		list += "\n"
 	get_node("PlayerList").set_text(list)
 
-func start_game():
+sync func start_game():
 	var level = get_node("ServerStart/LevelSelect").get_selected_id()
 	rpc("pre_configure_game", level)
 
diff --git a/util/start-multiple.sh b/util/start-multiple.sh
new file mode 100644
index 0000000..e9470a7
--- /dev/null
+++ b/util/start-multiple.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+count=$1
+if [[ $count ]]; then
+  shift # Only do this if arg exists (fixes error)
+else
+  count=2 # Default 2
+fi
+
+godot -server "$@" &
+for i in `seq 3 $count` # 3, for 1 + the server + the starter
+do
+  godot -client "$@" &
+done
+sleep 1
+godot -start-game "$@" &
+