extends Node const CONSOLE_THEME : String = &"console/theme" const CONSOLE_SCALE : String = &"console/scale" const CONSOLE_HEIGHT : String = &"console/height" const CONSOLE_COLOR_WARNING : String = &"console/color_warning" const CONSOLE_COLOR_ERROR : String = &"console/color_error" const CONSOLE_COLOR_INFO : String = &"console/color_info" const CONSOLE_COLOR_LITERAL : String = &"console/color_literal" const CONSOLE_TABSTOP : String = &"console/tabstop" const color_dictionary : Dictionary[String, Color] = { CONSOLE_COLOR_ERROR: Color.LIGHT_CORAL, CONSOLE_COLOR_INFO: Color.LIGHT_BLUE, CONSOLE_COLOR_LITERAL: Color.PALE_GREEN, CONSOLE_COLOR_WARNING: Color.LIGHT_GOLDENROD } var enabled : bool = true var enable_on_release_build : bool = false : set = set_enable_on_release_build var pause_enabled : bool = false var font_size : int : set = _set_font_size ## What visual scale should the console be var console_scale : float : set = set_console_scale ## Is the console in full screen or part screen mode var console_full_screen : bool = false var _conosle_tween_time : float = 5 signal console_opened signal console_closed signal console_unknown_command signal console_cvar_changed(cvar_name : String, value : Variant) class ConsoleCommand: var function : Callable var arguments : PackedStringArray var required : int var description : String var hidden : bool func _init(in_function : Callable, in_arguments : PackedStringArray, in_required : int = 0, in_description : String = ""): function = in_function arguments = in_arguments required = in_required description = in_description class ConsoleCvar: var name : String var description : String var type : int var save : bool var value : Variant var object : Object var property : String func _init(in_name : String, in_type : int, in_description : String = "", in_save : bool = false): name = in_name type = in_type description = in_description save = in_save func is_reference() -> bool: return object != null func is_alive() -> bool: return object == null or is_instance_valid(object) func get_value() -> Variant: if object != null: return object.get_indexed(property) return value func set_value(new_value : Variant) -> void: if object != null: object.set_indexed(property, new_value) else: value = new_value var theme : Theme var canvas_layer : CanvasLayer = CanvasLayer.new() var v_box_container : VBoxContainer = VBoxContainer.new() # If you want to customize the way the console looks, you can direcly modify # the properties of the rich text and line edit here: var rich_label : RichTextLabel = RichTextLabel.new() var panel : Panel = Panel.new() var line_edit : LineEdit = LineEdit.new() var console_commands : Dictionary[String, ConsoleCommand] = {} var console_cvars : Dictionary[String, ConsoleCvar] = {} # Pending values are applied when cvars are registered (if loaded from config) var _pending_cvar_values : Dictionary[String, Variant] = {} var command_parameters : Dictionary[String, PackedStringArray] = {} var console_history : Array[String] = [] var console_history_index : int = 0 var was_paused_already : bool = false var tab_string : String = " " var text_block_cache : Array[String] ## Usage: Console.add_command("command_name", , , , "Help description") func add_command(command_name : String, function : Callable, arguments = [], required: int = 0, description : String = "") -> void: if (arguments is int): # Legacy call using an argument number var param_array : PackedStringArray for i in range(arguments): param_array.append("arg_" + str(i + 1)) console_commands[command_name] = ConsoleCommand.new(function, param_array, required, description) elif (arguments is Array): # New array argument system var str_args : PackedStringArray for argument in arguments: str_args.append(str(argument)) console_commands[command_name] = ConsoleCommand.new(function, str_args, required, description) ## Adds a secret command that will not show up in the help or auto-complete. func add_hidden_command(command_name : String, function : Callable, arguments = [], required : int = 0) -> void: add_command(command_name, function, arguments, required) console_commands[command_name].hidden = true ## Removes a command from the console. This should be called on a script's _exit_tree() ## if you have console commands for things that are unloaded before the project closes. func remove_command(command_name : String) -> void: console_commands.erase(command_name) command_parameters.erase(command_name) ## Registers an auto-managed cvar that stores its value internally. ## Type the name to print the value, or "name value" to set it. The value is coerced to the ## type of default_value. If save is true, the value is persisted to user://console_cvars.txt. ## Usage: Console.add_cvar("cl_fov", 90.0, "Field of view.", true) func add_cvar(cvar_name : String, default_value : Variant, description : String = "", save : bool = false) -> void: var cvar := ConsoleCvar.new(cvar_name, typeof(default_value), description, save) cvar.value = default_value console_cvars[cvar_name] = cvar _apply_pending_cvar_value(cvar) ## Registers a cvar that reads from and writes to a property on another object. ## The cvar type is inferred from the property's current value. property may be a nested path ## (ex: "position:x"). ## Usage: Console.add_cvar_reference("cl_fov", camera, "fov", "Camera field of view.") func add_cvar_reference(cvar_name : String, object : Object, property : String, description : String = "", save : bool = false) -> void: if not is_instance_valid(object): print_error("Cannot register cvar \"%s\": invalid object." % cvar_name) return var cvar := ConsoleCvar.new(cvar_name, typeof(object.get_indexed(property)), description, save) cvar.object = object cvar.property = property console_cvars[cvar_name] = cvar _apply_pending_cvar_value(cvar) ## Removes a cvar. Call this on _exit_tree() for reference cvars whose object is freed early. func remove_cvar(cvar_name : String) -> void: console_cvars.erase(cvar_name) ## Returns the current value of a cvar, or null if it doesn't exist / its object was freed. func get_cvar(cvar_name : String) -> Variant: if console_cvars.has(cvar_name): var cvar : ConsoleCvar = console_cvars[cvar_name] if cvar.is_alive(): return cvar.get_value() return null ## Sets the value of a cvar programmatically (no string coercion) and emits console_cvar_changed. func set_cvar(cvar_name : String, value : Variant) -> void: if console_cvars.has(cvar_name): var cvar : ConsoleCvar = console_cvars[cvar_name] if cvar.is_alive(): cvar.set_value(value) console_cvar_changed.emit(cvar_name, value) func _apply_pending_cvar_value(cvar : ConsoleCvar) -> void: if cvar.save and _pending_cvar_values.has(cvar.name): var saved_value : Variant = _pending_cvar_values[cvar.name] if typeof(saved_value) != cvar.type: saved_value = type_convert(saved_value, cvar.type) cvar.set_value(saved_value) _pending_cvar_values.erase(cvar.name) ## Coerces a string into the given Variant.Type. Returns [success : bool, value : Variant]. func _coerce_string_to_type(string_value : String, type : int) -> Array: match type: TYPE_BOOL: var lower := string_value.strip_edges().to_lower() if lower in ["1", "true", "yes", "on"]: return [true, true] if lower in ["0", "false", "no", "off"]: return [true, false] return [false, null] TYPE_INT: var int_string := string_value.strip_edges() if int_string.is_valid_int(): return [true, int_string.to_int()] return [false, null] TYPE_FLOAT: var float_string := string_value.strip_edges() if float_string.is_valid_float(): return [true, float_string.to_float()] return [false, null] TYPE_STRING: return [true, string_value] TYPE_STRING_NAME: return [true, StringName(string_value)] _: # Complex types (Vector2, etc.) require GDScript literal syntax, ex: "Vector2(1, 2)". var parsed : Variant = str_to_var(string_value) if parsed != null and typeof(parsed) == type: return [true, parsed] return [false, null] func _print_cvar_value(cvar : ConsoleCvar) -> void: print_line("%s = [system_color color=CONSOLE_COLOR_LITERAL]%s[/system_color]" % [cvar.name, str(cvar.get_value())]) func _handle_cvar(cvar : ConsoleCvar, arguments : PackedStringArray) -> void: if not cvar.is_alive(): print_error("Variable \"%s\" references a freed object." % cvar.name) return if arguments.is_empty(): _print_cvar_value(cvar) if cvar.description: print_line("%s%s" % [tab_string, cvar.description]) return var raw_value := " ".join(arguments) var result := _coerce_string_to_type(raw_value, cvar.type) if not result[0]: print_error("Invalid value \"%s\" for variable \"%s\" (expected %s)." % [raw_value, cvar.name, type_string(cvar.type)]) return cvar.set_value(result[1]) console_cvar_changed.emit(cvar.name, result[1]) _print_cvar_value(cvar) ## Useful if you have a list of possible parameters (ex: level names). func add_command_autocomplete_list(command_name : String, param_list : PackedStringArray): command_parameters[command_name] = param_list func _enter_tree() -> void: var console_history_file := FileAccess.open("user://console_history.txt", FileAccess.READ) if (console_history_file): while (!console_history_file.eof_reached()): var line := console_history_file.get_line() if (line.length()): add_input_history(line) # Load persisted cvar values. They're stashed until the matching cvar is registered. var console_cvars_file := FileAccess.open("user://console_cvars.txt", FileAccess.READ) if (console_cvars_file): while (!console_cvars_file.eof_reached()): var line := console_cvars_file.get_line() if (line.length()): var split := line.split(" ", true, 1) # "name var_to_str(value)" if (split.size() == 2): _pending_cvar_values[split[0]] = str_to_var(split[1]) if ProjectSettings.has_setting(CONSOLE_THEME): theme = load(ProjectSettings.get_setting(CONSOLE_THEME)) if theme: v_box_container.theme = theme if ProjectSettings.has_setting(CONSOLE_TABSTOP): tab_string = "" for i in range(ProjectSettings.get_setting(CONSOLE_TABSTOP)): tab_string += " " canvas_layer.layer = ProjectSettings.get_setting(&"console/canvas_layer", 3) add_child(canvas_layer) console_scale = _get_console_scale_setting() v_box_container.offset_bottom = 0 v_box_container.offset_left = 0 v_box_container.offset_right = 0 v_box_container.offset_top = 0 canvas_layer.add_child(v_box_container) panel.size_flags_vertical = Control.SIZE_FILL ^ Control.SIZE_EXPAND v_box_container.add_child(panel) rich_label.selection_enabled = true rich_label.context_menu_enabled = true rich_label.bbcode_enabled = true rich_label.scroll_following = true rich_label.anchor_right = 1.0 rich_label.anchor_bottom = 1.0 rich_label.install_effect(preload("res://addons/console/system_color.gd").new()) panel.add_child(rich_label) rich_label.append_text("Development console.\n") line_edit.anchor_right = 1.0 line_edit.placeholder_text = "Enter \"help\" for instructions" if font_size > 0: line_edit.add_theme_font_size_override("font_size", font_size) v_box_container.add_child(line_edit) line_edit.text_submitted.connect(_on_text_entered) line_edit.text_changed.connect(_on_line_edit_text_changed) v_box_container.visible = false process_mode = PROCESS_MODE_ALWAYS ## Get the scale of the console from the settings -- if this is not in the system settings return a default value func _get_console_scale_setting() -> float: if ProjectSettings.has_setting(CONSOLE_SCALE): return ProjectSettings.get_setting(CONSOLE_SCALE) return 1.0 ## Set the console scale func set_console_scale(scale : float): console_scale = scale v_box_container.scale = Vector2(console_scale, console_scale) v_box_container.anchor_right = _get_console_width() v_box_container.anchor_bottom = _get_console_height() ## Get the height of the console - this is related to the scaling of the console func _get_console_height() -> float: if console_full_screen: return 1.0 / console_scale if ProjectSettings.has_setting(CONSOLE_HEIGHT): return ProjectSettings.get_setting(CONSOLE_HEIGHT) / console_scale return 0.5 / console_scale func _get_console_width() -> float: return 1.0 / console_scale func _set_font_size(value: int) -> void: font_size = value if value > 0: line_edit.add_theme_font_size_override("font_size", font_size) rich_label.add_theme_font_size_override("normal_font_size", font_size) rich_label.add_theme_font_size_override("bold_font_size", font_size) rich_label.add_theme_font_size_override("bold_italics_font_size", font_size) rich_label.add_theme_font_size_override("italics_font_size", font_size) rich_label.add_theme_font_size_override("mono_font_size", font_size) else: line_edit.remove_theme_font_size_override("font_size") rich_label.remove_theme_font_size_override("normal_font_size") rich_label.remove_theme_font_size_override("bold_font_size") rich_label.remove_theme_font_size_override("bold_italics_font_size") rich_label.remove_theme_font_size_override("italics_font_size") rich_label.remove_theme_font_size_override("mono_font_size") func _exit_tree() -> void: var console_history_file := FileAccess.open("user://console_history.txt", FileAccess.WRITE) if (console_history_file): var write_index := 0 var start_write_index := console_history.size() - 100 # Max lines to write for line in console_history: if (write_index >= start_write_index): console_history_file.store_line(line) write_index += 1 var console_cvars_file := FileAccess.open("user://console_cvars.txt", FileAccess.WRITE) if (console_cvars_file): for cvar_name in console_cvars: var cvar : ConsoleCvar = console_cvars[cvar_name] if (cvar.save and cvar.is_alive()): console_cvars_file.store_line("%s %s" % [cvar_name, var_to_str(cvar.get_value())]) for pending_name in _pending_cvar_values: if (!console_cvars.has(pending_name)): console_cvars_file.store_line("%s %s" % [pending_name, var_to_str(_pending_cvar_values[pending_name])]) func _ready() -> void: v_box_container.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST # For that retro look. add_command("quit", quit, 0, 0, "Quits the game.") add_command("exit", quit, 0, 0, "Quits the game.") add_command("clear", clear, 0, 0, "Clears the text on the console.") add_command("delete_history", delete_history, 0, 0, "Deletes the history of previously entered commands.") add_command("help", help, 0, 0, "Displays instructions on how to use the console.") add_command("commands_list", commands_list, 0, 0, "Lists all commands and their descriptions.") add_command("commands", commands, 0, 0, "Lists commands with no descriptions.") add_command("cvars", cvars, 0, 0, "Lists all console variables and their values.") add_command("calc", calculate, ["mathematical expression to evaluate"], 0, "Evaluates the math passed in for quick arithmetic.") add_command("echo", print_line, ["string"], 1, "Prints given string to the console.") add_command("echo_warning", print_warning, ["string"], 1, "Prints given string as warning to the console.") add_command("echo_info", print_info, ["string"], 1, "Prints given string as info to the console.") add_command("echo_error", print_error, ["string"], 1, "Prints given string as an error to the console.") add_command("pause", pause, 0, 0, "Pauses node processing.") add_command("unpause", unpause, 0, 0, "Unpauses node processing.") add_command("exec", exec, 1, 1, "Execute a script.") func _input(event : InputEvent) -> void: if (event is InputEventKey): if (event.physical_keycode == KEY_QUOTELEFT): # ~ key. if (event.physical_keycode == KEY_QUOTELEFT and event.is_command_or_control_pressed()): # Toggles console size or opens big console. if (event.pressed): if (v_box_container.visible): toggle_size() else: toggle_console() toggle_size() get_tree().get_root().set_input_as_handled() else: if (event.pressed): toggle_console() get_tree().get_root().set_input_as_handled() elif (event.get_physical_keycode_with_modifiers() == KEY_ESCAPE && v_box_container.visible): # Disable console on ESC if (event.pressed): toggle_console() get_tree().get_root().set_input_as_handled() if (v_box_container.visible and event.pressed): if (event.get_physical_keycode_with_modifiers() == KEY_UP): get_tree().get_root().set_input_as_handled() if (console_history_index > 0): console_history_index -= 1 if (console_history_index >= 0): line_edit.text = console_history[console_history_index] line_edit.caret_column = line_edit.text.length() reset_autocomplete() if (event.get_physical_keycode_with_modifiers() == KEY_DOWN): get_tree().get_root().set_input_as_handled() if (console_history_index < console_history.size()): console_history_index += 1 if (console_history_index < console_history.size()): line_edit.text = console_history[console_history_index] line_edit.caret_column = line_edit.text.length() reset_autocomplete() else: line_edit.text = "" reset_autocomplete() if (event.get_physical_keycode_with_modifiers() == KEY_PAGEUP): var scroll := rich_label.get_v_scroll_bar() var tween := create_tween() tween.tween_property(scroll, "value", scroll.value - (scroll.page - scroll.page * 0.1), 0.1) get_tree().get_root().set_input_as_handled() if (event.get_physical_keycode_with_modifiers() == KEY_PAGEDOWN): var scroll := rich_label.get_v_scroll_bar() var tween := create_tween() tween.tween_property(scroll, "value", scroll.value + (scroll.page - scroll.page * 0.1), 0.1) get_tree().get_root().set_input_as_handled() if (event.get_physical_keycode_with_modifiers() == KEY_TAB): autocomplete() get_tree().get_root().set_input_as_handled() elif event is InputEventMouseButton: if (v_box_container.visible): if (event.is_command_or_control_pressed()): if event.button_index == MOUSE_BUTTON_WHEEL_UP: # Increase font size with ctrl+mouse wheel up font_size = min(128, font_size + 2) # Limit to max of 128 get_tree().get_root().set_input_as_handled() elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN: # Decrease font size with ctrl+mouse wheel down font_size = max(8, font_size - 2) # Limit to minimum of 8 get_tree().get_root().set_input_as_handled() var suggestions := [] var current_suggest := 0 var suggesting := false func autocomplete() -> void: if (suggesting): for i in range(suggestions.size()): if (current_suggest == i): line_edit.text = str(suggestions[i]) line_edit.caret_column = line_edit.text.length() if (current_suggest == suggestions.size() - 1): current_suggest = 0 else: current_suggest += 1 return else: suggesting = true if (" " in line_edit.text): # We're searching for a parameter to autocomplete var split_text := parse_line_input(line_edit.text) if (split_text.size() > 1): var command := split_text[0] var param_input := split_text[1] if (command_parameters.has(command)): for param in command_parameters[command]: if (param_input in param) or (param_input.length() == 0): suggestions.append(str(command, " ", param)) else: var sorted_commands := [] for command in console_commands: if (!console_commands[command].hidden): sorted_commands.append(str(command)) for cvar_name in console_cvars: sorted_commands.append(str(cvar_name)) sorted_commands.sort() sorted_commands.reverse() var prev_index := 0 for command in sorted_commands: if (!line_edit.text || command.contains(line_edit.text)): var index : int = command.find(line_edit.text) if (index <= prev_index): suggestions.push_front(command) else: suggestions.push_back(command) prev_index = index autocomplete() func reset_autocomplete() -> void: suggestions.clear() current_suggest = 0 suggesting = false func toggle_size() -> void: console_full_screen = !console_full_screen v_box_container.anchor_bottom = _get_console_height() func disable(): enabled = false toggle_console() # Ensure hidden if opened func enable(): enabled = true func toggle_console() -> void: var was_visible := v_box_container.visible if (enabled): v_box_container.visible = !v_box_container.visible else: v_box_container.visible = false if (v_box_container.visible): was_paused_already = get_tree().paused get_tree().paused = was_paused_already || pause_enabled line_edit.grab_focus() if was_visible != v_box_container.visible: console_opened.emit() else: scroll_to_bottom() reset_autocomplete() if (pause_enabled && !was_paused_already): get_tree().paused = false if was_visible != v_box_container.visible: console_closed.emit() func is_visible(): return v_box_container.visible func scroll_to_bottom() -> void: var scroll: ScrollBar = rich_label.get_v_scroll_bar() scroll.value = scroll.max_value - scroll.page func print_error(text : Variant, print_godot := false) -> void: var _color : Color = Color.LIGHT_CORAL if not text is String: text = str(text) print_line("%s[system_color color=CONSOLE_COLOR_ERROR]ERROR:[/system_color] %s" % [tab_string, text], print_godot) func print_info(text : Variant, print_godot := false) -> void: var _color : Color = Color.LIGHT_BLUE if not text is String: text = str(text) print_line("%s[system_color color=CONSOLE_COLOR_INFO]INFO:[/system_color] %s" % [tab_string, text], print_godot) func print_warning(text : Variant, print_godot := false) -> void: var _color : Color = Color.LIGHT_GOLDENROD if not text is String: text = str(text) print_line("%s[system_color color=CONSOLE_COLOR_WARNING]WARNING:[/system_color] %s" % [tab_string, text], print_godot) func print_line(text : Variant, print_godot := false) -> void: if not text is String: text = str(text) if (!rich_label): # Tried to print something before the console was loaded. call_deferred("print_line", text) else: rich_label.append_text(text) rich_label.append_text("\n") if (print_godot): print_rich(text.dedent()) func parse_line_input(text : String) -> PackedStringArray: var out_array : PackedStringArray var first_char := true var in_quotes := false var escaped := false var token : String for c in text: if (c == '\\'): escaped = true continue elif (escaped): if (c == 'n'): c = '\n' elif (c == 't'): c = '\t' elif (c == 'r'): c = '\r' elif (c == 'a'): c = '\a' elif (c == 'b'): c = '\b' elif (c == 'f'): c = '\f' escaped = false elif (c == '\"'): in_quotes = !in_quotes continue elif (c == ' ' || c == '\t'): if (!in_quotes): out_array.push_back(token) token = "" continue token += c out_array.push_back(token) return out_array func _on_text_entered(new_text : String) -> void: scroll_to_bottom() reset_autocomplete() line_edit.clear() if (line_edit.has_method(&"edit")): line_edit.call_deferred(&"edit") if not new_text.strip_edges().is_empty(): add_input_history(new_text) print_line("[i]> " + new_text + "[/i]") var text_split := parse_line_input(new_text) var text_command := text_split[0] if console_commands.has(text_command): var arguments := text_split.slice(1) var console_command : ConsoleCommand = console_commands[text_command] # calc is a especial command that needs special treatment if (text_command.match("calc")): var expression := "" for word in arguments: expression += word console_command.function.callv([expression]) return if (arguments.size() < console_command.required): print_error("Too few arguments! Required < %d >" % console_command.required) return elif (arguments.size() > console_command.arguments.size()): arguments.resize(console_command.arguments.size()) # Functions fail to call if passed the incorrect number of arguments, so fill out with blank strings. while (arguments.size() < console_command.arguments.size()): arguments.append("") console_command.function.callv(arguments) elif console_cvars.has(text_command): _handle_cvar(console_cvars[text_command], text_split.slice(1)) else: console_unknown_command.emit(text_command) print_error("Unknown command or variable.") func _on_line_edit_text_changed(new_text : String) -> void: reset_autocomplete() func quit() -> void: get_tree().quit() func clear() -> void: rich_label.clear() func delete_history() -> void: console_history.clear() console_history_index = 0 DirAccess.remove_absolute("user://console_history.txt") func help() -> void: rich_label.append_text(" Built in commands: [system_color color=CONSOLE_COLOR_LITERAL]calc[/system_color]: Calculates a given expresion [system_color color=CONSOLE_COLOR_LITERAL]clear[/system_color]: Clears the registry view [system_color color=CONSOLE_COLOR_LITERAL]commands[/system_color]: Shows a reduced list of all the currently registered commands [system_color color=CONSOLE_COLOR_LITERAL]commands_list[/system_color]: Shows a detailed list of all the currently registered commands [system_color color=CONSOLE_COLOR_LITERAL]cvars[/system_color]: Lists all console variables and their values [system_color color=CONSOLE_COLOR_LITERAL]delete_history[/system_color]: Deletes the commands history [system_color color=CONSOLE_COLOR_LITERAL]echo[/system_color]: Prints a given string to the console [system_color color=CONSOLE_COLOR_LITERAL]echo_error[/system_color]: Prints a given string as an error to the console [system_color color=CONSOLE_COLOR_LITERAL]echo_info[/system_color]: Prints a given string as info to the console [system_color color=CONSOLE_COLOR_LITERAL]echo_warning[/system_color]: Prints a given string as warning to the console [system_color color=CONSOLE_COLOR_LITERAL]pause[/system_color]: Pauses node processing [system_color color=CONSOLE_COLOR_LITERAL]unpause[/system_color]: Unpauses node processing [system_color color=CONSOLE_COLOR_LITERAL]quit[/system_color]: Quits the game Controls: [system_color color=CONSOLE_COLOR_INFO]Up[/system_color] and [system_color color=CONSOLE_COLOR_INFO]Down[/system_color] arrow keys to navigate commands history [system_color color=CONSOLE_COLOR_INFO]PageUp[/system_color] and [system_color color=CONSOLE_COLOR_INFO]PageDown[/system_color] to scroll registry [[system_color color=CONSOLE_COLOR_INFO]Ctrl[/system_color] + [system_color color=CONSOLE_COLOR_INFO]~[/system_color]] to change console size between half screen and full screen [[system_color color=CONSOLE_COLOR_INFO]Ctrl[/system_color] + [system_color color=CONSOLE_COLOR_INFO]Mouse Wheel[/system_color]] up/down to change console font size [system_color color=CONSOLE_COLOR_INFO]~[/system_color] or [system_color color=CONSOLE_COLOR_INFO]Esc[/system_color] key to close the console [system_color color=CONSOLE_COLOR_INFO]Tab[/system_color] key to autocomplete, [system_color system_color=CONSOLE_COLOR_INFO]Tab[/system_color] again to cycle between matching suggestions\n\n") func calculate(command : String) -> void: var expression := Expression.new() var error = expression.parse(command) if error: print_error("%s" % expression.get_error_text()) return var result = expression.execute() if not expression.has_execute_failed(): print_line(str(result)) else: print_error("%s" % expression.get_error_text()) func commands() -> void: var commands := [] for command in console_commands: if (!console_commands[command].hidden): commands.append(str(command)) commands.sort() rich_label.append_text(" ") rich_label.append_text(str(commands) + "\n\n") func commands_list() -> void: var commands := [] for command in console_commands: if (!console_commands[command].hidden): commands.append(str(command)) commands.sort() for command in commands: var arguments_string := "" var description : String = console_commands[command].description for i in range(console_commands[command].arguments.size()): if i < console_commands[command].required: arguments_string += " [system_color color=CONSOLE_COLOR_ERROR]<" + console_commands[command].arguments[i] + ">[/system_color]" else: arguments_string += " [system_color color=CONSOLE_COLOR_INFO]<" + console_commands[command].arguments[i] + ">[/system_color]" rich_label.append_text(" [system_color color=CONSOLE_COLOR_LITERAL]%s[/system_color]%s: %s\n" % [command, arguments_string, description]) rich_label.append_text("\n") func cvars() -> void: var names := console_cvars.keys() names.sort() for cvar_name in names: var cvar : ConsoleCvar = console_cvars[cvar_name] var value_string := "" if (cvar.is_alive()): value_string = str(cvar.get_value()) rich_label.append_text(" [system_color color=CONSOLE_COLOR_LITERAL]%s[/system_color] = [system_color color=CONSOLE_COLOR_INFO]%s[/system_color] %s\n" % [cvar_name, value_string, cvar.description]) rich_label.append_text("\n") func add_input_history(text : String) -> void: if (!console_history.size() || text != console_history.back()): # Don't add consecutive duplicates console_history.append(text) console_history_index = console_history.size() func set_enable_on_release_build(enable : bool): enable_on_release_build = enable if (!enable_on_release_build): if (!OS.is_debug_build()): disable() func pause() -> void: get_tree().paused = true func unpause() -> void: get_tree().paused = false func exec(filename : String) -> void: var path := "user://%s.txt" % [filename] var script := FileAccess.open(path, FileAccess.READ) if (script): while (!script.eof_reached()): _on_text_entered(script.get_line()) else: print_error("File %s not found." % [path])