From 302f092dffb8966edda08da44baff72c7de28c2e Mon Sep 17 00:00:00 2001 From: pixelkingliam <79420307+pixelkingliam@users.noreply.github.com> Date: Fri, 13 Dec 2024 20:28:49 -0500 Subject: [PATCH 1/2] Added Dev Console --- assets/pixel/themes/Console.tres | 12 ++ assets/pixel/themes/ConsoleInput.tres | 9 ++ csharp/Client/UI/ConsoleEnabledControl.cs | 25 +++ csharp/Client/UI/DevConsole.cs | 184 ++++++++++++++++++++++ csharp/Managers/ConsoleManager.cs | 107 +++++++++++++ csharp/Managers/SceneManager.cs | 1 + csharp/Menus/MainMenu.cs | 30 +++- scenes/UI/DevConsole.tscn | 81 ++++++++++ 8 files changed, 448 insertions(+), 1 deletion(-) create mode 100644 assets/pixel/themes/Console.tres create mode 100644 assets/pixel/themes/ConsoleInput.tres create mode 100644 csharp/Client/UI/ConsoleEnabledControl.cs create mode 100644 csharp/Client/UI/DevConsole.cs create mode 100644 csharp/Managers/ConsoleManager.cs create mode 100644 scenes/UI/DevConsole.tscn diff --git a/assets/pixel/themes/Console.tres b/assets/pixel/themes/Console.tres new file mode 100644 index 0000000..1df6381 --- /dev/null +++ b/assets/pixel/themes/Console.tres @@ -0,0 +1,12 @@ +[gd_resource type="StyleBoxFlat" format=3 uid="uid://bs0wxlu2afue2"] + +[resource] +bg_color = Color(0.164706, 0.180392, 0.203922, 1) +corner_radius_top_left = 8 +corner_radius_top_right = 8 +corner_radius_bottom_right = 8 +corner_radius_bottom_left = 8 +expand_margin_left = 2.0 +expand_margin_right = 2.0 +shadow_color = Color(0.0784314, 0.0784314, 0.0784314, 1) +shadow_offset = Vector2(1, 2) diff --git a/assets/pixel/themes/ConsoleInput.tres b/assets/pixel/themes/ConsoleInput.tres new file mode 100644 index 0000000..8ab40c5 --- /dev/null +++ b/assets/pixel/themes/ConsoleInput.tres @@ -0,0 +1,9 @@ +[gd_resource type="Theme" load_steps=2 format=3 uid="uid://h368fwjarh3n"] + +[ext_resource type="StyleBox" uid="uid://bs0wxlu2afue2" path="res://assets/themes/box.tres" id="1_seonm"] + +[resource] +LineEdit/font_sizes/font_size = 16 +LineEdit/styles/focus = ExtResource("1_seonm") +LineEdit/styles/normal = ExtResource("1_seonm") +LineEdit/styles/read_only = ExtResource("1_seonm") diff --git a/csharp/Client/UI/ConsoleEnabledControl.cs b/csharp/Client/UI/ConsoleEnabledControl.cs new file mode 100644 index 0000000..e6be1dd --- /dev/null +++ b/csharp/Client/UI/ConsoleEnabledControl.cs @@ -0,0 +1,25 @@ +using System.Linq; +using ExtractIntoVoid.Managers; +using Godot; + +public partial class ConsoleEnabledScene : Control { + public override void _Input(InputEvent @event) + { + base._Input(@event); + if (@event is InputEventKey eKey ) { + // Key is ` + if (eKey.KeyLabel == Key.Quoteleft && eKey.IsReleased()) + { + var consoles = GetChildren().OfType(); + if (consoles.Any()) + { + var first = consoles.First(); + + first.Close(); + }else { + AddChild(ConsoleManager.GetConsole()); + } + } + } + } +} \ No newline at end of file diff --git a/csharp/Client/UI/DevConsole.cs b/csharp/Client/UI/DevConsole.cs new file mode 100644 index 0000000..f128108 --- /dev/null +++ b/csharp/Client/UI/DevConsole.cs @@ -0,0 +1,184 @@ +using Godot; +using System; +using System.Linq; +using ExtractIntoVoid.Managers; +using System.Globalization; + +// ReSharper disable CheckNamespace +// ReSharper disable MemberCanBePrivate.Global +public partial class DevConsole : Window +{ + // Called when the node enters the scene tree for the first time. + + private TextEdit Output => GetNode("./Control/Output"); + private LineEdit Input => GetNode("Control/InputArea/InputField"); + + public override void _Ready() + { + CloseRequested += () => + { + Close(); + }; + Open(); + //ConsoleManager.Console = this; + } + // Called every frame. 'delta' is the elapsed time since the previous frame. + public override void _Process(double delta) + { + if (Visible && Output.Text != ConsoleManager.Contents) + Output.Text = ConsoleManager.Contents; + } + private void OnInputSubmitted(string input) + { + + + + var command = input.Split(' ').First(); + var args = input.Split(' ').Skip(1).ToArray();//TakeLast(input.Split(' ').Length - 1).ToArray(); + Input.Text = ""; + Print("> " + input); + + HandleCommand(command, args); + + } + public override void _Input(InputEvent @event) + { + if(@event is InputEventKey eventKey) + if (eventKey.Pressed && eventKey.Keycode == Key.Tab && Input.Text != "") + { + var types = ConsoleManager.GetAll(); + if (!types.Keys.Any(s => s.StartsWith(Input.Text))) + { + return; + } + //Print(_cTypes.Keys.FirstOrDefault(s => s.StartsWith(Input.Text))); + var suggestions = new PopupMenu() + { + Size = new Vector2I(0, 0), + Position = new Vector2I((int)Input.GetScreenPosition().X, (int)Input.GetScreenPosition().Y + 24) + }; + foreach (var item in types.Where(s => s.Key.StartsWith(Input.Text))) + { + var context = new CommandContext(this, null); + + var value = ""; + if (item.Value is CVar cVarString) + { + value += " " + cVarString.Get(context); + } + if (item.Value is CVar cVarBool) + { + value += " " + cVarBool.Get(context); + } + if (item.Value is CVar cVarInt) + { + value += " " + cVarInt.Get(context); + } + if (item.Value is CVar cVarFloat) + { + value += " " + cVarFloat.Get(context); + } + suggestions.AddItem($"{item.Key}{value}"); + } + suggestions.IdPressed += delegate(long id) + { + Input.Text = suggestions.GetItemText((int)id).Split(" ").First(); + Input.CaretColumn = Input.Text.Length; + }; + suggestions.SetFocusedItem(0); + Input.AddChild(suggestions); + suggestions.Popup(); + //_cTypes.Keys.Where(s => s.StartsWith(Input.Text)); + } + base._Input(@event); + } + + public void Close() + { + Visible = false; + Godot.Input.MouseMode = ConsoleManager.CachedMouse; + QueueFree(); + } + + private void Open() + { + ConsoleManager.CachedMouse = Godot.Input.MouseMode; + Visible = true; + Godot.Input.MouseMode = Godot.Input.MouseModeEnum.Visible; + } + public void HandleCommand(string inputCommand, string[] args) + { + if (ConsoleManager.TryGet(inputCommand, out var command)) + { + var context = new CommandContext(this, args); + if (command is CFunc cFunc) + { + cFunc.Call(context); + } + if (command is CVar cVarString) + { + if (args.Length == 0) + { + Print(cVarString.Get(context)); + }else { + cVarString.Set(context, string.Join(" ", args)); + } + } + if (command is CVar cVarBool) + { + if (args.Length == 0) + { + Print(cVarBool.Get(context)); + }else { + try { + cVarBool.Set(context, Convert.ToBoolean(args[0])); + + } + catch (FormatException) { + Print($"Invalid input \"{args[0]}\", must be True or False"); + } + } + } + if (command is CVar cVarInt) + { + if (args.Length == 0) + { + Print(cVarInt.Get(context)); + }else { + try { + cVarInt.Set(context, Convert.ToInt32(args[0])); + + } + catch (FormatException) { + Print($"Invalid input \"{args[0]}\", must be an integer"); + } + } + } + if (command is CVar cVarFloat) + { + if (args.Length == 0) + { + Print(cVarFloat.Get(context)); + }else { + try { + cVarFloat.Set(context, float.Parse(args[0], CultureInfo.InvariantCulture)); + + } + catch (FormatException) { + Print($"Invalid input \"{args[0]}\", must be a float"); + } + } + } + } + } + public void Print(object what) + { + ConsoleManager.Contents += what; + ConsoleManager.Contents += '\n'; + Output.ScrollVertical = Output.GetVScrollBar().MaxValue; + + } + + + +} diff --git a/csharp/Managers/ConsoleManager.cs b/csharp/Managers/ConsoleManager.cs new file mode 100644 index 0000000..439ccd7 --- /dev/null +++ b/csharp/Managers/ConsoleManager.cs @@ -0,0 +1,107 @@ +// o7 This is all legacy code from Origin Framework + +// ReSharper disable CheckNamespace +// ReSharper disable MemberCanBePrivate.Global +using System; +using System.Collections.Generic; +using Godot; +using Serilog.Core; +using Serilog.Events; + +namespace ExtractIntoVoid.Managers; + +public static class ConsoleManager +{ + public static string Contents = "* Extract Into Void Developer Console *\nPress TAB for autocomplete.\n'help' to see all commands.\n"; + public static Godot.Input.MouseModeEnum CachedMouse = Godot.Input.MouseModeEnum.Visible; + + public static Node GetConsole() + { + var console = SceneManager.GetPackedScene("DevConsole").Instantiate() as DevConsole; + return console; + } + private static Dictionary _commands = new(); + /// + /// Registers a new Command. + /// + /// The name of the command + /// A CVar or CFunc Command + public static void Register(string name, Command CFuncOrCVar) + { + _commands.TryAdd(name, CFuncOrCVar); + } + public static bool TryGet(string name, out Command CFuncOrCVar) + { + return _commands.TryGetValue(name, out CFuncOrCVar); + } + public static Dictionary GetAll() + { + return new Dictionary(_commands); + } +} +public abstract class Command +{ + public string Description = ""; + +} +public class CommandContext +{ + public readonly DevConsole Console; + public readonly Control Scene; + public readonly string[] Args; + + public CommandContext(DevConsole console, string[] args) + { + Console = console; + Scene = console.GetParent() as Control; + Args = args; + } +} +public class CFunc : Command +{ + private Action _function; + public CFunc(string description, Action function) + { + Description = description; + _function = function; + } + public void Call(CommandContext ctx) + { + _function.Invoke(ctx); + } + +} +public class CVar : Command +{ + private Func _getter; + private Action _setter; + public CVar(string description, Func getter, Action setter) + { + Description = description; + // Check at runtime if the type is one of the allowed types + if (typeof(T) != typeof(int) && typeof(T) != typeof(float) && + typeof(T) != typeof(bool) && typeof(T) != typeof(string)) + { + throw new InvalidOperationException("Invalid type. Only int, float, bool, and string are allowed."); + } + _getter = getter; + _setter = setter; + } + public T Get(CommandContext ctx) + { + return _getter.Invoke(ctx); + } + public void Set(CommandContext ctx, T value) + { + _setter.Invoke(ctx, value); + } + +} +public class ConsoleSink : ILogEventSink +{ + + public void Emit(LogEvent logEvent) + { + ConsoleManager.Contents += logEvent.RenderMessage(); + } +} \ No newline at end of file diff --git a/csharp/Managers/SceneManager.cs b/csharp/Managers/SceneManager.cs index d1869f4..008c3f2 100644 --- a/csharp/Managers/SceneManager.cs +++ b/csharp/Managers/SceneManager.cs @@ -22,6 +22,7 @@ static SceneManager() { "ConnectionScreen", "res://scenes/Menu/ConnectionScreen.tscn" }, { "InputScene", "res://scenes/Menu/InputScene.tscn" }, { "LobbyScene", "res://scenes/Menu/LobbyScene.tscn" }, + { "DevConsole", "res://scenes/UI/DevConsole.tscn" }, // soon // { "Inventory", "res://scenes/Menu/Inventory.tscn" }, // { "Escape", "res://scenes/Menu/EscapeScene.tscn" }, diff --git a/csharp/Menus/MainMenu.cs b/csharp/Menus/MainMenu.cs index 0fc4248..1108cca 100644 --- a/csharp/Menus/MainMenu.cs +++ b/csharp/Menus/MainMenu.cs @@ -4,10 +4,38 @@ namespace ExtractIntoVoid.Menus; -public partial class MainMenu : Control +public partial class MainMenu : ConsoleEnabledScene { public override void _Ready() { + ConsoleManager.Register("help", new CFunc("Shows all commands.", (ctx) => + { + foreach(var command in ConsoleManager.GetAll()) + ctx.Console.Print($"{command.Key} ({(command.Value is CFunc ? "func" : "var")})\t {command.Value.Description}"); + })); + ConsoleManager.Register("close", new CFunc("Closes the console window.", (ctx) => + { + ctx.Console.Close(); + })); + ConsoleManager.Register("version", new CFunc("Prints the game's version", (ctx) => + { + ctx.Console.Print(BuildDefined.FullVersion); + })); + ConsoleManager.Register("exit", new CFunc("Closes the game", (ctx) => + { + GetTree().Quit(); + })); + ConsoleManager.Register("title", new CVar("The console window's title", + (ctx) =>{ + return ctx.Console.Title; + }, + (ctx, value) =>{ + ctx.Console.Title = value; + })); + + //AddChild(ConsoleManager.GetConsole()); + //AddChild(SceneManager.GetPackedScene("DevConsole").Instantiate()); + //ConsoleManager.Console.Visible = true; GameManager.Instance.UIManager.LoadScreenStop(); GetNode