diff --git a/godot_ship/script/debug/debug_menu.gd b/godot_ship/script/debug/debug_menu.gd index ef2950c..a2d1c45 100644 --- a/godot_ship/script/debug/debug_menu.gd +++ b/godot_ship/script/debug/debug_menu.gd @@ -1,5 +1,7 @@ extends Control +# Hello, god class. Though, as an optional module, it's not the worst it could be. +# Smells of feature creep, because it absolutely is one. var debug_canvas @@ -14,39 +16,46 @@ var history_pos = 0 # Controls whether to print to the screen var echo = true +# Controls whether the player is allowed to cheat: +var cheats = false +var cheat_code = "989172bdaff124fc237b3f904a1886b91dc3ae718da15a6055ff416284f39a58" + onready var expression = Expression.new() -# helptext: args list and help blurb accessed by function name -var helptext = { -# command_id [args "Help text" ] - "command_help": [" [command]", "Print information about command.\n" ], - "command_history": ["", "Print the history log.\n" ], - "command_perf": [" stat", "Print performance info (fps, nodes, proctime, ... )\n" ], +# metadata: args list, help blurb, and cheatmap accessed by function name +enum {ARGS, HELPTEXT, IS_CHEAT} +var command_metadata = { +# command_id [args "Help text" is cheat] + "command_help": [" [command]", "Print information about command.\n", false], + "command_history": ["", "Print the history log.\n", false], + "command_perf": [" stat", "Print performance info (fps, nodes, proctime, ... )\n", false], - "command_list": [" [path]", "List children of path, or of present working node.\n" ], - "command_start": [" filename", "Load PackedScene filename.tscn as child.\n" ], - "command_kill": [" name", "Kill child node with matching name.\n" ], + "command_list": [" [path]", "List children of path, or of present working node.\n", false], + "command_start": [" filename", "Load PackedScene filename.tscn as child.\n", true ], + "command_kill": [" name", "Kill child node with matching name.\n", true ], - "command_pwd": ["", "Print the Present Working Node.\n" ], - "command_cd": [" path", "Change the Present Working Node to path.\n" ], + "command_pwd": ["", "Print the Present Working Node.\n", false], + "command_cd": [" path", "Change the Present Working Node to path.\n", false], - "command_print": [" string", "Print string to the in-game debug console.\n" ], - "command_clear": ["", "Clear the debug output.\n" ], + "command_print": [" string", "Print string to the in-game debug console.\n", false], + "command_clear": ["", "Clear the debug output.\n", false], # !EXTREMELY DANGER { - "command_emit": [" signal [message]", "Emit a message on MessageBus.signal without validation.\n"], - "command_call": [" func [args ...]", "Call func(...) with arguments args.\n" ], - "command_exec": [" expression ...", "Evaluate an arbitrary expression, and print the result.\n"], + "command_emit": [" signal [message]", "Emit a message on MessageBus.signal without validation.\n", true ], + "command_call": [" func [args ...]", "Call func(...) with arguments args.\n", true ], + "command_exec": [" expression ...", "Evaluate an arbitrary expression, and print the result.\n", true ], # } - "command_listprops": ["", "List properties of the Present Working Node\n" ], - "command_getprop": [" prop", "Get the value of property prop\n" ], - "command_setprop": [" prop value", "Set the property prop to value.\n" ], + "command_listprops": ["", "List properties of the Present Working Node\n", true ], + "command_getprop": [" prop", "Get the value of property prop\n", true ], + "command_setprop": [" prop value", "Set the property prop to value.\n", true ], - "command_script": [" path", "Load and execute a script at user://scripts/\n" ], - "command_echo": [" on/off", "Controls whether lines should be printed to the screen\n" ], - "command_restart": ["", "Kill the current scene tree and plant a new Root.\n" ], - "command_exit": ["", "Quits the program.\n" ], + "command_script": [" path", "Load and execute a script at user://scripts/\n", false], + "command_echo": [" on/off", "Controls whether lines should be printed to the screen\n", true ], + "command_cheat": [" [password]", "Controls whether cheats are enabled, using a fun code\n", false], - "command_empty": ["", "No Operation.\n" ], + "command_restart": ["", "Kill the current scene tree and plant a new Root.\n", true ], + "command_exit": ["", "Quits the program.\n", false], + + "command_empty": ["", "No Operation.\n", false], } # List of debug commands accessed by alias @@ -77,6 +86,8 @@ var commands = { ["script", "sh"]: "command_script", ["@echo"]: "command_echo", + ["cheat", "*"]: "command_cheat", + ["restart", "killall"]: "command_restart", ["exit", "quit"]: "command_exit", @@ -177,10 +188,19 @@ func _on_LineEdit_text_entered(line): # returns: name of executed function, or null if nothing executed func execute_command(command): var command_func = parse(command[0]) - if command_func: + if command_func and is_command_allowed(command_func): call(command_func, command) + else: + command_func = null return command_func +# is_command_allowed: Test if the command is allowed (not cheating, or cheating is on) +# params: command_func: the name of the function to be executed +# returns: true if command is valid, or false if command is cheat and cheating is disabled +func is_command_allowed(command_func): + # return true if cheats or on, or command is not cheat + return cheats or not command_metadata[command_func][IS_CHEAT] + # History_related helper functions: # history_append: add a line of text to the history # params: text: line of text (unparsed command) to add to history @@ -262,7 +282,7 @@ func get_canonical(alias): # params: alias: alias of a command # returns: usage string for the command, formatted for printing func get_usage(alias): - return "Usage: " + alias + helptext[parse(alias)][0] + "\n" + return "Usage: " + alias + command_metadata[parse(alias)][0] + "\n" # String casting functions # variant_to_string: Cast arbitrary GDScript Variant to String @@ -321,7 +341,7 @@ func string_to_variant(string, type): TYPE_COLOR_ARRAY: res = PoolColorArray(list) _: - debug_print_line("No cast from String to " + types[typeof(type)] + "\n") + debug_print_line("No cast from String to %s\n" % types[typeof(type)]) return res # listify_string: takes a string and turns it into a list, by splitting on commas and/or spaces @@ -342,9 +362,9 @@ func listify_string(string): # start: Loads scene from res://scenes/*.tscn by filename, and starts it func command_start (command): if command.size() > 1: - var pack = load("res://scenes/" + command[1] + ".tscn"); + var pack = load("res://scenes/%s.tscn" % command[1]) get_pwn().add_child(pack.instance()); - debug_print_line("started '" + command[1] + "'\n") + debug_print_line("started '%s'\n" % command[1]) else: debug_print_line(get_usage(command[0])) @@ -357,9 +377,9 @@ func command_kill (command): debug_print_line("YOU DIDN'T SAY THE MAGIC WORD!\n") else: node.queue_free() - debug_print_line(command[1] + " killed\n") + debug_print_line("%s killed\n" % command[1]) else: - debug_print_line(command[0] + ": " + command[1] + " not found.\n") + debug_print_line("%s: %s not found.\n" % [command[0], command[1]]) else: debug_print_line(get_usage(command[0])) @@ -393,10 +413,10 @@ func command_emit (command): var mbus_signal = command[1].split(' ', true, 1) match mbus_signal.size(): 2: - debug_print_line("Message: " + String(mbus_signal) + "\n") + debug_print_line("Message: %s (%s)" % mbus_signal) MessageBus.emit_signal(mbus_signal[0], mbus_signal[1]) 1: - debug_print_line("Message: " + String(mbus_signal) + "\n") + debug_print_line("Message: %s" % mbus_signal) MessageBus.emit_signal(mbus_signal[0]) 0: debug_print_line(get_usage(command[0])) else: @@ -426,18 +446,18 @@ func command_help (command): if (command.size() == 1): debug_print_line("Valid commands:\n") for key in commands: - debug_print_line(key[0] + " ") + # if command is allowed in current context, print it + if is_command_allowed(commands[key]): + debug_print_line(key[0] + " ") debug_print_line("\n") else: var command_func = parse(command[1]) - var aliases - var text - if command_func in helptext: - text = helptext[command_func] - aliases = lookup(command[1]) - debug_print_line(command[1] + text[0] + ":\n Aliases: " + String(aliases) + "\n "+ text[1]) + if command_func in command_metadata and is_command_allowed(command_func): + var text = command_metadata[command_func] + var aliases = String(lookup(command[1])) + debug_print_line("%s%s:\n Aliases: %s\n %s" % [command[1], text[ARGS], aliases, text[HELPTEXT]]) else: - debug_print_line(get_canonical(command[0]) + ": command not found: " + command[1] + "\n") + debug_print_line("%s: command not found: %s\n" % [command[0], command[1]]) # exit: request program exit func command_exit(_command): @@ -457,7 +477,7 @@ func command_call(command): debug_print_line("We're sorry, but your call could not be completed as dialed.\n" + "Please hang up and try your call again.\n") return - debug_print_line(variant_to_string(call_ret) + "\n") + debug_print_line("%s\n" % variant_to_string(call_ret)) else: debug_print_line(get_usage(command[0])) @@ -469,11 +489,11 @@ func command_exec(command): if err == OK: res = expression.execute([], get_pwn(), false); if expression.has_execute_failed(): - debug_print_line(command[0] + ": command not found: " + command[1]) + debug_print_line("%s: command not found: %s " % [command[0], command[1]]) res = "" else: res = expression.get_error_text() - debug_print_line(variant_to_string(res) + "\n") + debug_print_line("%s\n" % variant_to_string(res)) else: debug_print_line(get_usage(command[0])) @@ -484,10 +504,11 @@ func command_listprops(_command): proplist.sort_custom(self, "propSort") for prop in proplist: if prop["name"]: - props += "" + types[prop["type"]] + " " + prop["name"] + "\n" + props += "%s %s\n" % [types[prop["type"]], prop["name"]] pass debug_print_line(props) pass +# propsort: sort props by type, alphabetically func propSort(a, b): if a["type"] == b["type"]: return a["name"] < b["name"] @@ -518,7 +539,7 @@ func command_history(_command): var lnum = 0 for line in history: if line: - debug_print_line(String(lnum) + ": " + line + "\n") + debug_print_line("%2d: %s\n" % [lnum, line]) lnum += 1 #debug_print_line("history_pos = " + String(history_pos) + "\n") @@ -527,7 +548,7 @@ func command_perf(command): if command.size() > 1: var stat = perf(command[1]) if stat: - debug_print_line(String(stat) + "\n") + debug_print_line("%s\n" % String(stat)) else: debug_print_line("null\n") else: @@ -546,11 +567,12 @@ func command_script(command): script.push_back(f.get_line()) f.close() # Save state and turn off echo - var state = {"echo": echo, - "pwn": present_working_node, + var state = {"echo": echo, + "pwn": present_working_node, "history_pos": history_pos, "history": history, - "expression": expression} + "expression": expression, + "cheats": cheats} echo = false # Execute the script for cmd in script: @@ -562,21 +584,85 @@ func command_script(command): history_pos = state["history_pos"] history = state["history"] expression = state["expression"] + cheats = state["cheats"] else: debug_print_line("File not found: " + command[1] + "\n") else: debug_print_line(get_usage(command[0])) +# echo: enable and disable echoing commands and their outputs to the terminal func command_echo(command): if command.size() > 1: echo = string_to_variant(command[1], TYPE_BOOL) else: debug_print_line(get_usage(command[0])) +# cheat: Disable cheats, or enable them if you say the magic word +func command_cheat(command): + # check if there's more than one input to the command: + if command.size() > 1: + # hash the password + var code = command[1].sha256_text() + if code == cheat_code: + debug_print_line("Cheats enabled.\n") + cheats = true + return + debug_print_line("Ah ah ah, you didn't say the magic word!\n") + cheats = false + debug_print_line("Cheats disabled.\n") + pass + +# look-up table for performance monitor -> index pairs +# See https://docs.godotengine.org/en/stable/classes/class_performance.html +const monitor_lookup = { + # Time + "time:fps": 0, + "time:process": 1, + "time:physics process": 2, + # Memory + "memory:static": 3, + "memory:dynamic": 4, + "memory:static max": 5, + "memory:dynamic max": 6, + "memory:message buffer max": 7, + # Objects + "object:count": 8, + "object:resource count": 9, + "object:node count": 10, + "object:orphan node count": 11, + # Render + "render:objects in frame": 12, + "render:vertices in frame": 13, + "render:material changes in frame": 14, + "render:shader changes in frame": 15, + "render:surface changes in frame": 16, + "render:draw calls in frame": 17, + "render:2d items in frame": 18, + "render:2d draw calls in frame": 19, + "render:video memory used": 20, + "render:texture memory used": 21, + "render:vertex memory used": 22, + "render:total video memory used": 23, + # Physics2D + "physics2d:active objects": 24, + "physics2d:collision pairs": 25, + "physics2d:island count":26, + # Physics3D + "physics3d:active objects": 27, + "physics3d:collision pairs": 28, + "physics3d:island count": 29, + # Audio + "audio:output latency": 30 +} + +# Get a performance counter given a string func perf(attribute): if attribute.is_valid_integer(): return Performance.get_monitor(int(attribute)) - match attribute: + if attribute in monitor_lookup: + return Performance.get_monitor(monitor_lookup[attribute.to_lower()]) + # Shortcut to popular counters + match attribute.to_lower(): "fps": return Performance.get_monitor(Performance.TIME_FPS) "proctime":