aboutsummaryrefslogtreecommitdiff
path: root/game/src
diff options
context:
space:
mode:
author Spartan322 <Megacake1234@gmail.com>2023-04-29 08:49:48 +0200
committer Spartan322 <Megacake1234@gmail.com>2023-05-02 01:00:09 +0200
commit865e7aeca7c3090fee914b9ebde9490ef23d3559 (patch)
tree7836ab6e2e5bd751c226d86646883f8424577a7c /game/src
parent10053cf259c55ee45803268a844edf1011d8a16b (diff)
Add ArgumentParser
Streamlines parsing of commandline arguments Arguments reside in ProjectSettings as `openvic2/data/arguments` as a dictionary The dictionary's key is the option name The dictionary is set with default values This enables project setting overrides for arguments (user specified arguments take priority) Add help commandline option Prepare for removal of GameDebug.gd Add game project description
Diffstat (limited to 'game/src')
-rw-r--r--game/src/ArgumentOption.gd60
-rw-r--r--game/src/ArgumentParser.gd259
-rw-r--r--game/src/ArgumentParser.tscn24
-rw-r--r--game/src/GameStart.tscn5
4 files changed, 347 insertions, 1 deletions
diff --git a/game/src/ArgumentOption.gd b/game/src/ArgumentOption.gd
new file mode 100644
index 0000000..f14cef0
--- /dev/null
+++ b/game/src/ArgumentOption.gd
@@ -0,0 +1,60 @@
+@tool
+class_name ArgumentOption
+extends Resource
+
+@export var name : StringName
+@export var aliases : Array[StringName] = []
+@export var type : Variant.Type :
+ get: return type
+ set(v):
+ type = v
+ match v:
+ TYPE_BOOL: default_value = false
+ TYPE_INT: default_value = 0
+ TYPE_FLOAT: default_value = 0.0
+ TYPE_STRING: default_value = ""
+ TYPE_STRING_NAME: default_value = &""
+ TYPE_COLOR: default_value = Color()
+ _: default_value = null
+ notify_property_list_changed()
+var default_value
+@export var description : String
+
+func _init(_name = "", _type = TYPE_NIL, _description = "", default = null):
+ name = _name
+ type = _type
+ if default != null and typeof(default) == type:
+ default_value = default
+ description = _description
+
+func add_alias(alias : StringName) -> ArgumentOption:
+ aliases.append(alias)
+ return self
+
+func get_type_string() -> StringName:
+ match type:
+ TYPE_NIL: return "null"
+ TYPE_BOOL: return "boolean"
+ TYPE_INT: return "integer"
+ TYPE_FLOAT: return "float"
+ TYPE_STRING, TYPE_STRING_NAME: return "string"
+ TYPE_COLOR: return "color"
+ return "<invalid type>"
+
+func _get(property):
+ if property == "default_value": return default_value
+
+func _set(property, value):
+ if property == "default_value":
+ default_value = value
+ return true
+
+func _get_property_list():
+ var properties := []
+
+ properties.append({
+ "name": "default_value",
+ "type": type
+ })
+
+ return properties
diff --git a/game/src/ArgumentParser.gd b/game/src/ArgumentParser.gd
new file mode 100644
index 0000000..3f77919
--- /dev/null
+++ b/game/src/ArgumentParser.gd
@@ -0,0 +1,259 @@
+@tool
+extends Node
+
+const argument_setting_path := &"openvic2/data/arguments"
+
+@export var option_array : Array[ArgumentOption] = [
+ ArgumentOption.new(
+ "help",
+ TYPE_BOOL,
+ "Displays help and quits.",
+ false
+ ).add_alias(&"h")
+]
+
+const color_name_array : PackedStringArray =[
+ "aliceblue", "antiquewhite", "aqua", "aquamarine",
+ "azure", "beige", "bisque", "black", "blanchedalmond",
+ "blue", "blueviolet", "brown", "burlywood", "cadetblue",
+ "chartreuse", "chocolate", "coral", "cornflower", "cornsilk",
+ "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod",
+ "darkgray", "darkgreen", "darkkhaki", "darkmagenta", "darkolivegreen",
+ "darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen",
+ "darkslateblue", "darkslategray", "darkturquoise", "darkviolet",
+ "deeppink", "deepskyblue", "dimgray", "dodgerblue", "firebrick",
+ "floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite",
+ "gold", "goldenrod", "gray", "green", "greenyellow", "honeydew",
+ "hotpink", "indianred", "indigo", "ivory", "khaki", "lavender",
+ "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral",
+ "lightcyan", "lightgoldenrod", "lightgray", "lightgreen", "lightpink",
+ "lightsalmon", "lightseagreen", "lightskyblue", "lightslategray",
+ "lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta",
+ "maroon", "mediumaquamarine", "mediumblue", "mediumorchid",
+ "mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen",
+ "mediumturquoise", "mediumvioletred", "midnightblue", "mintcream",
+ "mistyrose", "moccasin", "navajowhite", "navyblue", "oldlace", "olive",
+ "olivedrab", "orange", "orangered", "orchid", "palegoldenrod",
+ "palegreen", "paleturquoise", "palevioletred", "papayawhip",
+ "peachpuff", "peru", "pink", "plum", "powderblue", "purple",
+ "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown",
+ "salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver",
+ "skyblue", "slateblue", "slategray", "snow", "springgreen", "steelblue",
+ "tan", "teal", "thistle", "tomato", "transparent", "turquoise", "violet",
+ "webgray", "webgreen", "webmaroon", "webpurple", "wheat", "white",
+ "whitesmoke", "yellow", "yellowgreen"
+]
+
+func _parse_value(arg_name : StringName, value_string : String, type : Variant.Type) -> Variant:
+ match type:
+ TYPE_NIL: return null
+ TYPE_BOOL:
+ value_string = value_string.to_lower()
+ if value_string == "true" or value_string == "t" or value_string == "yes" or value_string == "y":
+ return true
+ if value_string == "false" or value_string == "f" or value_string == "no" or value_string == "n":
+ return false
+ return true
+ TYPE_INT:
+ return value_string.to_int()
+ TYPE_FLOAT:
+ return value_string.to_float()
+ TYPE_STRING, TYPE_STRING_NAME:
+ return value_string
+ TYPE_COLOR:
+ if Color.html_is_valid(value_string) or value_string.to_lower() in color_name_array:
+ return Color.from_string(value_string, Color())
+ push_error("'%s' must be an html Color or Color name, '%s' is an invalid value." % [arg_name, value_string])
+ return null
+ # Unsupported types
+ TYPE_VECTOR2, \
+ TYPE_VECTOR2I, \
+ TYPE_VECTOR3, \
+ TYPE_VECTOR3I, \
+ TYPE_VECTOR4, \
+ TYPE_VECTOR4I, \
+ TYPE_RECT2, \
+ TYPE_RECT2I:
+ push_warning("Value type '%s' may not be supported." % type)
+ var data_array = value_string.lstrip("(").rstrip(")").split(",", false)
+ for index in range(data_array.size()):
+ data_array[index] = " " + data_array[index].strip_edges()
+ match type:
+ TYPE_VECTOR2:
+ if data_array.size() != 2:
+ push_error("'%s' value must be a Vector2, '%s' is an invalid value." % [arg_name, value_string])
+ return null
+ return str_to_var("Vector2(%s )" % ",".join(data_array))
+ TYPE_VECTOR2I:
+ if data_array.size() != 2:
+ push_error("'%s' value must be a Vector2i, '%s' is an invalid value." % [arg_name, value_string])
+ return null
+ return str_to_var("Vector2i(%s )" % ",".join(data_array))
+ TYPE_VECTOR3:
+ if data_array.size() != 2:
+ push_error("'%s' value must be a Vector3, '%s' is an invalid value." % [arg_name, value_string])
+ return null
+ return str_to_var("Vector3(%s )" % ",".join(data_array))
+ TYPE_VECTOR3I:
+ if data_array.size() != 2:
+ push_error("'%s' value must be a Vector3i, '%s' is an invalid value." % [arg_name, value_string])
+ return null
+ return str_to_var("Vector3i(%s )" % ",".join(data_array))
+ TYPE_VECTOR4:
+ if data_array.size() != 2:
+ push_error("'%s' value must be a Vector4, '%s' is an invalid value." % [arg_name, value_string])
+ return null
+ return str_to_var("Vector4(%s )" % ",".join(data_array))
+ TYPE_VECTOR4I:
+ if data_array.size() != 2:
+ push_error("'%s' value must be a Vector4i, '%s' is an invalid value." % [arg_name, value_string])
+ return null
+ return str_to_var("Vector4i(%s )" % ",".join(data_array))
+ TYPE_RECT2:
+ if data_array.size() != 2:
+ push_error("'%s' value must be a Rect2, '%s' is an invalid value." % [arg_name, value_string])
+ return null
+ return str_to_var("Rect2(%s )" % ",".join(data_array))
+ TYPE_RECT2I:
+ if data_array.size() != 2:
+ push_error("'%s' value must be a Rect2i, '%s' is an invalid value." % [arg_name, value_string])
+ return null
+ return str_to_var("Rect2i(%s )" % ",".join(data_array))
+ _:
+ push_error("'%s' value of type '%s' requested but could not be parsed." % [arg_name, type])
+ return null
+
+ return null
+
+# Missing types
+# TYPE_TRANSFORM2D = 11
+# TYPE_VECTOR4 = 12
+# TYPE_VECTOR4I = 13
+# TYPE_PLANE = 14
+# TYPE_QUATERNION = 15
+# TYPE_AABB = 16
+# TYPE_BASIS = 17
+# TYPE_TRANSFORM3D = 18
+# TYPE_PROJECTION = 19
+# TYPE_NODE_PATH = 22
+# TYPE_RID = 23
+# TYPE_OBJECT = 24
+# TYPE_CALLABLE = 25
+# TYPE_SIGNAL = 26
+# TYPE_DICTIONARY = 27
+# TYPE_ARRAY = 28
+# TYPE_PACKED_BYTE_ARRAY = 29
+# TYPE_PACKED_INT32_ARRAY = 30
+# TYPE_PACKED_INT64_ARRAY = 31
+# TYPE_PACKED_FLOAT32_ARRAY = 32
+# TYPE_PACKED_FLOAT64_ARRAY = 33
+# TYPE_PACKED_STRING_ARRAY = 34
+# TYPE_PACKED_VECTOR2_ARRAY = 35
+# TYPE_PACKED_VECTOR3_ARRAY = 36
+# TYPE_PACKED_COLOR_ARRAY = 37
+
+func _parse_argument_list(dictionary : Dictionary, arg_list : PackedStringArray) -> Dictionary:
+ var current_key : String = ""
+ var current_option : ArgumentOption = null
+ for arg in arg_list:
+ if current_option != null and not arg.begins_with("-"):
+ var result = _parse_value(current_key, arg, current_option.type)
+ if result != null:
+ dictionary[current_option.name] = result
+ current_option = null
+ continue
+
+ if current_option != null:
+ push_warning("Valid argument '%s' was not set as a value, skipping." % current_key)
+
+ if arg.begins_with("-"):
+ current_option = null
+ arg = arg.substr(1)
+ var key := &""
+ var value := &""
+
+ # Support for Unix shorthand of multiple boolean arguments
+ # eg: "-abc" means a == true, b == true, c == true
+ if arg.length() > 1 and arg[0] != "-" and arg[1] != "=":
+ for c in arg:
+ for o in option_array:
+ if o.aliases.any(func(v): return c == v):
+ dictionary[o.name] = true
+ continue
+
+ # Support for = key/value split
+ # eg: "-v=5" and "--value=5" means v == 5 and value == 5
+ var first_equal := arg.find("=")
+ if first_equal > -1:
+ key = arg.substr(0, first_equal - 1)
+ value = arg.substr(first_equal + 1)
+ else:
+ key = arg
+
+ # Removes - for full name arguments
+ if key.begins_with("-"):
+ key = key.substr(1)
+
+ for o in option_array:
+ if key == o.name or o.aliases.any(func(v): return key == v):
+ current_option = o
+ break
+
+ if current_option == null:
+ push_warning("Invalid argument '%s' found, skipping." % key)
+ continue
+
+ current_key = key
+ var arg_result = _parse_value(key, value, current_option.type)
+ if arg_result != null:
+ dictionary[current_option.name] = arg_result
+ current_option = null
+
+ return dictionary
+
+func _print_help():
+ var project_name : StringName = ProjectSettings.get_setting_with_override(&"application/config/name")
+ var project_version : String = _GIT_INFO_.tag
+ var project_hash : String = _GIT_INFO_.short_hash
+ var project_website : String = "https://openvic2.com"
+ var project_description : String = ProjectSettings.get_setting_with_override(&"application/config/description")
+ print_rich(
+"""
+%s - %s - %s - %s
+%s
+
+%s
+
+Options:
+"""
+ % [
+ project_name,
+ project_version,
+ project_hash,
+ project_website,
+ project_description,
+ "usage: %s [options]" % OS.get_executable_path().get_file()
+ ]
+ )
+ for option in option_array:
+ print_rich(" --%s%s%s" % [
+ (option.name + (",-%s" % (",-".join(option.aliases)) if option.aliases.size() > 0 else "")).rpad(45),
+ ("Type: %s - Default Value: %s" % [option.get_type_string(), option.default_value]).rpad(45),
+ option.description
+ ])
+func _ready():
+ if Engine.is_editor_hint(): return
+
+ var argument_dictionary : Dictionary = {}
+ if ProjectSettings.has_setting(argument_setting_path):
+ argument_dictionary = ProjectSettings.get_setting_with_override(argument_setting_path)
+ for option in option_array:
+ argument_dictionary[option.name] = option.default_value
+
+ _parse_argument_list(argument_dictionary, OS.get_cmdline_args())
+ _parse_argument_list(argument_dictionary, OS.get_cmdline_user_args())
+
+ ProjectSettings.set_setting(argument_setting_path, argument_dictionary)
+ if argument_dictionary[&"help"]:
+ _print_help()
+ get_tree().quit()
diff --git a/game/src/ArgumentParser.tscn b/game/src/ArgumentParser.tscn
new file mode 100644
index 0000000..964c19e
--- /dev/null
+++ b/game/src/ArgumentParser.tscn
@@ -0,0 +1,24 @@
+[gd_scene load_steps=5 format=3 uid="uid://dayjmgc34tqo6"]
+
+[ext_resource type="Script" path="res://src/ArgumentParser.gd" id="1_8e7gi"]
+[ext_resource type="Script" path="res://src/ArgumentOption.gd" id="2_f3c26"]
+
+[sub_resource type="Resource" id="Resource_tq3y4"]
+script = ExtResource("2_f3c26")
+name = &"help"
+aliases = Array[StringName]([&"h"])
+type = 1
+description = "Displays help and quits."
+default_value = false
+
+[sub_resource type="Resource" id="Resource_j1to4"]
+script = ExtResource("2_f3c26")
+name = &"game-debug"
+aliases = Array[StringName]([&"d", &"-debug", &"-debug-mode"])
+type = 1
+description = "Start in debug mode."
+default_value = false
+
+[node name="ArgumentParser" type="Node"]
+script = ExtResource("1_8e7gi")
+option_array = Array[ExtResource("2_f3c26")]([SubResource("Resource_tq3y4"), SubResource("Resource_j1to4")])
diff --git a/game/src/GameStart.tscn b/game/src/GameStart.tscn
index 2046bb5..d5f9d45 100644
--- a/game/src/GameStart.tscn
+++ b/game/src/GameStart.tscn
@@ -1,5 +1,6 @@
-[gd_scene load_steps=6 format=3 uid="uid://1udsn4mggep2"]
+[gd_scene load_steps=7 format=3 uid="uid://1udsn4mggep2"]
+[ext_resource type="PackedScene" uid="uid://dayjmgc34tqo6" path="res://src/ArgumentParser.tscn" id="1_oe61r"]
[ext_resource type="PackedScene" uid="uid://o4u142w4qkln" path="res://src/GameMenu.tscn" id="1_wlojq"]
[ext_resource type="Script" path="res://src/SplashContainer.gd" id="2_xmcgv"]
[ext_resource type="Texture2D" uid="uid://deef5hufq0j61" path="res://splash_assets/splash_end.png" id="3_qfv12"]
@@ -14,6 +15,8 @@ anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
+[node name="ArgumentParser" parent="." instance=ExtResource("1_oe61r")]
+
[node name="GameMenu" parent="." instance=ExtResource("1_wlojq")]
visible = false
layout_mode = 1