Add LimboConsole plugin with command interpreter and configuration options
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 101 KiB |
Before Width: | Height: | Size: 101 KiB |
@@ -3,13 +3,61 @@
|
|||||||
{
|
{
|
||||||
"tierEnum": "Tier1",
|
"tierEnum": "Tier1",
|
||||||
"threshold": 150,
|
"threshold": 150,
|
||||||
"imagePath": "res://Mods/Tiers/Huts/hut_tier_1.png",
|
"imagePath": "res://Sprites/Hut.png",
|
||||||
"scale": {"x": 0.05, "y": 0.05}
|
"scale": {"x": 0.05, "y": 0.05}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tierEnum": "Tier2",
|
"tierEnum": "Tier2",
|
||||||
"threshold": 750,
|
"threshold": 750,
|
||||||
"imagePath": "res://Mods/Tiers/Huts/hut_tier_2.png",
|
"imagePath": "res://Sprites/hut_tier_2.png",
|
||||||
|
"scale": {"x": 0.05, "y": 0.05}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tierEnum": "Tier3",
|
||||||
|
"threshold": 1000,
|
||||||
|
"imagePath": "res://Sprites/hut_tier_3.png",
|
||||||
|
"scale": {"x": 0.05, "y": 0.05}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tierEnum": "Tier4",
|
||||||
|
"threshold": 5000,
|
||||||
|
"imagePath": "res://Sprites/castle.png",
|
||||||
|
"scale": {"x": 0.05, "y": 0.05}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tierEnum": "Tier5",
|
||||||
|
"threshold": 7500,
|
||||||
|
"imagePath": "res://Sprites/house.png",
|
||||||
|
"scale": {"x": 0.05, "y": 0.05}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tierEnum": "Tier6",
|
||||||
|
"threshold": 10000,
|
||||||
|
"imagePath": "res://Sprites/house_tier_2.png",
|
||||||
|
"scale": {"x": 0.05, "y": 0.05}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tierEnum": "Tier7",
|
||||||
|
"threshold": 50000,
|
||||||
|
"imagePath": "res://Sprites/house_tier_3.png",
|
||||||
|
"scale": {"x": 0.05, "y": 0.05}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tierEnum": "Tier8",
|
||||||
|
"threshold": 150000,
|
||||||
|
"imagePath": "res://Sprites/Skyscraper.png",
|
||||||
|
"scale": {"x": 0.05, "y": 0.05}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tierEnum": "Tier9",
|
||||||
|
"threshold": 350000,
|
||||||
|
"imagePath": "res://Sprites/skyscraper_tier_2.png",
|
||||||
|
"scale": {"x": 0.05, "y": 0.05}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tierEnum": "Tier10",
|
||||||
|
"threshold": 550000,
|
||||||
|
"imagePath": "res://Sprites/skyscraper_tier_3.png",
|
||||||
"scale": {"x": 0.05, "y": 0.05}
|
"scale": {"x": 0.05, "y": 0.05}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
<RootNamespace>parasiticgod</RootNamespace>
|
<RootNamespace>parasiticgod</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="LimboConsole.Sharp" Version="0.0.1-beta-008" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4-beta1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4-beta1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
@@ -5,6 +5,6 @@ namespace ParasiticGod.Scripts;
|
|||||||
[GlobalClass]
|
[GlobalClass]
|
||||||
public partial class Follower : Node2D
|
public partial class Follower : Node2D
|
||||||
{
|
{
|
||||||
public enum FollowerTier { Tier1, Tier2, Tier3, Tier4, Tier5 }
|
public enum FollowerTier { Tier1, Tier2, Tier3, Tier4, Tier5, Tier6, Tier7, Tier8, Tier9, Tier10 }
|
||||||
[Export] public FollowerTier Tier { get; private set; }
|
[Export] public FollowerTier Tier { get; private set; }
|
||||||
}
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Godot;
|
using Godot;
|
||||||
|
using Limbo.Console.Sharp;
|
||||||
using ParasiticGod.Scripts.Core;
|
using ParasiticGod.Scripts.Core;
|
||||||
using ParasiticGod.Scripts.Core.Effects;
|
using ParasiticGod.Scripts.Core.Effects;
|
||||||
|
|
||||||
@@ -38,6 +39,11 @@ public partial class GameBus : Node
|
|||||||
Instance = null;
|
Instance = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
RegisterConsoleCommands();
|
||||||
|
}
|
||||||
|
|
||||||
public override void _Process(double delta)
|
public override void _Process(double delta)
|
||||||
{
|
{
|
||||||
_gameLogic.UpdateGameState(_gameState, delta);
|
_gameLogic.UpdateGameState(_gameState, delta);
|
||||||
@@ -66,7 +72,7 @@ public partial class GameBus : Node
|
|||||||
if (AllMiracles.TryGetValue(id, out var def) && !_gameState.IsMiracleUnlocked(id))
|
if (AllMiracles.TryGetValue(id, out var def) && !_gameState.IsMiracleUnlocked(id))
|
||||||
{
|
{
|
||||||
miraclesToUnlock.Add(def);
|
miraclesToUnlock.Add(def);
|
||||||
_gameState.AddUnlockedMiracle(id);
|
_gameState.AddUnlockedMiracle(def.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,4 +114,7 @@ public partial class GameBus : Node
|
|||||||
public void UnsubscribeFromStat(Stat stat, Action<double> listener) => _gameState.Unsubscribe(stat, listener);
|
public void UnsubscribeFromStat(Stat stat, Action<double> listener) => _gameState.Unsubscribe(stat, listener);
|
||||||
|
|
||||||
public GameState CurrentState => _gameState;
|
public GameState CurrentState => _gameState;
|
||||||
|
|
||||||
|
[ConsoleCommand("set_stat", "Sets the value of a specified stat.")]
|
||||||
|
private void SetStatCommand(Stat stat, double value) => _gameState.Set(stat, value);
|
||||||
}
|
}
|
BIN
Sprites/Skyscraper.png
Normal file
After Width: | Height: | Size: 36 KiB |
@@ -2,16 +2,16 @@
|
|||||||
|
|
||||||
importer="texture"
|
importer="texture"
|
||||||
type="CompressedTexture2D"
|
type="CompressedTexture2D"
|
||||||
uid="uid://c88ltenh4ghit"
|
uid="uid://b7shwf6ob3qk5"
|
||||||
path="res://.godot/imported/hut_tier_1.png-95c994b0565c43d199344569cb1a91ab.ctex"
|
path="res://.godot/imported/Skyscraper.png-dbe8acef55267af18c54925cfa634290.ctex"
|
||||||
metadata={
|
metadata={
|
||||||
"vram_texture": false
|
"vram_texture": false
|
||||||
}
|
}
|
||||||
|
|
||||||
[deps]
|
[deps]
|
||||||
|
|
||||||
source_file="res://Mods/Tiers/Huts/hut_tier_1.png"
|
source_file="res://Sprites/Skyscraper.png"
|
||||||
dest_files=["res://.godot/imported/hut_tier_1.png-95c994b0565c43d199344569cb1a91ab.ctex"]
|
dest_files=["res://.godot/imported/Skyscraper.png-dbe8acef55267af18c54925cfa634290.ctex"]
|
||||||
|
|
||||||
[params]
|
[params]
|
||||||
|
|
Before Width: | Height: | Size: 55 KiB |
BIN
Sprites/house_tier_2.png
Normal file
After Width: | Height: | Size: 39 KiB |
@@ -2,16 +2,16 @@
|
|||||||
|
|
||||||
importer="texture"
|
importer="texture"
|
||||||
type="CompressedTexture2D"
|
type="CompressedTexture2D"
|
||||||
uid="uid://2wsb1jsmtifq"
|
uid="uid://8omh4jhf3dwy"
|
||||||
path="res://.godot/imported/follower_tier_1.png-d66a290ad46b1bb9ea69b54444ab5725.ctex"
|
path="res://.godot/imported/house_tier_2.png-81b05409415d85bd1a2017792c00d1d0.ctex"
|
||||||
metadata={
|
metadata={
|
||||||
"vram_texture": false
|
"vram_texture": false
|
||||||
}
|
}
|
||||||
|
|
||||||
[deps]
|
[deps]
|
||||||
|
|
||||||
source_file="res://Mods/Tiers/Followers/follower_tier_1.png"
|
source_file="res://Sprites/house_tier_2.png"
|
||||||
dest_files=["res://.godot/imported/follower_tier_1.png-d66a290ad46b1bb9ea69b54444ab5725.ctex"]
|
dest_files=["res://.godot/imported/house_tier_2.png-81b05409415d85bd1a2017792c00d1d0.ctex"]
|
||||||
|
|
||||||
[params]
|
[params]
|
||||||
|
|
BIN
Sprites/house_tier_3.png
Normal file
After Width: | Height: | Size: 59 KiB |
34
Sprites/house_tier_3.png.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://6swd4nukhqw3"
|
||||||
|
path="res://.godot/imported/house_tier_3.png-6cbc031313f2c66f5365fe6cc5406e8f.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://Sprites/house_tier_3.png"
|
||||||
|
dest_files=["res://.godot/imported/house_tier_3.png-6cbc031313f2c66f5365fe6cc5406e8f.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
BIN
Sprites/hut_tier_2.png
Normal file
After Width: | Height: | Size: 37 KiB |
@@ -2,16 +2,16 @@
|
|||||||
|
|
||||||
importer="texture"
|
importer="texture"
|
||||||
type="CompressedTexture2D"
|
type="CompressedTexture2D"
|
||||||
uid="uid://cgoigfok3s0fc"
|
uid="uid://bvmc6om3x08a"
|
||||||
path="res://.godot/imported/hut_tier_2.png-7e373825ef1bbf63f359ae57d709b394.ctex"
|
path="res://.godot/imported/hut_tier_2.png-3b01622803083ef93f5d9958304c62a4.ctex"
|
||||||
metadata={
|
metadata={
|
||||||
"vram_texture": false
|
"vram_texture": false
|
||||||
}
|
}
|
||||||
|
|
||||||
[deps]
|
[deps]
|
||||||
|
|
||||||
source_file="res://Mods/Tiers/Huts/hut_tier_2.png"
|
source_file="res://Sprites/hut_tier_2.png"
|
||||||
dest_files=["res://.godot/imported/hut_tier_2.png-7e373825ef1bbf63f359ae57d709b394.ctex"]
|
dest_files=["res://.godot/imported/hut_tier_2.png-3b01622803083ef93f5d9958304c62a4.ctex"]
|
||||||
|
|
||||||
[params]
|
[params]
|
||||||
|
|
BIN
Sprites/hut_tier_3.png
Normal file
After Width: | Height: | Size: 43 KiB |
34
Sprites/hut_tier_3.png.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://c047caxjt7n1h"
|
||||||
|
path="res://.godot/imported/hut_tier_3.png-7223c535ac426228250d1a42c2991446.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://Sprites/hut_tier_3.png"
|
||||||
|
dest_files=["res://.godot/imported/hut_tier_3.png-7223c535ac426228250d1a42c2991446.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
BIN
Sprites/skyscraper_tier_2.png
Normal file
After Width: | Height: | Size: 38 KiB |
34
Sprites/skyscraper_tier_2.png.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bjp0e05j0rut5"
|
||||||
|
path="res://.godot/imported/skyscraper_tier_2.png-08c46a0e23a9922ac087d37397d5de1d.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://Sprites/skyscraper_tier_2.png"
|
||||||
|
dest_files=["res://.godot/imported/skyscraper_tier_2.png-08c46a0e23a9922ac087d37397d5de1d.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
BIN
Sprites/skyscraper_tier_3.png
Normal file
After Width: | Height: | Size: 42 KiB |
34
Sprites/skyscraper_tier_3.png.import
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://cu5env6dkuc1u"
|
||||||
|
path="res://.godot/imported/skyscraper_tier_3.png-4b648ec2b3736510201be45be1a2c6a7.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://Sprites/skyscraper_tier_3.png"
|
||||||
|
dest_files=["res://.godot/imported/skyscraper_tier_3.png-4b648ec2b3736510201be45be1a2c6a7.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
39
addons/limbo_console.cfg
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
[main]
|
||||||
|
|
||||||
|
aliases={
|
||||||
|
"exit": "quit",
|
||||||
|
"source": "exec",
|
||||||
|
"usage": "help"
|
||||||
|
}
|
||||||
|
disable_in_release_build=false
|
||||||
|
print_to_stdout=false
|
||||||
|
pause_when_open=true
|
||||||
|
commands_disabled_in_release=["eval"]
|
||||||
|
|
||||||
|
[appearance]
|
||||||
|
|
||||||
|
custom_theme="res://addons/limbo_console_theme.tres"
|
||||||
|
height_ratio=0.5
|
||||||
|
open_speed=5.0
|
||||||
|
opacity=1.0
|
||||||
|
sparse_mode=false
|
||||||
|
|
||||||
|
[greet]
|
||||||
|
|
||||||
|
greet_user=true
|
||||||
|
greeting_message="{project_name}"
|
||||||
|
greet_using_ascii_art=true
|
||||||
|
|
||||||
|
[history]
|
||||||
|
|
||||||
|
persist_history=true
|
||||||
|
history_lines=1000
|
||||||
|
|
||||||
|
[autocomplete]
|
||||||
|
|
||||||
|
autocomplete_use_history_with_matches=true
|
||||||
|
|
||||||
|
[autoexec]
|
||||||
|
|
||||||
|
autoexec_script="user://autoexec.lcs"
|
||||||
|
autoexec_auto_create=true
|
15
addons/limbo_console/CONTRIBUTING.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
Thanks for your interest in contributing. Please follow these guidelines to keep the project consistent and maintainable in the long-term.
|
||||||
|
|
||||||
|
## Vision
|
||||||
|
|
||||||
|
- This is a simple text-based interface. Follow the [KISS principle](https://en.wikipedia.org/wiki/KISS_principle) – keep the code simple and easy to maintain, abstractions minimal, and the API easy to use without reading a manual.
|
||||||
|
- Don't run any logic if the console is closed or hidden.
|
||||||
|
|
||||||
|
## Code Style & Recommendations
|
||||||
|
|
||||||
|
- Follow the official [GDScript Style Guide](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_styleguide.html).
|
||||||
|
- Use [static typing](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/static_typing.html) wherever possible.
|
||||||
|
- Apply the 80/20 rule: if a feature only benefits a small number of users, make it optional or don’t include it.
|
||||||
|
- To avoid unnecessary whitespace changes, please enable this setting in Godot: `Editor Settings > Text Editor > Behavior > Files > Trim Trailing Whitespace on Save`
|
21
addons/limbo_console/LICENSE.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Serhii Snitsaruk
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
128
addons/limbo_console/README.md
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
<p align="left">
|
||||||
|
<img src=".github/logo.png" width=128 alt="LimboConsole logo">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
[](https://github.com/limbonaut/limbo_console/blob/master/LICENSE.md)
|
||||||
|
|
||||||
|
A simple and easy-to-use in-game dev console with a command interpreter for Godot Engine 4.
|
||||||
|
|
||||||
|
It supports auto-completion with `TAB` for commands and history, auto-correction, inline hints and highlighting, command help text generation, argument parsing for basic types, aliases, custom theming, and more.
|
||||||
|
|
||||||
|
This plugin is currently in development, so expect breaking changes.
|
||||||
|
|
||||||
|
[](https://ko-fi.com/Y8Y2TCNH0)
|
||||||
|
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
> ℹ LimboConsole can be added as a Git submodule
|
||||||
|
|
||||||
|
Place the source code in the `res://addons/limbo_console/` directory, and enable this plugin in the project settings, then reload the project. Toggle the console with the `GRAVE ACCENT` key (aka backtick - the key to the left of the `1` key). This can be changed in the Input Map tab in the project settings.
|
||||||
|
|
||||||
|
Adding a new command is quite simple:
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
func _ready() -> void:
|
||||||
|
LimboConsole.register_command(multiply)
|
||||||
|
|
||||||
|
func multiply(a: float, b: float) -> void:
|
||||||
|
LimboConsole.info("a * b: " + str(a * b))
|
||||||
|
```
|
||||||
|
|
||||||
|
> ℹ For C# support, see the next section.
|
||||||
|
|
||||||
|
The example above adds a command that multiplies two numbers and prints the result (type `multiply 2 4`). Additionally, you can specify a name and a description:
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
LimboConsole.register_command(multiply, "multiply", "multiply two numbers")
|
||||||
|
```
|
||||||
|
|
||||||
|
You can add a command as a subcommand of another command:
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
# Register `multiply` as a subcommand under a new `math` command.
|
||||||
|
LimboConsole.register_command(multiply, "math multiply", "Multiply two numbers")
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, you can enter `math multiply 2 4` in the console. By the way, the parent command doesn't have to exist.
|
||||||
|
|
||||||
|
Several basic types are supported for command arguments, such as `bool`, `int`, `float`, `String` and `Vector{2,3,4}` types. To enter a `Vector2` argument, enclose its components in parentheses, like this: `(1 2)`. String arguments can also be enclosed in double quotation marks `"`.
|
||||||
|
|
||||||
|
Autocompletion works for both command names and history. It can also be implemented for specific command arguments, as shown in the following example:
|
||||||
|
```gdscript
|
||||||
|
LimboConsole.register_command(teleport, "teleport", "teleport to site on this level")
|
||||||
|
LimboConsole.add_argument_autocomplete_source("teleport", 1,
|
||||||
|
func(): return ["entrance", "caves", "boss"]
|
||||||
|
)
|
||||||
|
```
|
||||||
|
For a dynamically generated list of autocomplete values, the code could look like this:
|
||||||
|
```gdscript
|
||||||
|
LimboConsole.add_argument_autocomplete_source("teleport", 1,
|
||||||
|
func(): return get_tree().get_nodes_in_group("teleportation_site").map(
|
||||||
|
func(node): return node.name)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using in C#
|
||||||
|
|
||||||
|
A community-maintained C# wrapper for this project is available as a NuGet package: https://github.com/ryan-linehan/limbo_console_sharp
|
||||||
|
|
||||||
|
Thanks to @ryan-linehan for maintaining it!
|
||||||
|
|
||||||
|
### Methods and properties
|
||||||
|
|
||||||
|
Some notable methods and properties:
|
||||||
|
|
||||||
|
- LimboConsole.enabled
|
||||||
|
- LimboConsole.register_command(callable, command_name, description)
|
||||||
|
- LimboConsole.unregister_command(callable_or_command_name)
|
||||||
|
- LimboConsole.add_alias(alias_name, command_name)
|
||||||
|
- LimboConsole.info(text_line)
|
||||||
|
- LimboConsole.error(text_line)
|
||||||
|
- LimboConsole.warning(text_line)
|
||||||
|
- LimboConsole.toggle_console()
|
||||||
|
- LimboConsole.add_argument_autocomplete_source(command_name, argument, callable)
|
||||||
|
- LimboConsole.execute_script(path, silent)
|
||||||
|
|
||||||
|
This is not a complete list. For the rest, check out `limbo_console.gd`.
|
||||||
|
|
||||||
|
### Keyboard Shortcuts
|
||||||
|
|
||||||
|
- `Grave Accent` *(aka backtick - the key to the left of the `1` key)* — Toggle the console.
|
||||||
|
- `Enter` — Run entered command.
|
||||||
|
- `Tab` — Autocomplete command entry or cycle through autocomplete suggestions.
|
||||||
|
- `Shift+Tab` — Cycle through autocomplete suggestions in reverse.
|
||||||
|
- `Right` *(when cursor is at the end of command entry)* — Autocomplete according to inline hint (doesn't cycle like `Tab`).
|
||||||
|
- `Up/Down` — Cycle through command history, replacing contents of command entry.
|
||||||
|
- `Ctrl+R` — Toggle the history search interface (similar to [fzf](https://github.com/junegunn/fzf)).
|
||||||
|
- `Ctrl+C` *(when no text selected)* — Clear the command entry.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
Options can be modified in the project-specific configuration file located at `res://addons/limbo_console.cfg`. This file is stored outside the plugin's directory to support adding the plugin as a Git submodule.
|
||||||
|
|
||||||
|
LimboConsole also supports UI theming. Simply duplicate the `default_theme.tres` file and rename it to `limbo_console_theme.tres`. The file path is important - it should be located at `res://addons/limbo_console_theme.tres`. You can change this location in the config file.
|
||||||
|
Open the theme resource in Godot to customize it for your game. Console text colors can be adjusted in the `ConsoleColors` category.
|
||||||
|
|
||||||
|
### Scripting
|
||||||
|
|
||||||
|
You can execute simple scripts containing a sequence of commands:
|
||||||
|
```shell
|
||||||
|
exec lcs/my_script.lcs
|
||||||
|
```
|
||||||
|
|
||||||
|
Simple rules:
|
||||||
|
- Commands must be provided in the same syntax as in the prompt, with each command on a separate line.
|
||||||
|
- The script must exist at the specified path, either in the `res://` or `user://` directory.
|
||||||
|
- The script must have the `.lcs` extension, but when running the `exec` command, you can omit the extension in the command line.
|
||||||
|
- A line that starts with a '#' is treated as a comment and is not executed as part of the script.
|
||||||
|
|
||||||
|
You can have a script execute automatically every time the game starts. There is a special script called `user://autoexec.lcs` that runs each time the game starts. This can be customized in the configuration file.
|
||||||
|
|
||||||
|
### Contributing
|
||||||
|
|
||||||
|
Check out [CONTRIBUTING.md](CONTRIBUTING.md).
|
208
addons/limbo_console/ascii_art.gd
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
|
||||||
|
const boxed_map: Dictionary = {
|
||||||
|
'a': """
|
||||||
|
▒▄▀█
|
||||||
|
░█▀█
|
||||||
|
""",
|
||||||
|
'b': """
|
||||||
|
░█▄▄
|
||||||
|
▒█▄█
|
||||||
|
""",
|
||||||
|
'c': """
|
||||||
|
░▄▀▀
|
||||||
|
░▀▄▄
|
||||||
|
""",
|
||||||
|
'd': """
|
||||||
|
▒█▀▄
|
||||||
|
░█▄▀
|
||||||
|
""",
|
||||||
|
'e': """
|
||||||
|
░██▀
|
||||||
|
▒█▄▄
|
||||||
|
""",
|
||||||
|
'f': """
|
||||||
|
░█▀▀
|
||||||
|
░█▀░
|
||||||
|
""",
|
||||||
|
'g': """
|
||||||
|
▒▄▀▀
|
||||||
|
░▀▄█
|
||||||
|
""",
|
||||||
|
'h': """
|
||||||
|
░█▄█
|
||||||
|
▒█▒█
|
||||||
|
""",
|
||||||
|
'i': """
|
||||||
|
░█
|
||||||
|
░█
|
||||||
|
""",
|
||||||
|
'j': """
|
||||||
|
░░▒█
|
||||||
|
░▀▄█
|
||||||
|
""",
|
||||||
|
'k': """
|
||||||
|
░█▄▀
|
||||||
|
░█▒█
|
||||||
|
""",
|
||||||
|
'l': """
|
||||||
|
░█▒░
|
||||||
|
▒█▄▄
|
||||||
|
""",
|
||||||
|
'm': """
|
||||||
|
▒█▀▄▀█
|
||||||
|
░█▒▀▒█
|
||||||
|
""",
|
||||||
|
'n': """
|
||||||
|
░█▄░█
|
||||||
|
░█▒▀█
|
||||||
|
""",
|
||||||
|
'o': """
|
||||||
|
░█▀█
|
||||||
|
▒█▄█
|
||||||
|
""",
|
||||||
|
'p': """
|
||||||
|
▒█▀█
|
||||||
|
░█▀▀
|
||||||
|
""",
|
||||||
|
'q': """
|
||||||
|
░▄▀▄
|
||||||
|
░▀▄█
|
||||||
|
""",
|
||||||
|
'r': """
|
||||||
|
▒█▀█
|
||||||
|
░█▀▄
|
||||||
|
""",
|
||||||
|
's': """
|
||||||
|
░▄▀
|
||||||
|
▒▄█
|
||||||
|
""",
|
||||||
|
't': """
|
||||||
|
░▀█▀
|
||||||
|
░▒█▒
|
||||||
|
""",
|
||||||
|
'u': """
|
||||||
|
░█░█
|
||||||
|
▒█▄█
|
||||||
|
""",
|
||||||
|
'v': """
|
||||||
|
░█░█
|
||||||
|
▒▀▄▀
|
||||||
|
""",
|
||||||
|
'w': """
|
||||||
|
▒█░█░█
|
||||||
|
░▀▄▀▄▀
|
||||||
|
""",
|
||||||
|
'x': """
|
||||||
|
░▀▄▀
|
||||||
|
░█▒█
|
||||||
|
""",
|
||||||
|
'y': """
|
||||||
|
░▀▄▀
|
||||||
|
░▒█▒
|
||||||
|
""",
|
||||||
|
'z': """
|
||||||
|
░▀█
|
||||||
|
▒█▄
|
||||||
|
""",
|
||||||
|
' ': """
|
||||||
|
░
|
||||||
|
░
|
||||||
|
""",
|
||||||
|
'_': """
|
||||||
|
░░░
|
||||||
|
▒▄▄
|
||||||
|
""",
|
||||||
|
',': """
|
||||||
|
░▒
|
||||||
|
░█
|
||||||
|
""",
|
||||||
|
'.': """
|
||||||
|
░░
|
||||||
|
░▄
|
||||||
|
""",
|
||||||
|
'!': """
|
||||||
|
░█
|
||||||
|
░▄
|
||||||
|
""",
|
||||||
|
'-': """
|
||||||
|
░▒░
|
||||||
|
░▀▀
|
||||||
|
""",
|
||||||
|
'?': """
|
||||||
|
░▀▀▄
|
||||||
|
░▒█▀
|
||||||
|
""",
|
||||||
|
'\'': """
|
||||||
|
░▀
|
||||||
|
░░
|
||||||
|
""",
|
||||||
|
':': """
|
||||||
|
░▄░
|
||||||
|
▒▄▒
|
||||||
|
""",
|
||||||
|
'0': """
|
||||||
|
░▄▀▄
|
||||||
|
░▀▄▀
|
||||||
|
""",
|
||||||
|
'1': """
|
||||||
|
░▄█
|
||||||
|
░░█
|
||||||
|
""",
|
||||||
|
'2': """
|
||||||
|
░▀█
|
||||||
|
░█▄
|
||||||
|
""",
|
||||||
|
'3': """
|
||||||
|
░▀██
|
||||||
|
░▄▄█
|
||||||
|
""",
|
||||||
|
'4': """
|
||||||
|
░█▄
|
||||||
|
░░█
|
||||||
|
""",
|
||||||
|
'5': """
|
||||||
|
░█▀
|
||||||
|
░▄█
|
||||||
|
""",
|
||||||
|
'6': """
|
||||||
|
░█▀
|
||||||
|
░██
|
||||||
|
""",
|
||||||
|
'7': """
|
||||||
|
░▀█
|
||||||
|
░█░
|
||||||
|
""",
|
||||||
|
'8': """
|
||||||
|
░█▄█
|
||||||
|
░█▄█
|
||||||
|
""",
|
||||||
|
'9': """
|
||||||
|
░██
|
||||||
|
░▄█
|
||||||
|
""",
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsupported_char := """
|
||||||
|
░▒░
|
||||||
|
▒░▒
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
static func str_to_boxed_art(p_text: String) -> PackedStringArray:
|
||||||
|
var lines: PackedStringArray = []
|
||||||
|
lines.resize(2)
|
||||||
|
for c in p_text:
|
||||||
|
var ascii: String = boxed_map.get(c.to_lower(), unsupported_char)
|
||||||
|
var parts: PackedStringArray = ascii.split('\n')
|
||||||
|
lines[0] += parts[1]
|
||||||
|
lines[1] += parts[2]
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
static func is_boxed_art_supported(p_text: String) -> bool:
|
||||||
|
for c in p_text:
|
||||||
|
if not boxed_map.has(c.to_lower()):
|
||||||
|
return false
|
||||||
|
return true
|
1
addons/limbo_console/ascii_art.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://coot1au4b7np
|
169
addons/limbo_console/builtin_commands.gd
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
extends RefCounted
|
||||||
|
## BuiltinCommands
|
||||||
|
|
||||||
|
|
||||||
|
const Util := preload("res://addons/limbo_console/util.gd")
|
||||||
|
|
||||||
|
|
||||||
|
static func register_commands() -> void:
|
||||||
|
LimboConsole.register_command(cmd_alias, "alias", "add command alias")
|
||||||
|
LimboConsole.register_command(cmd_aliases, "aliases", "list all aliases")
|
||||||
|
LimboConsole.register_command(LimboConsole.clear_console, "clear", "clear console screen")
|
||||||
|
LimboConsole.register_command(cmd_commands, "commands", "list all commands")
|
||||||
|
LimboConsole.register_command(LimboConsole.info, "echo", "display a line of text")
|
||||||
|
LimboConsole.register_command(cmd_eval, "eval", "evaluate an expression")
|
||||||
|
LimboConsole.register_command(cmd_exec, "exec", "execute commands from file")
|
||||||
|
LimboConsole.register_command(cmd_fps_max, "fps_max", "limit framerate")
|
||||||
|
LimboConsole.register_command(cmd_fullscreen, "fullscreen", "toggle fullscreen mode")
|
||||||
|
LimboConsole.register_command(cmd_help, "help", "show command info")
|
||||||
|
LimboConsole.register_command(cmd_log, "log", "show recent log entries")
|
||||||
|
LimboConsole.register_command(cmd_quit, "quit", "exit the application")
|
||||||
|
LimboConsole.register_command(cmd_unalias, "unalias", "remove command alias")
|
||||||
|
LimboConsole.register_command(cmd_vsync, "vsync", "adjust V-Sync")
|
||||||
|
LimboConsole.register_command(LimboConsole.erase_history, "erase_history", "erases current history and persisted history")
|
||||||
|
LimboConsole.add_argument_autocomplete_source("help", 1, LimboConsole.get_command_names.bind(true))
|
||||||
|
|
||||||
|
|
||||||
|
static func cmd_alias(p_alias: String, p_command: String) -> void:
|
||||||
|
LimboConsole.info("Adding %s => %s" % [LimboConsole.format_name(p_alias), p_command])
|
||||||
|
LimboConsole.add_alias(p_alias, p_command)
|
||||||
|
|
||||||
|
|
||||||
|
static func cmd_aliases() -> void:
|
||||||
|
var aliases: Array = LimboConsole.get_aliases()
|
||||||
|
aliases.sort()
|
||||||
|
for alias in aliases:
|
||||||
|
var alias_argv: PackedStringArray = LimboConsole.get_alias_argv(alias)
|
||||||
|
var cmd_name: String = alias_argv[0]
|
||||||
|
var desc: String = LimboConsole.get_command_description(cmd_name)
|
||||||
|
alias_argv[0] = LimboConsole.format_name(cmd_name)
|
||||||
|
if desc.is_empty():
|
||||||
|
LimboConsole.info(LimboConsole.format_name(alias))
|
||||||
|
else:
|
||||||
|
LimboConsole.info("%s is alias of: %s %s" % [
|
||||||
|
LimboConsole.format_name(alias),
|
||||||
|
' '.join(alias_argv),
|
||||||
|
LimboConsole.format_tip(" // " + desc)
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
static func cmd_commands() -> void:
|
||||||
|
LimboConsole.info("Available commands:")
|
||||||
|
for name in LimboConsole.get_command_names(false):
|
||||||
|
var desc: String = LimboConsole.get_command_description(name)
|
||||||
|
name = LimboConsole.format_name(name)
|
||||||
|
LimboConsole.info(name if desc.is_empty() else "%s -- %s" % [name, desc])
|
||||||
|
|
||||||
|
|
||||||
|
static func cmd_eval(p_expression: String) -> Error:
|
||||||
|
var exp := Expression.new()
|
||||||
|
var err: int = exp.parse(p_expression, LimboConsole.get_eval_input_names())
|
||||||
|
if err != OK:
|
||||||
|
LimboConsole.error(exp.get_error_text())
|
||||||
|
return err
|
||||||
|
var result = exp.execute(LimboConsole.get_eval_inputs(),
|
||||||
|
LimboConsole.get_eval_base_instance())
|
||||||
|
if not exp.has_execute_failed():
|
||||||
|
if result != null:
|
||||||
|
LimboConsole.info(str(result))
|
||||||
|
return OK
|
||||||
|
else:
|
||||||
|
LimboConsole.error(exp.get_error_text())
|
||||||
|
return ERR_SCRIPT_FAILED
|
||||||
|
|
||||||
|
|
||||||
|
static func cmd_exec(p_file: String, p_silent: bool = true) -> void:
|
||||||
|
if not p_file.ends_with(".lcs"):
|
||||||
|
# Prevent users from reading other game assets.
|
||||||
|
p_file += ".lcs"
|
||||||
|
if not FileAccess.file_exists(p_file):
|
||||||
|
p_file = "user://" + p_file
|
||||||
|
LimboConsole.execute_script(p_file, p_silent)
|
||||||
|
|
||||||
|
|
||||||
|
static func cmd_fps_max(p_limit: int = -1) -> void:
|
||||||
|
if p_limit < 0:
|
||||||
|
if Engine.max_fps == 0:
|
||||||
|
LimboConsole.info("Framerate is unlimited.")
|
||||||
|
else:
|
||||||
|
LimboConsole.info("Framerate is limited to %d FPS." % [Engine.max_fps])
|
||||||
|
return
|
||||||
|
|
||||||
|
Engine.max_fps = p_limit
|
||||||
|
if p_limit > 0:
|
||||||
|
LimboConsole.info("Limiting framerate to %d FPS." % [p_limit])
|
||||||
|
elif p_limit == 0:
|
||||||
|
LimboConsole.info("Removing framerate limits.")
|
||||||
|
|
||||||
|
|
||||||
|
static func cmd_fullscreen() -> void:
|
||||||
|
if LimboConsole.get_viewport().mode == Window.MODE_WINDOWED:
|
||||||
|
# get_viewport().mode = Window.MODE_EXCLUSIVE_FULLSCREEN
|
||||||
|
LimboConsole.get_viewport().mode = Window.MODE_FULLSCREEN
|
||||||
|
LimboConsole.info("Window switched to fullscreen mode.")
|
||||||
|
else:
|
||||||
|
LimboConsole.get_viewport().mode = Window.MODE_WINDOWED
|
||||||
|
LimboConsole.info("Window switched to windowed mode.")
|
||||||
|
|
||||||
|
|
||||||
|
static func cmd_help(p_command_name: String = "") -> Error:
|
||||||
|
if p_command_name.is_empty():
|
||||||
|
LimboConsole.print_line(LimboConsole.format_tip("Type %s to list all available commands." %
|
||||||
|
[LimboConsole.format_name("commands")]))
|
||||||
|
LimboConsole.print_line(LimboConsole.format_tip("Type %s to get more info about the command." %
|
||||||
|
[LimboConsole.format_name("help command")]))
|
||||||
|
return OK
|
||||||
|
else:
|
||||||
|
return LimboConsole.usage(p_command_name)
|
||||||
|
|
||||||
|
|
||||||
|
static func cmd_log(p_num_lines: int = 10) -> Error:
|
||||||
|
var fn: String = ProjectSettings.get_setting("debug/file_logging/log_path")
|
||||||
|
var file = FileAccess.open(fn, FileAccess.READ)
|
||||||
|
if not file:
|
||||||
|
LimboConsole.error("Can't open file: " + fn)
|
||||||
|
return ERR_CANT_OPEN
|
||||||
|
var contents := file.get_as_text()
|
||||||
|
var lines := contents.split('\n')
|
||||||
|
if lines.size() and lines[lines.size() - 1].strip_edges() == "":
|
||||||
|
lines.remove_at(lines.size() - 1)
|
||||||
|
lines = lines.slice(maxi(lines.size() - p_num_lines, 0))
|
||||||
|
for line in lines:
|
||||||
|
LimboConsole.print_line(Util.bbcode_escape(line), false)
|
||||||
|
return OK
|
||||||
|
|
||||||
|
|
||||||
|
static func cmd_quit() -> void:
|
||||||
|
LimboConsole.get_tree().quit()
|
||||||
|
|
||||||
|
|
||||||
|
static func cmd_unalias(p_alias: String) -> void:
|
||||||
|
if LimboConsole.has_alias(p_alias):
|
||||||
|
LimboConsole.remove_alias(p_alias)
|
||||||
|
LimboConsole.info("Alias removed.")
|
||||||
|
else:
|
||||||
|
LimboConsole.warn("Alias not found.")
|
||||||
|
|
||||||
|
|
||||||
|
static func cmd_vsync(p_mode: int = -1) -> void:
|
||||||
|
if p_mode < 0:
|
||||||
|
var current: int = DisplayServer.window_get_vsync_mode()
|
||||||
|
if current == 0:
|
||||||
|
LimboConsole.info("V-Sync: disabled.")
|
||||||
|
elif current == 1:
|
||||||
|
LimboConsole.info('V-Sync: enabled.')
|
||||||
|
elif current == 2:
|
||||||
|
LimboConsole.info('Current V-Sync mode: adaptive.')
|
||||||
|
LimboConsole.info("Adjust V-Sync mode with an argument: 0 - disabled, 1 - enabled, 2 - adaptive.")
|
||||||
|
elif p_mode == DisplayServer.VSYNC_DISABLED:
|
||||||
|
LimboConsole.info("Changing to disabled.")
|
||||||
|
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
|
||||||
|
elif p_mode == DisplayServer.VSYNC_ENABLED:
|
||||||
|
LimboConsole.info("Changing to default V-Sync.")
|
||||||
|
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED)
|
||||||
|
elif p_mode == DisplayServer.VSYNC_ADAPTIVE:
|
||||||
|
LimboConsole.info("Changing to adaptive V-Sync.")
|
||||||
|
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ADAPTIVE)
|
||||||
|
else:
|
||||||
|
LimboConsole.error("Invalid mode.")
|
||||||
|
LimboConsole.info("Acceptable modes: 0 - disabled, 1 - enabled, 2 - adaptive.")
|
1
addons/limbo_console/builtin_commands.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://npao6ckwv7cm
|
130
addons/limbo_console/command_entry.gd
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
extends TextEdit
|
||||||
|
## CommandEntry
|
||||||
|
|
||||||
|
|
||||||
|
signal text_submitted(command_line: String)
|
||||||
|
signal autocomplete_requested()
|
||||||
|
|
||||||
|
var autocomplete_hint: String:
|
||||||
|
set(value):
|
||||||
|
if autocomplete_hint != value:
|
||||||
|
autocomplete_hint = value
|
||||||
|
queue_redraw()
|
||||||
|
|
||||||
|
var _font: Font
|
||||||
|
var _font_size: int
|
||||||
|
var _hint_color: Color
|
||||||
|
var _sb_normal: StyleBox
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
syntax_highlighter = CommandEntryHighlighter.new()
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
caret_multiple = false
|
||||||
|
autowrap_mode = TextServer.AUTOWRAP_OFF
|
||||||
|
scroll_fit_content_height = true
|
||||||
|
# placeholder_text = ""
|
||||||
|
|
||||||
|
get_v_scroll_bar().visibility_changed.connect(_hide_scrollbars)
|
||||||
|
get_h_scroll_bar().visibility_changed.connect(_hide_scrollbars)
|
||||||
|
_hide_scrollbars()
|
||||||
|
|
||||||
|
_font = get_theme_font("font")
|
||||||
|
_font_size = get_theme_font_size("font_size")
|
||||||
|
_hint_color = get_theme_color("hint_color")
|
||||||
|
_sb_normal = get_theme_stylebox("normal")
|
||||||
|
|
||||||
|
|
||||||
|
func _notification(what: int) -> void:
|
||||||
|
match what:
|
||||||
|
NOTIFICATION_FOCUS_ENTER:
|
||||||
|
set_process_input(true)
|
||||||
|
NOTIFICATION_FOCUS_EXIT:
|
||||||
|
set_process_input(false)
|
||||||
|
|
||||||
|
|
||||||
|
func _input(event: InputEvent) -> void:
|
||||||
|
if not has_focus():
|
||||||
|
return
|
||||||
|
if event is InputEventKey:
|
||||||
|
if event.keycode == KEY_ENTER or event.keycode == KEY_KP_ENTER:
|
||||||
|
if event.is_pressed():
|
||||||
|
submit_text()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
elif event.keycode == KEY_C and event.get_modifiers_mask() == KEY_MASK_CTRL and get_selected_text().is_empty():
|
||||||
|
# Clear input on Ctrl+C if no text selected.
|
||||||
|
if event.is_pressed():
|
||||||
|
text = ""
|
||||||
|
text_changed.emit()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
elif event.keycode in [KEY_RIGHT, KEY_END] and get_caret_column() == text.length():
|
||||||
|
# Request autocomplete on RIGHT & END.
|
||||||
|
if event.is_pressed() and not autocomplete_hint.is_empty():
|
||||||
|
autocomplete_requested.emit()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
var offset_x: int = 0
|
||||||
|
offset_x += _sb_normal.get_offset().x * 0.5
|
||||||
|
offset_x += get_line_width(0)
|
||||||
|
|
||||||
|
var offset_y: int = 0
|
||||||
|
offset_y += _sb_normal.get_offset().y * 0.5
|
||||||
|
offset_y += get_line_height() + 0.5 # + line_spacing
|
||||||
|
offset_y -= _font.get_descent(_font_size)
|
||||||
|
|
||||||
|
draw_string(_font, Vector2(offset_x, offset_y), autocomplete_hint, 0, -1, _font_size, _hint_color)
|
||||||
|
|
||||||
|
|
||||||
|
func submit_text() -> void:
|
||||||
|
text_submitted.emit(text)
|
||||||
|
|
||||||
|
|
||||||
|
func _hide_scrollbars() -> void:
|
||||||
|
get_v_scroll_bar().hide()
|
||||||
|
get_h_scroll_bar().hide()
|
||||||
|
|
||||||
|
|
||||||
|
class CommandEntryHighlighter extends SyntaxHighlighter:
|
||||||
|
var command_found_color: Color
|
||||||
|
var subcommand_color: Color
|
||||||
|
var command_not_found_color: Color
|
||||||
|
var text_color: Color
|
||||||
|
|
||||||
|
func _get_line_syntax_highlighting(line: int) -> Dictionary:
|
||||||
|
var text: String = get_text_edit().text
|
||||||
|
var command_end_idx: int = -1 # index where last recognized command ends (with subcommands)
|
||||||
|
|
||||||
|
var argv: PackedStringArray = [] # argument vector (aka tokens)
|
||||||
|
var argi: PackedInt32Array = [] # argument starting indices in text
|
||||||
|
var start: int = 0
|
||||||
|
var cur: int = 0
|
||||||
|
for char in text + ' ':
|
||||||
|
if char == ' ':
|
||||||
|
if cur > start:
|
||||||
|
argv.append(text.substr(start, cur - start))
|
||||||
|
argi.append(start)
|
||||||
|
var maybe_command: String = ' '.join(argv)
|
||||||
|
if LimboConsole.has_command(maybe_command) or LimboConsole.has_alias(maybe_command):
|
||||||
|
command_end_idx = cur
|
||||||
|
start = cur + 1
|
||||||
|
cur += 1
|
||||||
|
|
||||||
|
var command_color: Color
|
||||||
|
var arg_start_idx: int = 0 # index where arguments start
|
||||||
|
|
||||||
|
if command_end_idx > -1:
|
||||||
|
command_color = command_found_color
|
||||||
|
arg_start_idx = command_end_idx + 1
|
||||||
|
else:
|
||||||
|
command_color = command_not_found_color
|
||||||
|
arg_start_idx = argi[1] if argi.size() > 1 else text.length()
|
||||||
|
|
||||||
|
var result: Dictionary
|
||||||
|
result[0] = { "color": command_color }
|
||||||
|
if command_end_idx > -1 and argi.size() > 1:
|
||||||
|
result[argi[1]] = { "color": subcommand_color }
|
||||||
|
result[arg_start_idx] = { "color": text_color }
|
||||||
|
return result
|
1
addons/limbo_console/command_entry.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://ddtreeatktyov
|
165
addons/limbo_console/command_history.gd
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
extends RefCounted
|
||||||
|
## Manages command history.
|
||||||
|
|
||||||
|
|
||||||
|
const HISTORY_FILE := "user://limbo_console_history.log"
|
||||||
|
|
||||||
|
|
||||||
|
var _entries: PackedStringArray
|
||||||
|
var _hist_idx = -1
|
||||||
|
var _iterators: Array[WrappingIterator]
|
||||||
|
var _is_dirty: bool = false
|
||||||
|
|
||||||
|
|
||||||
|
func push_entry(p_entry: String) -> void:
|
||||||
|
_push_entry(p_entry)
|
||||||
|
_reset_iterators()
|
||||||
|
|
||||||
|
|
||||||
|
func _push_entry(p_entry: String) -> void:
|
||||||
|
var idx: int = _entries.find(p_entry)
|
||||||
|
if idx != -1:
|
||||||
|
# Duplicate commands not allowed in history.
|
||||||
|
_entries.remove_at(idx)
|
||||||
|
_entries.append(p_entry)
|
||||||
|
_is_dirty = true
|
||||||
|
|
||||||
|
|
||||||
|
func get_entry(p_index: int) -> String:
|
||||||
|
return _entries[clampi(p_index, 0, _entries.size())]
|
||||||
|
|
||||||
|
|
||||||
|
func create_iterator() -> WrappingIterator:
|
||||||
|
var it := WrappingIterator.new(_entries)
|
||||||
|
_iterators.append(it)
|
||||||
|
return it
|
||||||
|
|
||||||
|
|
||||||
|
func release_iterator(p_iter: WrappingIterator) -> void:
|
||||||
|
_iterators.erase(p_iter)
|
||||||
|
|
||||||
|
|
||||||
|
func size() -> int:
|
||||||
|
return _entries.size()
|
||||||
|
|
||||||
|
|
||||||
|
func trim(p_max_size: int) -> void:
|
||||||
|
if _entries.size() > p_max_size:
|
||||||
|
_entries.slice(p_max_size - _entries.size())
|
||||||
|
_reset_iterators()
|
||||||
|
|
||||||
|
|
||||||
|
func clear() -> void:
|
||||||
|
_entries.clear()
|
||||||
|
|
||||||
|
|
||||||
|
func load(p_path: String = HISTORY_FILE) -> void:
|
||||||
|
var file := FileAccess.open(p_path, FileAccess.READ)
|
||||||
|
if not file:
|
||||||
|
return
|
||||||
|
while not file.eof_reached():
|
||||||
|
var line: String = file.get_line().strip_edges()
|
||||||
|
if not line.is_empty():
|
||||||
|
_push_entry(line)
|
||||||
|
file.close()
|
||||||
|
_reset_iterators()
|
||||||
|
_is_dirty = false
|
||||||
|
|
||||||
|
|
||||||
|
func save(p_path: String = HISTORY_FILE) -> void:
|
||||||
|
if not _is_dirty:
|
||||||
|
return
|
||||||
|
var file := FileAccess.open(p_path, FileAccess.WRITE)
|
||||||
|
if not file:
|
||||||
|
push_error("LimboConsole: Failed to save console history to file: ", p_path)
|
||||||
|
return
|
||||||
|
for line in _entries:
|
||||||
|
file.store_line(line)
|
||||||
|
file.close()
|
||||||
|
_is_dirty = false
|
||||||
|
|
||||||
|
|
||||||
|
## Searches history and returns an array starting with most relevant entries.
|
||||||
|
func fuzzy_match(p_query: String) -> PackedStringArray:
|
||||||
|
if len(p_query) == 0:
|
||||||
|
var copy := _entries.duplicate()
|
||||||
|
copy.reverse()
|
||||||
|
return copy
|
||||||
|
|
||||||
|
var results: Array = []
|
||||||
|
for entry: String in _entries:
|
||||||
|
var score: int = _compute_match_score(p_query.to_lower(), entry.to_lower())
|
||||||
|
if score > 0:
|
||||||
|
results.append({"entry": entry, "score": score})
|
||||||
|
|
||||||
|
results.sort_custom(func(a, b): return a.score > b.score)
|
||||||
|
return results.map(func(rec): return rec.entry)
|
||||||
|
|
||||||
|
|
||||||
|
func _reset_iterators() -> void:
|
||||||
|
for it in _iterators:
|
||||||
|
it._reassign(_entries)
|
||||||
|
|
||||||
|
|
||||||
|
## Scoring function for fuzzy matching.
|
||||||
|
static func _compute_match_score(query: String, target: String) -> int:
|
||||||
|
var score: int = 0
|
||||||
|
var query_index: int = 0
|
||||||
|
|
||||||
|
# Exact match. give unbeatable score
|
||||||
|
if query == target:
|
||||||
|
score = 99999
|
||||||
|
return score
|
||||||
|
|
||||||
|
for i in range(target.length()):
|
||||||
|
if query_index < query.length() and target[i] == query[query_index]:
|
||||||
|
score += 10 # Base score for a match
|
||||||
|
if i == 0 or target[i - 1] == " ": # Bonus for word start
|
||||||
|
score += 5
|
||||||
|
query_index += 1
|
||||||
|
if query_index == query.length():
|
||||||
|
break
|
||||||
|
|
||||||
|
# Ensure full query matches
|
||||||
|
return score if query_index == query.length() else 0
|
||||||
|
|
||||||
|
|
||||||
|
## Iterator that wraps around and resets on history change.
|
||||||
|
class WrappingIterator:
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
var _idx: int = -1
|
||||||
|
var _entries: PackedStringArray
|
||||||
|
|
||||||
|
|
||||||
|
func _init(p_entries: PackedStringArray) -> void:
|
||||||
|
_entries = p_entries
|
||||||
|
|
||||||
|
|
||||||
|
func prev() -> String:
|
||||||
|
_idx = wrapi(_idx - 1, -1, _entries.size())
|
||||||
|
if _idx == -1:
|
||||||
|
return String()
|
||||||
|
return _entries[_idx]
|
||||||
|
|
||||||
|
|
||||||
|
func next() -> String:
|
||||||
|
_idx = wrapi(_idx + 1, -1, _entries.size())
|
||||||
|
if _idx == -1:
|
||||||
|
return String()
|
||||||
|
return _entries[_idx]
|
||||||
|
|
||||||
|
|
||||||
|
func current() -> String:
|
||||||
|
if _idx < 0 or _idx >= _entries.size():
|
||||||
|
return String()
|
||||||
|
return _entries[_idx]
|
||||||
|
|
||||||
|
|
||||||
|
func reset() -> void:
|
||||||
|
_idx = -1
|
||||||
|
|
||||||
|
|
||||||
|
func _reassign(p_entries: PackedStringArray) -> void:
|
||||||
|
_idx = -1
|
||||||
|
_entries = p_entries
|
1
addons/limbo_console/command_history.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dc55ouwu3ylf
|
77
addons/limbo_console/config_mapper.gd
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
@tool
|
||||||
|
extends RefCounted
|
||||||
|
## Store object properties in an INI-style configuration file.
|
||||||
|
|
||||||
|
|
||||||
|
const CONFIG_PATH_PROPERTY := &"CONFIG_PATH"
|
||||||
|
const MAIN_SECTION_PROPERTY := &"MAIN_SECTION"
|
||||||
|
const MAIN_SECTION_DEFAULT := "main"
|
||||||
|
|
||||||
|
static var verbose: bool = false
|
||||||
|
|
||||||
|
static func _get_config_file(p_object: Object) -> String:
|
||||||
|
var from_object_constant = p_object.get(CONFIG_PATH_PROPERTY)
|
||||||
|
return from_object_constant if from_object_constant is String else ""
|
||||||
|
|
||||||
|
|
||||||
|
static func _get_main_section(p_object: Object) -> String:
|
||||||
|
var from_object_constant = p_object.get(MAIN_SECTION_PROPERTY)
|
||||||
|
return from_object_constant if from_object_constant != null else MAIN_SECTION_DEFAULT
|
||||||
|
|
||||||
|
|
||||||
|
static func _msg(p_text: String, p_arg1 = "") -> void:
|
||||||
|
if verbose:
|
||||||
|
print(p_text, p_arg1)
|
||||||
|
|
||||||
|
|
||||||
|
## Load object properties from config file and return status. [br]
|
||||||
|
## If p_config_path is empty, configuration path is taken from object's CONFIG_PATH property.
|
||||||
|
static func load_from_config(p_object: Object, p_config_path: String = "") -> int:
|
||||||
|
var config_path: String = p_config_path
|
||||||
|
if config_path.is_empty():
|
||||||
|
config_path = _get_config_file(p_object)
|
||||||
|
var section: String = _get_main_section(p_object)
|
||||||
|
var config := ConfigFile.new()
|
||||||
|
var err: int = config.load(config_path)
|
||||||
|
if err != OK:
|
||||||
|
_msg("ConfigMapper: Failed to load config: %s err_code: %d" % [config_path, err])
|
||||||
|
return err
|
||||||
|
_msg("ConfigMapper: Loading config: ", config_path)
|
||||||
|
|
||||||
|
for prop_info in p_object.get_property_list():
|
||||||
|
if prop_info.usage & PROPERTY_USAGE_CATEGORY and prop_info.hint_string.is_empty():
|
||||||
|
_msg("ConfigMapper: Processing category: ", prop_info.name)
|
||||||
|
section = prop_info.name
|
||||||
|
elif prop_info.usage & PROPERTY_USAGE_SCRIPT_VARIABLE and prop_info.usage & PROPERTY_USAGE_STORAGE:
|
||||||
|
var value = null
|
||||||
|
if config.has_section_key(section, prop_info.name):
|
||||||
|
value = config.get_value(section, prop_info.name)
|
||||||
|
if value != null and typeof(value) == prop_info.type:
|
||||||
|
_msg("ConfigMapper: Loaded setting: %s section: %s value: %s" % [prop_info.name, section, value])
|
||||||
|
p_object.set(prop_info.name, value)
|
||||||
|
_msg("ConfigMapper: Finished with code: ", OK)
|
||||||
|
return OK
|
||||||
|
|
||||||
|
|
||||||
|
## Save object properties to config file and return status. [br]
|
||||||
|
## If p_config_path is empty, configuration path is taken from object's CONFIG_PATH property. [br]
|
||||||
|
## WARNING: replaces file contents!
|
||||||
|
static func save_to_config(p_object: Object, p_config_path: String = "") -> int:
|
||||||
|
var config_path: String = p_config_path
|
||||||
|
if config_path.is_empty():
|
||||||
|
config_path = _get_config_file(p_object)
|
||||||
|
var section: String = _get_main_section(p_object)
|
||||||
|
var config := ConfigFile.new()
|
||||||
|
_msg("ConfigMapper: Saving config: ", config_path)
|
||||||
|
|
||||||
|
for prop_info in p_object.get_property_list():
|
||||||
|
if prop_info.usage & PROPERTY_USAGE_CATEGORY and prop_info.hint_string.is_empty():
|
||||||
|
_msg("ConfigMapper: Processing category: ", prop_info.name)
|
||||||
|
section = prop_info.name
|
||||||
|
elif prop_info.usage & PROPERTY_USAGE_SCRIPT_VARIABLE and prop_info.usage & PROPERTY_USAGE_STORAGE:
|
||||||
|
_msg("ConfigMapper: Saving setting: %s section: %s value: %s" % [prop_info.name, section, p_object.get(prop_info.name)])
|
||||||
|
config.set_value(section, prop_info.name, p_object.get(prop_info.name))
|
||||||
|
|
||||||
|
var err: int = config.save(config_path)
|
||||||
|
_msg("ConfigMapper: Finished with code: ", err)
|
||||||
|
return err
|
1
addons/limbo_console/config_mapper.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dvokj0q23nb50
|
40
addons/limbo_console/console_options.gd
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
# Configuration is outside of limbo_console directory for compatibility with GIT submodules
|
||||||
|
const CONFIG_PATH := "res://addons/limbo_console.cfg"
|
||||||
|
|
||||||
|
@export var aliases := {
|
||||||
|
"exit": "quit",
|
||||||
|
"source": "exec",
|
||||||
|
"usage": "help",
|
||||||
|
}
|
||||||
|
@export var disable_in_release_build: bool = false
|
||||||
|
@export var print_to_stdout: bool = false
|
||||||
|
@export var pause_when_open: bool = true
|
||||||
|
|
||||||
|
@export var commands_disabled_in_release: Array = [
|
||||||
|
"eval" # enables arbitrary code execution and asset extraction in the running game.
|
||||||
|
]
|
||||||
|
|
||||||
|
@export_category("appearance")
|
||||||
|
@export var custom_theme: String = "res://addons/limbo_console_theme.tres"
|
||||||
|
@export var height_ratio: float = 0.5
|
||||||
|
@export var open_speed: float = 5.0 # For instant, set to a really high float like 99999.0
|
||||||
|
@export var opacity: float = 1.0
|
||||||
|
@export var sparse_mode: bool = false # Print empty line after each command execution.
|
||||||
|
|
||||||
|
@export_category("greet")
|
||||||
|
@export var greet_user: bool = true
|
||||||
|
@export var greeting_message: String = "{project_name}"
|
||||||
|
@export var greet_using_ascii_art: bool = true
|
||||||
|
|
||||||
|
@export_category("history")
|
||||||
|
@export var persist_history: bool = true
|
||||||
|
@export var history_lines: int = 1000
|
||||||
|
|
||||||
|
@export_category("autocomplete")
|
||||||
|
@export var autocomplete_use_history_with_matches: bool = true
|
||||||
|
|
||||||
|
@export_category("autoexec")
|
||||||
|
@export var autoexec_script: String = "user://autoexec.lcs"
|
||||||
|
@export var autoexec_auto_create: bool = true
|
1
addons/limbo_console/console_options.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://c7a12a1pe5esr
|
256
addons/limbo_console/history_gui.gd
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
extends Panel
|
||||||
|
|
||||||
|
const CommandHistory := preload("res://addons/limbo_console/command_history.gd")
|
||||||
|
|
||||||
|
# Visual Elements
|
||||||
|
var _last_highlighted_label: Label
|
||||||
|
var _history_labels: Array[Label]
|
||||||
|
var _scroll_bar: VScrollBar
|
||||||
|
var _scroll_bar_width: int = 12
|
||||||
|
|
||||||
|
# Indexing Results
|
||||||
|
var _command: String = "<placeholder>" # Needs default value so first search always processes
|
||||||
|
var _history: CommandHistory # Command history to search through
|
||||||
|
var _filter_results: PackedStringArray # Most recent results of performing a search for the _command in _history
|
||||||
|
|
||||||
|
var _display_count: int = 0 # Number of history items to display in search
|
||||||
|
var _offset: int = 0 # The offset _filter_results
|
||||||
|
var _sub_index: int = 0 # The highlight index
|
||||||
|
|
||||||
|
# Theme Cache
|
||||||
|
var _highlight_color: Color
|
||||||
|
|
||||||
|
|
||||||
|
# *** GODOT / VIRTUAL
|
||||||
|
|
||||||
|
|
||||||
|
func _init(p_history: CommandHistory) -> void:
|
||||||
|
_history = p_history
|
||||||
|
|
||||||
|
set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
|
size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||||
|
|
||||||
|
# Create first label, and set placeholder text to determine the display size
|
||||||
|
# once this node is _ready(). There should always be one label at minimum
|
||||||
|
# anyways since this search is usless without a way to show results.
|
||||||
|
var new_item := Label.new()
|
||||||
|
new_item.size_flags_vertical = Control.SIZE_SHRINK_END
|
||||||
|
new_item.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
new_item.text = "<Placeholder>"
|
||||||
|
add_child(new_item)
|
||||||
|
_history_labels.append(new_item)
|
||||||
|
|
||||||
|
_scroll_bar = VScrollBar.new()
|
||||||
|
add_child(_scroll_bar)
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
# The sizing of the labels is dependant on visiblity.
|
||||||
|
visibility_changed.connect(_calculate_display_count)
|
||||||
|
_scroll_bar.scrolling.connect(_scroll_bar_scrolled)
|
||||||
|
|
||||||
|
_highlight_color = get_theme_color(&"history_highlight_color", &"ConsoleColors")
|
||||||
|
|
||||||
|
|
||||||
|
func _input(event: InputEvent) -> void:
|
||||||
|
if not is_visible_in_tree():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Scroll up/down on mouse wheel up/down
|
||||||
|
if event is InputEventMouseButton:
|
||||||
|
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
|
||||||
|
_increment_index()
|
||||||
|
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
|
||||||
|
_decrement_index()
|
||||||
|
|
||||||
|
# Remaining inputs are key press handles
|
||||||
|
if event is not InputEventKey:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Increment/Decrement index
|
||||||
|
if event.keycode == KEY_UP and event.is_pressed():
|
||||||
|
_increment_index()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
elif event.keycode == KEY_DOWN and event.is_pressed():
|
||||||
|
_decrement_index()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
|
||||||
|
|
||||||
|
# *** PUBLIC
|
||||||
|
|
||||||
|
|
||||||
|
## Set visibility of history search
|
||||||
|
func set_visibility(p_visible: bool) -> void:
|
||||||
|
if not visible and p_visible:
|
||||||
|
# It's possible the _history has updated while not visible
|
||||||
|
# make sure the filtered list is up-to-date
|
||||||
|
_search_and_filter()
|
||||||
|
visible = p_visible
|
||||||
|
|
||||||
|
|
||||||
|
## Move cursor downwards
|
||||||
|
func _decrement_index() -> void:
|
||||||
|
var current_index: int = _get_current_index()
|
||||||
|
if current_index - 1 < 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
if _sub_index == 0:
|
||||||
|
_offset -= 1
|
||||||
|
_update_scroll_list()
|
||||||
|
else:
|
||||||
|
_sub_index -= 1
|
||||||
|
_update_highlight()
|
||||||
|
|
||||||
|
|
||||||
|
## Move cursor upwards
|
||||||
|
func _increment_index() -> void:
|
||||||
|
var current_index: int = _get_current_index()
|
||||||
|
if current_index + 1 >= _filter_results.size():
|
||||||
|
return
|
||||||
|
|
||||||
|
if _sub_index >= _display_count - 1:
|
||||||
|
_offset += 1
|
||||||
|
_update_scroll_list()
|
||||||
|
else:
|
||||||
|
_sub_index += 1
|
||||||
|
_update_highlight()
|
||||||
|
|
||||||
|
|
||||||
|
## Get the current selected text
|
||||||
|
func get_current_text() -> String:
|
||||||
|
var current_text: String = _command
|
||||||
|
if _history_labels.size() != 0 and _filter_results.size() != 0:
|
||||||
|
current_text = _filter_results[_get_current_index()]
|
||||||
|
return current_text
|
||||||
|
|
||||||
|
|
||||||
|
## Search for the command in the history
|
||||||
|
func search(command: String) -> void:
|
||||||
|
# Don't process if we used the same command before
|
||||||
|
if command == _command:
|
||||||
|
return
|
||||||
|
_command = command
|
||||||
|
|
||||||
|
_search_and_filter()
|
||||||
|
|
||||||
|
|
||||||
|
# *** PRIVATE
|
||||||
|
|
||||||
|
|
||||||
|
## Update the text in the scroll list to match current offset and filtered results
|
||||||
|
func _update_scroll_list() -> void:
|
||||||
|
# Iterate through the number of displayed history items
|
||||||
|
for i in range(0, _display_count):
|
||||||
|
var filter_index: int = _offset + i
|
||||||
|
|
||||||
|
# Default empty
|
||||||
|
_history_labels[i].text = ""
|
||||||
|
|
||||||
|
# Set non empty if in range
|
||||||
|
var index_in_range: bool = filter_index < _filter_results.size()
|
||||||
|
if index_in_range:
|
||||||
|
_history_labels[i].text += _filter_results[filter_index]
|
||||||
|
|
||||||
|
_update_scroll_bar()
|
||||||
|
|
||||||
|
|
||||||
|
## Highlight the subindex
|
||||||
|
func _update_highlight() -> void:
|
||||||
|
if _sub_index < 0 or _history.size() == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
var style := StyleBoxFlat.new()
|
||||||
|
style.bg_color = _highlight_color
|
||||||
|
|
||||||
|
# Always clear out the highlight of the last label
|
||||||
|
if is_instance_valid(_last_highlighted_label):
|
||||||
|
_last_highlighted_label.remove_theme_stylebox_override("normal")
|
||||||
|
|
||||||
|
if _filter_results.size() <= 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
_history_labels[_sub_index].add_theme_stylebox_override("normal", style)
|
||||||
|
_last_highlighted_label = _history_labels[_sub_index]
|
||||||
|
|
||||||
|
|
||||||
|
## Get the current index of the selected item
|
||||||
|
func _get_current_index() -> int:
|
||||||
|
return _offset + _sub_index
|
||||||
|
|
||||||
|
|
||||||
|
## Reset offset and sub_indexes to scroll list back to bottom
|
||||||
|
func _reset_indexes() -> void:
|
||||||
|
_offset = 0
|
||||||
|
_sub_index = 0
|
||||||
|
|
||||||
|
|
||||||
|
## When the scrollbar has been scrolled (by mouse), scroll the list
|
||||||
|
func _scroll_bar_scrolled() -> void:
|
||||||
|
_offset = _scroll_bar.max_value - _display_count - _scroll_bar.value
|
||||||
|
_update_highlight()
|
||||||
|
_update_scroll_list()
|
||||||
|
|
||||||
|
|
||||||
|
func _calculate_display_count():
|
||||||
|
if not visible:
|
||||||
|
return
|
||||||
|
# The display count is finnicky to get right due to the label needing to be
|
||||||
|
# rendered so the fize can be determined. This gets the job done, it ain't
|
||||||
|
# pretty, but it works
|
||||||
|
var max_y: float = size.y
|
||||||
|
|
||||||
|
var label_size_y: float = (_history_labels[0] as Control).size.y
|
||||||
|
var label_size_x: float = size.x - _scroll_bar_width
|
||||||
|
|
||||||
|
var display_count: int = int(max_y) / int(label_size_y)
|
||||||
|
if _display_count != display_count and display_count != 0 and display_count > _display_count:
|
||||||
|
_display_count = (display_count as int)
|
||||||
|
|
||||||
|
# Since the labels are going from the bottom to the top, the label
|
||||||
|
# coordinates are offset from the bottom by label size.
|
||||||
|
# The first label already exists, so it's handlded by itself
|
||||||
|
_history_labels[0].position.y = size.y - label_size_y
|
||||||
|
_history_labels[0].set_size(Vector2(label_size_x, label_size_y))
|
||||||
|
# The remaining labels may or may not exist already, create them
|
||||||
|
for i in range(0, _display_count - _history_labels.size()):
|
||||||
|
var new_item := Label.new()
|
||||||
|
new_item.size_flags_vertical = Control.SIZE_SHRINK_END
|
||||||
|
new_item.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
|
||||||
|
# The +1 is due to the labels going upwards from the bottom, otherwise
|
||||||
|
# their position will be 1 row lower than they should be
|
||||||
|
var position_offset: int = _history_labels.size() + 1
|
||||||
|
new_item.position.y = size.y - (position_offset * label_size_y)
|
||||||
|
new_item.set_size(Vector2(label_size_x, label_size_y))
|
||||||
|
_history_labels.append(new_item)
|
||||||
|
add_child(new_item)
|
||||||
|
|
||||||
|
# Update the scroll bar to be positioned correctly
|
||||||
|
_scroll_bar.size.x = _scroll_bar_width
|
||||||
|
_scroll_bar.size.y = size.y
|
||||||
|
_scroll_bar.position.x = label_size_x
|
||||||
|
|
||||||
|
_reset_history_to_beginning()
|
||||||
|
|
||||||
|
|
||||||
|
func _update_scroll_bar() -> void:
|
||||||
|
if _display_count > 0:
|
||||||
|
var max_size: int = _filter_results.size()
|
||||||
|
_scroll_bar.max_value = max_size
|
||||||
|
_scroll_bar.page = _display_count
|
||||||
|
_scroll_bar.set_value_no_signal((max_size - _display_count) - _offset)
|
||||||
|
|
||||||
|
|
||||||
|
## Reset indexes to 0, scroll to the bottom of the history list, and update visuals
|
||||||
|
func _reset_history_to_beginning() -> void:
|
||||||
|
_reset_indexes()
|
||||||
|
_update_highlight()
|
||||||
|
_update_scroll_list()
|
||||||
|
|
||||||
|
|
||||||
|
## Search for the current command and filter the results
|
||||||
|
func _search_and_filter() -> void:
|
||||||
|
_filter_results = _history.fuzzy_match(_command)
|
||||||
|
|
||||||
|
_reset_history_to_beginning()
|
1
addons/limbo_console/history_gui.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cpl5jb0mwxanh
|
1083
addons/limbo_console/limbo_console.gd
Normal file
1
addons/limbo_console/limbo_console.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dyxornv8vwibg
|
7
addons/limbo_console/plugin.cfg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[plugin]
|
||||||
|
|
||||||
|
name="LimboConsole"
|
||||||
|
description="Yet another in-game console with a simple command interpreter."
|
||||||
|
author="Serhii Snitsaruk"
|
||||||
|
version="0.4.1"
|
||||||
|
script="plugin.gd"
|
57
addons/limbo_console/plugin.gd
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
@tool
|
||||||
|
extends EditorPlugin
|
||||||
|
|
||||||
|
const ConsoleOptions := preload("res://addons/limbo_console/console_options.gd")
|
||||||
|
const ConfigMapper := preload("res://addons/limbo_console/config_mapper.gd")
|
||||||
|
|
||||||
|
func _enter_tree() -> void:
|
||||||
|
add_autoload_singleton("LimboConsole", "res://addons/limbo_console/limbo_console.gd")
|
||||||
|
|
||||||
|
# Sync config file (create if not exists)
|
||||||
|
var console_options := ConsoleOptions.new()
|
||||||
|
var do_project_setting_save: bool = false
|
||||||
|
ConfigMapper.load_from_config(console_options)
|
||||||
|
ConfigMapper.save_to_config(console_options)
|
||||||
|
|
||||||
|
if not ProjectSettings.has_setting("input/limbo_console_toggle"):
|
||||||
|
print("LimboConsole: Adding \"limbo_console_toggle\" input action to project settings...")
|
||||||
|
|
||||||
|
var key_event := InputEventKey.new()
|
||||||
|
key_event.keycode = KEY_QUOTELEFT
|
||||||
|
|
||||||
|
ProjectSettings.set_setting("input/limbo_console_toggle", {
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": [key_event],
|
||||||
|
})
|
||||||
|
do_project_setting_save = true
|
||||||
|
|
||||||
|
if not ProjectSettings.has_setting("input/limbo_auto_complete_reverse"):
|
||||||
|
print("LimboConsole: Adding \"limbo_auto_complete_reverse\" input action to project settings...")
|
||||||
|
var key_event = InputEventKey.new()
|
||||||
|
key_event.keycode = KEY_TAB
|
||||||
|
key_event.shift_pressed = true
|
||||||
|
|
||||||
|
ProjectSettings.set_setting("input/limbo_auto_complete_reverse", {
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": [key_event],
|
||||||
|
})
|
||||||
|
do_project_setting_save = true
|
||||||
|
|
||||||
|
if not ProjectSettings.has_setting("input/limbo_console_search_history"):
|
||||||
|
print("LimboConsole: Adding \"limbo_console_search_history\" input action to project settings...")
|
||||||
|
var key_event = InputEventKey.new()
|
||||||
|
key_event.keycode = KEY_R
|
||||||
|
key_event.ctrl_pressed = true
|
||||||
|
|
||||||
|
ProjectSettings.set_setting("input/limbo_console_search_history", {
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": [key_event],
|
||||||
|
})
|
||||||
|
do_project_setting_save = true
|
||||||
|
|
||||||
|
if do_project_setting_save:
|
||||||
|
ProjectSettings.save()
|
||||||
|
|
||||||
|
|
||||||
|
func _exit_tree() -> void:
|
||||||
|
remove_autoload_singleton("LimboConsole")
|
1
addons/limbo_console/plugin.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://b4t0bfyjdn8i0
|
176
addons/limbo_console/res/default_theme.tres
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
[gd_resource type="Theme" load_steps=23 format=3 uid="uid://dq4nntds66bix"]
|
||||||
|
|
||||||
|
[ext_resource type="FontFile" uid="uid://dbbw833hg2v7o" path="res://addons/limbo_console/res/fonts/monaspace_argon_bold.otf" id="1_cry2i"]
|
||||||
|
[ext_resource type="FontFile" uid="uid://dmeyp84repfbw" path="res://addons/limbo_console/res/fonts/monaspace_argon_bold_italic.otf" id="2_h3f73"]
|
||||||
|
[ext_resource type="FontFile" uid="uid://dhm45nttm5i3s" path="res://addons/limbo_console/res/fonts/monaspace_argon_italic.otf" id="3_2qq11"]
|
||||||
|
[ext_resource type="FontFile" uid="uid://ds7dvyquauqub" path="res://addons/limbo_console/res/fonts/monaspace_argon_medium.otf" id="4_06w3f"]
|
||||||
|
[ext_resource type="FontFile" uid="uid://d4js20k8kslqt" path="res://addons/limbo_console/res/fonts/monaspace_argon_regular.otf" id="5_p4ppy"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_yia2g"]
|
||||||
|
content_margin_left = 6.0
|
||||||
|
content_margin_top = 4.0
|
||||||
|
content_margin_right = 6.0
|
||||||
|
content_margin_bottom = 4.0
|
||||||
|
bg_color = Color(0.147, 0.168, 0.203, 1)
|
||||||
|
corner_radius_top_left = 3
|
||||||
|
corner_radius_top_right = 3
|
||||||
|
corner_radius_bottom_right = 3
|
||||||
|
corner_radius_bottom_left = 3
|
||||||
|
corner_detail = 3
|
||||||
|
anti_aliasing = false
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_bt363"]
|
||||||
|
content_margin_left = 2.0
|
||||||
|
content_margin_top = 4.0
|
||||||
|
content_margin_right = 2.0
|
||||||
|
content_margin_bottom = 4.0
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_txn37"]
|
||||||
|
content_margin_left = 2.0
|
||||||
|
content_margin_top = 4.0
|
||||||
|
content_margin_right = 2.0
|
||||||
|
content_margin_bottom = 4.0
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ypc4n"]
|
||||||
|
content_margin_left = 2.0
|
||||||
|
content_margin_top = 4.0
|
||||||
|
content_margin_right = 2.0
|
||||||
|
content_margin_bottom = 4.0
|
||||||
|
bg_color = Color(0, 0, 0, 0)
|
||||||
|
border_width_top = 1
|
||||||
|
border_color = Color(0.211765, 0.239216, 0.290196, 1)
|
||||||
|
|
||||||
|
[sub_resource type="Image" id="Image_jqg6r"]
|
||||||
|
data = {
|
||||||
|
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 3, 255, 255, 255, 41, 255, 255, 255, 67, 255, 255, 255, 67, 255, 255, 255, 40, 255, 255, 255, 3, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 41, 255, 255, 255, 74, 255, 255, 255, 74, 255, 255, 255, 74, 255, 255, 255, 74, 255, 255, 255, 40, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 67, 255, 255, 255, 74, 255, 255, 255, 74, 255, 255, 255, 74, 255, 255, 255, 74, 255, 255, 255, 67, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 67, 255, 255, 255, 74, 255, 255, 255, 74, 255, 255, 255, 74, 255, 255, 255, 74, 255, 255, 255, 67, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 40, 255, 255, 255, 74, 255, 255, 255, 74, 255, 255, 255, 74, 255, 255, 255, 74, 255, 255, 255, 40, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 3, 255, 255, 255, 40, 255, 255, 255, 67, 255, 255, 255, 67, 255, 255, 255, 40, 255, 255, 255, 3, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||||
|
"format": "RGBA8",
|
||||||
|
"height": 12,
|
||||||
|
"mipmaps": false,
|
||||||
|
"width": 12
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="ImageTexture" id="ImageTexture_utg8u"]
|
||||||
|
image = SubResource("Image_jqg6r")
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_xismo"]
|
||||||
|
content_margin_left = 7.0
|
||||||
|
content_margin_top = 7.0
|
||||||
|
content_margin_right = 7.0
|
||||||
|
content_margin_bottom = 7.0
|
||||||
|
texture = SubResource("ImageTexture_utg8u")
|
||||||
|
texture_margin_left = 6.0
|
||||||
|
texture_margin_top = 6.0
|
||||||
|
texture_margin_right = 6.0
|
||||||
|
texture_margin_bottom = 6.0
|
||||||
|
|
||||||
|
[sub_resource type="Image" id="Image_rt1dl"]
|
||||||
|
data = {
|
||||||
|
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 6, 248, 248, 248, 102, 249, 249, 249, 168, 249, 249, 249, 168, 248, 248, 248, 101, 213, 213, 213, 6, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 248, 248, 248, 102, 249, 249, 249, 186, 249, 249, 249, 186, 249, 249, 249, 186, 249, 249, 249, 186, 248, 248, 248, 101, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 249, 249, 249, 168, 249, 249, 249, 186, 249, 249, 249, 186, 249, 249, 249, 186, 249, 249, 249, 186, 249, 249, 249, 168, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 249, 249, 249, 168, 249, 249, 249, 186, 249, 249, 249, 186, 249, 249, 249, 186, 249, 249, 249, 186, 248, 248, 248, 168, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 248, 248, 248, 101, 249, 249, 249, 186, 249, 249, 249, 186, 249, 249, 249, 186, 249, 249, 249, 186, 250, 250, 250, 99, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 213, 213, 213, 6, 248, 248, 248, 101, 249, 249, 249, 168, 248, 248, 248, 168, 250, 250, 250, 99, 213, 213, 213, 6, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||||
|
"format": "RGBA8",
|
||||||
|
"height": 12,
|
||||||
|
"mipmaps": false,
|
||||||
|
"width": 12
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="ImageTexture" id="ImageTexture_foc76"]
|
||||||
|
image = SubResource("Image_rt1dl")
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_oajxf"]
|
||||||
|
content_margin_left = 6.0
|
||||||
|
content_margin_top = 6.0
|
||||||
|
content_margin_right = 6.0
|
||||||
|
content_margin_bottom = 6.0
|
||||||
|
texture = SubResource("ImageTexture_foc76")
|
||||||
|
texture_margin_left = 5.0
|
||||||
|
texture_margin_top = 5.0
|
||||||
|
texture_margin_right = 5.0
|
||||||
|
texture_margin_bottom = 5.0
|
||||||
|
|
||||||
|
[sub_resource type="Image" id="Image_2c5qw"]
|
||||||
|
data = {
|
||||||
|
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 213, 213, 213, 6, 180, 180, 180, 102, 181, 181, 181, 168, 181, 181, 181, 168, 179, 179, 179, 101, 170, 170, 170, 6, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 180, 180, 180, 102, 180, 180, 180, 186, 180, 180, 180, 186, 180, 180, 180, 186, 180, 180, 180, 186, 179, 179, 179, 101, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 181, 181, 181, 168, 180, 180, 180, 186, 180, 180, 180, 186, 180, 180, 180, 186, 180, 180, 180, 186, 181, 181, 181, 168, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 181, 181, 181, 168, 180, 180, 180, 186, 180, 180, 180, 186, 180, 180, 180, 186, 180, 180, 180, 186, 179, 179, 179, 168, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 179, 179, 179, 101, 180, 180, 180, 186, 180, 180, 180, 186, 180, 180, 180, 186, 180, 180, 180, 186, 181, 181, 181, 99, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 170, 170, 170, 6, 179, 179, 179, 101, 181, 181, 181, 168, 179, 179, 179, 168, 181, 181, 181, 99, 170, 170, 170, 6, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||||
|
"format": "RGBA8",
|
||||||
|
"height": 12,
|
||||||
|
"mipmaps": false,
|
||||||
|
"width": 12
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="ImageTexture" id="ImageTexture_alomt"]
|
||||||
|
image = SubResource("Image_2c5qw")
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_ev3il"]
|
||||||
|
content_margin_left = 7.0
|
||||||
|
content_margin_top = 7.0
|
||||||
|
content_margin_right = 7.0
|
||||||
|
content_margin_bottom = 7.0
|
||||||
|
texture = SubResource("ImageTexture_alomt")
|
||||||
|
texture_margin_left = 6.0
|
||||||
|
texture_margin_top = 6.0
|
||||||
|
texture_margin_right = 6.0
|
||||||
|
texture_margin_bottom = 6.0
|
||||||
|
|
||||||
|
[sub_resource type="Image" id="Image_c2fg1"]
|
||||||
|
data = {
|
||||||
|
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 7, 255, 255, 255, 19, 255, 255, 255, 19, 255, 255, 255, 7, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 19, 255, 255, 255, 21, 255, 255, 255, 21, 255, 255, 255, 19, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 19, 255, 255, 255, 21, 255, 255, 255, 21, 255, 255, 255, 19, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 7, 255, 255, 255, 19, 255, 255, 255, 19, 255, 255, 255, 6, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||||
|
"format": "RGBA8",
|
||||||
|
"height": 12,
|
||||||
|
"mipmaps": false,
|
||||||
|
"width": 12
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="ImageTexture" id="ImageTexture_lun2q"]
|
||||||
|
image = SubResource("Image_c2fg1")
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_lk88d"]
|
||||||
|
content_margin_left = 6.0
|
||||||
|
content_margin_top = 0.0
|
||||||
|
content_margin_right = 6.0
|
||||||
|
content_margin_bottom = 0.0
|
||||||
|
texture = SubResource("ImageTexture_lun2q")
|
||||||
|
texture_margin_left = 5.0
|
||||||
|
texture_margin_top = 5.0
|
||||||
|
texture_margin_right = 5.0
|
||||||
|
texture_margin_bottom = 5.0
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_4ui4a"]
|
||||||
|
content_margin_left = 6.0
|
||||||
|
content_margin_top = 6.0
|
||||||
|
content_margin_right = 6.0
|
||||||
|
content_margin_bottom = 6.0
|
||||||
|
texture = SubResource("ImageTexture_lun2q")
|
||||||
|
texture_margin_left = 5.0
|
||||||
|
texture_margin_top = 5.0
|
||||||
|
texture_margin_right = 5.0
|
||||||
|
texture_margin_bottom = 5.0
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
default_font = ExtResource("5_p4ppy")
|
||||||
|
default_font_size = 18
|
||||||
|
ConsoleColors/colors/entry_command_found_color = Color(0.729412, 0.901961, 0.494118, 1)
|
||||||
|
ConsoleColors/colors/entry_command_not_found_color = Color(1, 0.2, 0.2, 1)
|
||||||
|
ConsoleColors/colors/entry_hint_color = Color(0.439216, 0.478431, 0.54902, 1)
|
||||||
|
ConsoleColors/colors/entry_subcommand_color = Color(0.584314, 0.901961, 0.796078, 1)
|
||||||
|
ConsoleColors/colors/entry_text_color = Color(0.796078, 0.8, 0.776471, 1)
|
||||||
|
ConsoleColors/colors/history_highlight_color = Color(0.317647, 0.364706, 0.439216, 1)
|
||||||
|
ConsoleColors/colors/output_command_color = Color(0.729412, 0.901961, 0.494118, 1)
|
||||||
|
ConsoleColors/colors/output_command_mention_color = Color(0.584314, 0.901961, 0.796078, 1)
|
||||||
|
ConsoleColors/colors/output_debug_color = Color(0.439216, 0.478431, 0.54902, 1)
|
||||||
|
ConsoleColors/colors/output_error_color = Color(1, 0.2, 0.2, 1)
|
||||||
|
ConsoleColors/colors/output_text_color = Color(0.796078, 0.8, 0.776471, 1)
|
||||||
|
ConsoleColors/colors/output_warning_color = Color(1, 0.654902, 0.34902, 1)
|
||||||
|
Panel/styles/panel = SubResource("StyleBoxFlat_yia2g")
|
||||||
|
PanelContainer/styles/panel = SubResource("StyleBoxFlat_yia2g")
|
||||||
|
RichTextLabel/fonts/bold_font = ExtResource("1_cry2i")
|
||||||
|
RichTextLabel/fonts/bold_italics_font = ExtResource("2_h3f73")
|
||||||
|
RichTextLabel/fonts/italics_font = ExtResource("3_2qq11")
|
||||||
|
RichTextLabel/fonts/mono_font = ExtResource("4_06w3f")
|
||||||
|
RichTextLabel/fonts/normal_font = ExtResource("5_p4ppy")
|
||||||
|
RichTextLabel/styles/focus = SubResource("StyleBoxEmpty_bt363")
|
||||||
|
RichTextLabel/styles/normal = SubResource("StyleBoxEmpty_txn37")
|
||||||
|
TextEdit/styles/focus = SubResource("StyleBoxFlat_ypc4n")
|
||||||
|
TextEdit/styles/normal = SubResource("StyleBoxFlat_ypc4n")
|
||||||
|
VScrollBar/styles/grabber = SubResource("StyleBoxTexture_xismo")
|
||||||
|
VScrollBar/styles/grabber_highlight = SubResource("StyleBoxTexture_oajxf")
|
||||||
|
VScrollBar/styles/grabber_pressed = SubResource("StyleBoxTexture_ev3il")
|
||||||
|
VScrollBar/styles/scroll = SubResource("StyleBoxTexture_lk88d")
|
||||||
|
VScrollBar/styles/scroll_focus = SubResource("StyleBoxTexture_4ui4a")
|
93
addons/limbo_console/res/fonts/LICENSE.md
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
Copyright (c) 2023, GitHub https://github.com/githubnext/monaspace
|
||||||
|
with Reserved Font Name "Monaspace", including subfamilies: "Argon", "Neon", "Xenon", "Radon", and "Krypton"
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting — in part or in whole — any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
BIN
addons/limbo_console/res/fonts/monaspace_argon_bold.otf
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="font_data_dynamic"
|
||||||
|
type="FontFile"
|
||||||
|
uid="uid://dbbw833hg2v7o"
|
||||||
|
path="res://.godot/imported/monaspace_argon_bold.otf-5d916059e7810f56d30161557f70d71b.fontdata"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/limbo_console/res/fonts/monaspace_argon_bold.otf"
|
||||||
|
dest_files=["res://.godot/imported/monaspace_argon_bold.otf-5d916059e7810f56d30161557f70d71b.fontdata"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
Rendering=null
|
||||||
|
antialiasing=1
|
||||||
|
generate_mipmaps=false
|
||||||
|
disable_embedded_bitmaps=true
|
||||||
|
multichannel_signed_distance_field=false
|
||||||
|
msdf_pixel_range=8
|
||||||
|
msdf_size=48
|
||||||
|
allow_system_fallback=true
|
||||||
|
force_autohinter=false
|
||||||
|
hinting=1
|
||||||
|
subpixel_positioning=1
|
||||||
|
keep_rounding_remainders=true
|
||||||
|
oversampling=0.0
|
||||||
|
Fallbacks=null
|
||||||
|
fallbacks=[]
|
||||||
|
Compress=null
|
||||||
|
compress=true
|
||||||
|
preload=[]
|
||||||
|
language_support={}
|
||||||
|
script_support={}
|
||||||
|
opentype_features={}
|
BIN
addons/limbo_console/res/fonts/monaspace_argon_bold_italic.otf
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="font_data_dynamic"
|
||||||
|
type="FontFile"
|
||||||
|
uid="uid://dmeyp84repfbw"
|
||||||
|
path="res://.godot/imported/monaspace_argon_bold_italic.otf-cd05eebec36875096d59dc2e6dfb87db.fontdata"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/limbo_console/res/fonts/monaspace_argon_bold_italic.otf"
|
||||||
|
dest_files=["res://.godot/imported/monaspace_argon_bold_italic.otf-cd05eebec36875096d59dc2e6dfb87db.fontdata"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
Rendering=null
|
||||||
|
antialiasing=1
|
||||||
|
generate_mipmaps=false
|
||||||
|
disable_embedded_bitmaps=true
|
||||||
|
multichannel_signed_distance_field=false
|
||||||
|
msdf_pixel_range=8
|
||||||
|
msdf_size=48
|
||||||
|
allow_system_fallback=true
|
||||||
|
force_autohinter=false
|
||||||
|
hinting=1
|
||||||
|
subpixel_positioning=1
|
||||||
|
keep_rounding_remainders=true
|
||||||
|
oversampling=0.0
|
||||||
|
Fallbacks=null
|
||||||
|
fallbacks=[]
|
||||||
|
Compress=null
|
||||||
|
compress=true
|
||||||
|
preload=[]
|
||||||
|
language_support={}
|
||||||
|
script_support={}
|
||||||
|
opentype_features={}
|
BIN
addons/limbo_console/res/fonts/monaspace_argon_italic.otf
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="font_data_dynamic"
|
||||||
|
type="FontFile"
|
||||||
|
uid="uid://dhm45nttm5i3s"
|
||||||
|
path="res://.godot/imported/monaspace_argon_italic.otf-69d64783adde526699a99a191cd14ed6.fontdata"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/limbo_console/res/fonts/monaspace_argon_italic.otf"
|
||||||
|
dest_files=["res://.godot/imported/monaspace_argon_italic.otf-69d64783adde526699a99a191cd14ed6.fontdata"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
Rendering=null
|
||||||
|
antialiasing=1
|
||||||
|
generate_mipmaps=false
|
||||||
|
disable_embedded_bitmaps=true
|
||||||
|
multichannel_signed_distance_field=false
|
||||||
|
msdf_pixel_range=8
|
||||||
|
msdf_size=48
|
||||||
|
allow_system_fallback=true
|
||||||
|
force_autohinter=false
|
||||||
|
hinting=1
|
||||||
|
subpixel_positioning=1
|
||||||
|
keep_rounding_remainders=true
|
||||||
|
oversampling=0.0
|
||||||
|
Fallbacks=null
|
||||||
|
fallbacks=[]
|
||||||
|
Compress=null
|
||||||
|
compress=true
|
||||||
|
preload=[]
|
||||||
|
language_support={}
|
||||||
|
script_support={}
|
||||||
|
opentype_features={}
|
BIN
addons/limbo_console/res/fonts/monaspace_argon_medium.otf
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="font_data_dynamic"
|
||||||
|
type="FontFile"
|
||||||
|
uid="uid://ds7dvyquauqub"
|
||||||
|
path="res://.godot/imported/monaspace_argon_medium.otf-2c090420a59a2bfcd1b6b28a7c3469ad.fontdata"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/limbo_console/res/fonts/monaspace_argon_medium.otf"
|
||||||
|
dest_files=["res://.godot/imported/monaspace_argon_medium.otf-2c090420a59a2bfcd1b6b28a7c3469ad.fontdata"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
Rendering=null
|
||||||
|
antialiasing=1
|
||||||
|
generate_mipmaps=false
|
||||||
|
disable_embedded_bitmaps=true
|
||||||
|
multichannel_signed_distance_field=false
|
||||||
|
msdf_pixel_range=8
|
||||||
|
msdf_size=48
|
||||||
|
allow_system_fallback=true
|
||||||
|
force_autohinter=false
|
||||||
|
hinting=1
|
||||||
|
subpixel_positioning=1
|
||||||
|
keep_rounding_remainders=true
|
||||||
|
oversampling=0.0
|
||||||
|
Fallbacks=null
|
||||||
|
fallbacks=[]
|
||||||
|
Compress=null
|
||||||
|
compress=true
|
||||||
|
preload=[]
|
||||||
|
language_support={}
|
||||||
|
script_support={}
|
||||||
|
opentype_features={}
|
BIN
addons/limbo_console/res/fonts/monaspace_argon_regular.otf
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="font_data_dynamic"
|
||||||
|
type="FontFile"
|
||||||
|
uid="uid://d4js20k8kslqt"
|
||||||
|
path="res://.godot/imported/monaspace_argon_regular.otf-7622e0becc7f9143f21c9951dd015f30.fontdata"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/limbo_console/res/fonts/monaspace_argon_regular.otf"
|
||||||
|
dest_files=["res://.godot/imported/monaspace_argon_regular.otf-7622e0becc7f9143f21c9951dd015f30.fontdata"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
Rendering=null
|
||||||
|
antialiasing=1
|
||||||
|
generate_mipmaps=false
|
||||||
|
disable_embedded_bitmaps=true
|
||||||
|
multichannel_signed_distance_field=false
|
||||||
|
msdf_pixel_range=8
|
||||||
|
msdf_size=48
|
||||||
|
allow_system_fallback=true
|
||||||
|
force_autohinter=false
|
||||||
|
hinting=1
|
||||||
|
subpixel_positioning=1
|
||||||
|
keep_rounding_remainders=true
|
||||||
|
oversampling=0.0
|
||||||
|
Fallbacks=null
|
||||||
|
fallbacks=[]
|
||||||
|
Compress=null
|
||||||
|
compress=true
|
||||||
|
preload=[]
|
||||||
|
language_support={}
|
||||||
|
script_support={}
|
||||||
|
opentype_features={}
|
119
addons/limbo_console/util.gd
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
extends Object
|
||||||
|
## Utility functions
|
||||||
|
|
||||||
|
|
||||||
|
static func bbcode_escape(p_text: String) -> String:
|
||||||
|
return p_text \
|
||||||
|
.replace("[", "~LB~") \
|
||||||
|
.replace("]", "~RB~") \
|
||||||
|
.replace("~LB~", "[lb]") \
|
||||||
|
.replace("~RB~", "[rb]")
|
||||||
|
|
||||||
|
|
||||||
|
static func bbcode_strip(p_text: String) -> String:
|
||||||
|
var stripped := ""
|
||||||
|
var in_brackets: bool = false
|
||||||
|
for c: String in p_text:
|
||||||
|
if c == '[':
|
||||||
|
in_brackets = true
|
||||||
|
elif c == ']':
|
||||||
|
in_brackets = false
|
||||||
|
elif not in_brackets:
|
||||||
|
stripped += c
|
||||||
|
return stripped
|
||||||
|
|
||||||
|
|
||||||
|
static func get_method_info(p_callable: Callable) -> Dictionary:
|
||||||
|
var method_info: Dictionary
|
||||||
|
var method_list: Array[Dictionary]
|
||||||
|
if p_callable.get_object() is GDScript:
|
||||||
|
method_list = p_callable.get_object().get_script_method_list()
|
||||||
|
else:
|
||||||
|
method_list = p_callable.get_object().get_method_list()
|
||||||
|
for m in method_list:
|
||||||
|
if m.name == p_callable.get_method():
|
||||||
|
method_info = m
|
||||||
|
break
|
||||||
|
if !method_info and p_callable.is_custom():
|
||||||
|
var args: Array
|
||||||
|
var default_args: Array
|
||||||
|
for i in p_callable.get_argument_count():
|
||||||
|
var argument: Dictionary
|
||||||
|
argument["name"] = "arg%d" % i
|
||||||
|
argument["type"] = TYPE_NIL
|
||||||
|
args.push_back(argument)
|
||||||
|
method_info["name"] = "<anonymous lambda>"
|
||||||
|
method_info["args"] = args
|
||||||
|
method_info["default_args"] = default_args
|
||||||
|
return method_info
|
||||||
|
|
||||||
|
|
||||||
|
## Finds the most similar string in an array.
|
||||||
|
static func fuzzy_match_string(p_string: String, p_max_edit_distance: int, p_array) -> String:
|
||||||
|
if typeof(p_array) < TYPE_ARRAY:
|
||||||
|
push_error("LimboConsole: Internal error: p_array is not an array")
|
||||||
|
return ""
|
||||||
|
if p_array.size() == 0:
|
||||||
|
return ""
|
||||||
|
var best_distance: int = 9223372036854775807
|
||||||
|
var best_match: String = ""
|
||||||
|
for i in p_array.size():
|
||||||
|
var elem := str(p_array[i])
|
||||||
|
var dist: float = _calculate_osa_distance(p_string, elem)
|
||||||
|
if dist < best_distance:
|
||||||
|
best_distance = dist
|
||||||
|
best_match = elem
|
||||||
|
return best_match if best_distance <= p_max_edit_distance else ""
|
||||||
|
|
||||||
|
|
||||||
|
## Calculates optimal string alignment distance [br]
|
||||||
|
## See: https://en.wikipedia.org/wiki/Levenshtein_distance
|
||||||
|
static func _calculate_osa_distance(s1: String, s2: String) -> int:
|
||||||
|
var s1_len: int = s1.length()
|
||||||
|
var s2_len: int = s2.length()
|
||||||
|
|
||||||
|
# Iterative approach with 3 matrix rows.
|
||||||
|
# Most of the work is done on row1 and row2 - row0 is only needed to calculate transposition cost.
|
||||||
|
var row0: PackedInt32Array # previous-previous
|
||||||
|
var row1: PackedInt32Array # previous
|
||||||
|
var row2: PackedInt32Array # current aka the one we need to calculate
|
||||||
|
row0.resize(s2_len + 1)
|
||||||
|
row1.resize(s2_len + 1)
|
||||||
|
row2.resize(s2_len + 1)
|
||||||
|
|
||||||
|
# edit distance is the number of characters to insert to get from empty string to s2
|
||||||
|
for i in range(s2_len + 1):
|
||||||
|
row1[i] = i
|
||||||
|
|
||||||
|
for i in range(s1_len):
|
||||||
|
# edit distance is the number of characters to delete from s1 to match empty s2
|
||||||
|
row2[0] = i + 1
|
||||||
|
|
||||||
|
for j in range(s2_len):
|
||||||
|
var deletion_cost: int = row1[j + 1] + 1
|
||||||
|
var insertion_cost: int = row2[j] + 1
|
||||||
|
var substitution_cost: int = row1[j] if s1[i] == s2[j] else row1[j] + 1
|
||||||
|
|
||||||
|
row2[j + 1] = min(deletion_cost, insertion_cost, substitution_cost)
|
||||||
|
|
||||||
|
if i > 1 and j > 1 and s1[i - 1] == s2[j] and s1[i - 1] == s2[j]:
|
||||||
|
var transposition_cost: int = row0[j - 1] + 1
|
||||||
|
row2[j + 1] = mini(transposition_cost, row2[j + 1])
|
||||||
|
|
||||||
|
# Swap rows.
|
||||||
|
var tmp: PackedInt32Array = row0
|
||||||
|
row0 = row1
|
||||||
|
row1 = row2
|
||||||
|
row2 = tmp
|
||||||
|
return row1[s2_len]
|
||||||
|
|
||||||
|
|
||||||
|
## Returns true, if a string is constructed of one or more space-separated valid
|
||||||
|
## command identifiers ("command" or "command sub1 sub2").
|
||||||
|
## A valid command identifier may contain only letters, digits, and underscores (_),
|
||||||
|
## and the first character may not be a digit.
|
||||||
|
static func is_valid_command_sequence(p_string: String) -> bool:
|
||||||
|
for part in p_string.split(' '):
|
||||||
|
if not part.is_valid_ascii_identifier():
|
||||||
|
return false
|
||||||
|
return true
|
1
addons/limbo_console/util.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cw6s1es6yjip5
|
@@ -21,6 +21,7 @@ config/icon="uid://dihjvrhshhklu"
|
|||||||
[autoload]
|
[autoload]
|
||||||
|
|
||||||
GameBus="*res://Scripts/Singletons/GameBus.cs"
|
GameBus="*res://Scripts/Singletons/GameBus.cs"
|
||||||
|
LimboConsole="*res://addons/limbo_console/limbo_console.gd"
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
|
|
||||||
@@ -30,10 +31,32 @@ window/stretch/mode="canvas_items"
|
|||||||
|
|
||||||
project/assembly_name="ParasiticGod"
|
project/assembly_name="ParasiticGod"
|
||||||
|
|
||||||
|
[editor_plugins]
|
||||||
|
|
||||||
|
enabled=PackedStringArray("res://addons/limbo_console/plugin.cfg")
|
||||||
|
|
||||||
[gui]
|
[gui]
|
||||||
|
|
||||||
theme/custom_font="uid://b8tf6dbm2j0ju"
|
theme/custom_font="uid://b8tf6dbm2j0ju"
|
||||||
|
|
||||||
|
[input]
|
||||||
|
|
||||||
|
limbo_console_toggle={
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":96,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
limbo_auto_complete_reverse={
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194306,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
limbo_console_search_history={
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":82,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
[rendering]
|
[rendering]
|
||||||
|
|
||||||
renderer/rendering_method="gl_compatibility"
|
renderer/rendering_method="gl_compatibility"
|
||||||
|