Compare commits
2 Commits
9da3b02a46
...
07995821f5
Author | SHA1 | Date | |
---|---|---|---|
07995821f5 | |||
846c3a6d83 |
BIN
Fonts/Playful Boxes.otf
Normal file
@@ -2,13 +2,13 @@
|
||||
|
||||
importer="font_data_dynamic"
|
||||
type="FontFile"
|
||||
uid="uid://cxiv86ha7xb6"
|
||||
path="res://.godot/imported/comicz.ttf-66a741130a0c8d7e12d809da56e73bbf.fontdata"
|
||||
uid="uid://wofoiaejxgsp"
|
||||
path="res://.godot/imported/Playful Boxes.otf-113f6b887ec1f2b8d73de65734580dbc.fontdata"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Fonts/comicz.ttf"
|
||||
dest_files=["res://.godot/imported/comicz.ttf-66a741130a0c8d7e12d809da56e73bbf.fontdata"]
|
||||
source_file="res://Fonts/Playful Boxes.otf"
|
||||
dest_files=["res://.godot/imported/Playful Boxes.otf-113f6b887ec1f2b8d73de65734580dbc.fontdata"]
|
||||
|
||||
[params]
|
||||
|
BIN
Fonts/comici.ttf
BIN
Fonts/comicz.ttf
@@ -1,35 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="font_data_dynamic"
|
||||
type="FontFile"
|
||||
uid="uid://cafeq0txk8o1p"
|
||||
path="res://.godot/imported/design.graffiti.comicsansmsgras.ttf-ab7fcd3a4b4dcda31884c957a19862a4.fontdata"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Fonts/design.graffiti.comicsansmsgras.ttf"
|
||||
dest_files=["res://.godot/imported/design.graffiti.comicsansmsgras.ttf-ab7fcd3a4b4dcda31884c957a19862a4.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=4
|
||||
keep_rounding_remainders=true
|
||||
oversampling=0.0
|
||||
Fallbacks=null
|
||||
fallbacks=[]
|
||||
Compress=null
|
||||
compress=true
|
||||
preload=[]
|
||||
language_support={}
|
||||
script_support={}
|
||||
opentype_features={}
|
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",
|
||||
"threshold": 150,
|
||||
"imagePath": "res://Mods/Tiers/Huts/hut_tier_1.png",
|
||||
"imagePath": "res://Sprites/Hut.png",
|
||||
"scale": {"x": 0.05, "y": 0.05}
|
||||
},
|
||||
{
|
||||
"tierEnum": "Tier2",
|
||||
"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}
|
||||
}
|
||||
]
|
||||
|
@@ -5,6 +5,7 @@
|
||||
<RootNamespace>parasiticgod</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="LimboConsole.Sharp" Version="0.0.1-beta-008" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4-beta1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
67
Scenes/main_menu.tscn
Normal file
@@ -0,0 +1,67 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://cmhvni5njpmee"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cbdokimy0qarg" path="res://Scripts/MainMenu.cs" id="1_1ehe0"]
|
||||
[ext_resource type="FontFile" uid="uid://wofoiaejxgsp" path="res://Fonts/Playful Boxes.otf" id="1_28flt"]
|
||||
[ext_resource type="Texture2D" uid="uid://d2wi2cs20q2b6" path="res://Parasitic_God.png" id="1_48xlc"]
|
||||
[ext_resource type="PackedScene" uid="uid://bfil8sd154327" path="res://Scenes/Main/Main.tscn" id="2_ce3w2"]
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_48xlc"]
|
||||
font = ExtResource("1_28flt")
|
||||
font_size = 72
|
||||
outline_size = 8
|
||||
outline_color = Color(0.48, 0.408, 0, 1)
|
||||
|
||||
[node name="MainMenu" type="CanvasLayer" node_paths=PackedStringArray("_startButton", "_quitButton")]
|
||||
script = ExtResource("1_1ehe0")
|
||||
_gameScene = ExtResource("2_ce3w2")
|
||||
_startButton = NodePath("CenterContainer/VBoxContainer/Play")
|
||||
_quitButton = NodePath("CenterContainer/VBoxContainer/Exit")
|
||||
|
||||
[node name="Control" type="Control" parent="."]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="Control"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 4
|
||||
texture = ExtResource("1_48xlc")
|
||||
expand_mode = 2
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="."]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Title" type="Label" parent="CenterContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 0
|
||||
size_flags_stretch_ratio = 0.0
|
||||
text = "PARASITIC GOD"
|
||||
label_settings = SubResource("LabelSettings_48xlc")
|
||||
|
||||
[node name="Play" type="Button" parent="CenterContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_stretch_ratio = 0.0
|
||||
theme_override_font_sizes/font_size = 30
|
||||
text = "Play"
|
||||
|
||||
[node name="Exit" type="Button" parent="CenterContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 30
|
||||
text = "Exit"
|
@@ -5,6 +5,6 @@ namespace ParasiticGod.Scripts;
|
||||
[GlobalClass]
|
||||
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; }
|
||||
}
|
52
Scripts/MainMenu.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Godot;
|
||||
|
||||
namespace ParasiticGod.Scripts;
|
||||
|
||||
public partial class MainMenu : Node
|
||||
{
|
||||
[Export] private PackedScene _gameScene;
|
||||
[Export] private Button _startButton;
|
||||
[Export] private Button _quitButton;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
if (_startButton != null)
|
||||
{
|
||||
_startButton.Pressed += OnStartButtonPressed;
|
||||
}
|
||||
|
||||
if (_quitButton != null)
|
||||
{
|
||||
_quitButton.Pressed += OnQuitButtonPressed;
|
||||
}
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
if (_startButton != null)
|
||||
{
|
||||
_startButton.Pressed -= OnStartButtonPressed;
|
||||
}
|
||||
|
||||
if (_quitButton != null)
|
||||
{
|
||||
_quitButton.Pressed -= OnQuitButtonPressed;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStartButtonPressed()
|
||||
{
|
||||
if (_gameScene == null)
|
||||
{
|
||||
GD.PrintErr("Game scene is not assigned in MainMenu.");
|
||||
return;
|
||||
}
|
||||
|
||||
GetTree().ChangeSceneToPacked(_gameScene);
|
||||
}
|
||||
|
||||
private void OnQuitButtonPressed()
|
||||
{
|
||||
GetTree().Quit();
|
||||
}
|
||||
}
|
1
Scripts/MainMenu.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cbdokimy0qarg
|
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Godot;
|
||||
using Limbo.Console.Sharp;
|
||||
using ParasiticGod.Scripts.Core;
|
||||
using ParasiticGod.Scripts.Core.Effects;
|
||||
|
||||
@@ -38,6 +39,11 @@ public partial class GameBus : Node
|
||||
Instance = null;
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
RegisterConsoleCommands();
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
_gameLogic.UpdateGameState(_gameState, delta);
|
||||
@@ -66,7 +72,7 @@ public partial class GameBus : Node
|
||||
if (AllMiracles.TryGetValue(id, out var def) && !_gameState.IsMiracleUnlocked(id))
|
||||
{
|
||||
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 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"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c88ltenh4ghit"
|
||||
path="res://.godot/imported/hut_tier_1.png-95c994b0565c43d199344569cb1a91ab.ctex"
|
||||
uid="uid://b7shwf6ob3qk5"
|
||||
path="res://.godot/imported/Skyscraper.png-dbe8acef55267af18c54925cfa634290.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Mods/Tiers/Huts/hut_tier_1.png"
|
||||
dest_files=["res://.godot/imported/hut_tier_1.png-95c994b0565c43d199344569cb1a91ab.ctex"]
|
||||
source_file="res://Sprites/Skyscraper.png"
|
||||
dest_files=["res://.godot/imported/Skyscraper.png-dbe8acef55267af18c54925cfa634290.ctex"]
|
||||
|
||||
[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"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://2wsb1jsmtifq"
|
||||
path="res://.godot/imported/follower_tier_1.png-d66a290ad46b1bb9ea69b54444ab5725.ctex"
|
||||
uid="uid://8omh4jhf3dwy"
|
||||
path="res://.godot/imported/house_tier_2.png-81b05409415d85bd1a2017792c00d1d0.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Mods/Tiers/Followers/follower_tier_1.png"
|
||||
dest_files=["res://.godot/imported/follower_tier_1.png-d66a290ad46b1bb9ea69b54444ab5725.ctex"]
|
||||
source_file="res://Sprites/house_tier_2.png"
|
||||
dest_files=["res://.godot/imported/house_tier_2.png-81b05409415d85bd1a2017792c00d1d0.ctex"]
|
||||
|
||||
[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"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cgoigfok3s0fc"
|
||||
path="res://.godot/imported/hut_tier_2.png-7e373825ef1bbf63f359ae57d709b394.ctex"
|
||||
uid="uid://bvmc6om3x08a"
|
||||
path="res://.godot/imported/hut_tier_2.png-3b01622803083ef93f5d9958304c62a4.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Mods/Tiers/Huts/hut_tier_2.png"
|
||||
dest_files=["res://.godot/imported/hut_tier_2.png-7e373825ef1bbf63f359ae57d709b394.ctex"]
|
||||
source_file="res://Sprites/hut_tier_2.png"
|
||||
dest_files=["res://.godot/imported/hut_tier_2.png-3b01622803083ef93f5d9958304c62a4.ctex"]
|
||||
|
||||
[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
@@ -2,13 +2,13 @@
|
||||
|
||||
importer="font_data_dynamic"
|
||||
type="FontFile"
|
||||
uid="uid://bns5c6hocyjua"
|
||||
path="res://.godot/imported/ComicSansMS3.ttf-96b759465ff31d11f80283a7c12539ca.fontdata"
|
||||
uid="uid://dbbw833hg2v7o"
|
||||
path="res://.godot/imported/monaspace_argon_bold.otf-5d916059e7810f56d30161557f70d71b.fontdata"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Fonts/ComicSansMS3.ttf"
|
||||
dest_files=["res://.godot/imported/ComicSansMS3.ttf-96b759465ff31d11f80283a7c12539ca.fontdata"]
|
||||
source_file="res://addons/limbo_console/res/fonts/monaspace_argon_bold.otf"
|
||||
dest_files=["res://.godot/imported/monaspace_argon_bold.otf-5d916059e7810f56d30161557f70d71b.fontdata"]
|
||||
|
||||
[params]
|
||||
|
||||
@@ -22,7 +22,7 @@ msdf_size=48
|
||||
allow_system_fallback=true
|
||||
force_autohinter=false
|
||||
hinting=1
|
||||
subpixel_positioning=4
|
||||
subpixel_positioning=1
|
||||
keep_rounding_remainders=true
|
||||
oversampling=0.0
|
||||
Fallbacks=null
|
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
@@ -2,13 +2,13 @@
|
||||
|
||||
importer="font_data_dynamic"
|
||||
type="FontFile"
|
||||
uid="uid://cqj7ianpr6jau"
|
||||
path="res://.godot/imported/design.graffiti.comicsansms.ttf-6b5fd3e9dc5336099f793666df4ffc33.fontdata"
|
||||
uid="uid://dhm45nttm5i3s"
|
||||
path="res://.godot/imported/monaspace_argon_italic.otf-69d64783adde526699a99a191cd14ed6.fontdata"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Fonts/design.graffiti.comicsansms.ttf"
|
||||
dest_files=["res://.godot/imported/design.graffiti.comicsansms.ttf-6b5fd3e9dc5336099f793666df4ffc33.fontdata"]
|
||||
source_file="res://addons/limbo_console/res/fonts/monaspace_argon_italic.otf"
|
||||
dest_files=["res://.godot/imported/monaspace_argon_italic.otf-69d64783adde526699a99a191cd14ed6.fontdata"]
|
||||
|
||||
[params]
|
||||
|
||||
@@ -22,7 +22,7 @@ msdf_size=48
|
||||
allow_system_fallback=true
|
||||
force_autohinter=false
|
||||
hinting=1
|
||||
subpixel_positioning=4
|
||||
subpixel_positioning=1
|
||||
keep_rounding_remainders=true
|
||||
oversampling=0.0
|
||||
Fallbacks=null
|
BIN
addons/limbo_console/res/fonts/monaspace_argon_medium.otf
Normal file
@@ -2,13 +2,13 @@
|
||||
|
||||
importer="font_data_dynamic"
|
||||
type="FontFile"
|
||||
uid="uid://bn4w716pu11lj"
|
||||
path="res://.godot/imported/comici.ttf-66bd62e0af47442ee6f5c29193ae1405.fontdata"
|
||||
uid="uid://ds7dvyquauqub"
|
||||
path="res://.godot/imported/monaspace_argon_medium.otf-2c090420a59a2bfcd1b6b28a7c3469ad.fontdata"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Fonts/comici.ttf"
|
||||
dest_files=["res://.godot/imported/comici.ttf-66bd62e0af47442ee6f5c29193ae1405.fontdata"]
|
||||
source_file="res://addons/limbo_console/res/fonts/monaspace_argon_medium.otf"
|
||||
dest_files=["res://.godot/imported/monaspace_argon_medium.otf-2c090420a59a2bfcd1b6b28a7c3469ad.fontdata"]
|
||||
|
||||
[params]
|
||||
|
||||
@@ -22,7 +22,7 @@ msdf_size=48
|
||||
allow_system_fallback=true
|
||||
force_autohinter=false
|
||||
hinting=1
|
||||
subpixel_positioning=4
|
||||
subpixel_positioning=1
|
||||
keep_rounding_remainders=true
|
||||
oversampling=0.0
|
||||
Fallbacks=null
|
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
|
@@ -11,7 +11,7 @@ config_version=5
|
||||
[application]
|
||||
|
||||
config/name="Parasitic God"
|
||||
run/main_scene="uid://bfil8sd154327"
|
||||
run/main_scene="uid://cmhvni5njpmee"
|
||||
config/features=PackedStringArray("4.4", "C#", "GL Compatibility")
|
||||
boot_splash/show_image=false
|
||||
boot_splash/fullsize=false
|
||||
@@ -21,6 +21,7 @@ config/icon="uid://dihjvrhshhklu"
|
||||
[autoload]
|
||||
|
||||
GameBus="*res://Scripts/Singletons/GameBus.cs"
|
||||
LimboConsole="*res://addons/limbo_console/limbo_console.gd"
|
||||
|
||||
[display]
|
||||
|
||||
@@ -30,10 +31,32 @@ window/stretch/mode="canvas_items"
|
||||
|
||||
project/assembly_name="ParasiticGod"
|
||||
|
||||
[editor_plugins]
|
||||
|
||||
enabled=PackedStringArray("res://addons/limbo_console/plugin.cfg")
|
||||
|
||||
[gui]
|
||||
|
||||
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]
|
||||
|
||||
renderer/rendering_method="gl_compatibility"
|
||||
|