Compare commits

..

No commits in common. "4df3d03d07d1cc8ae289068ee38ee1f9e439e312" and "2462174a0286325e338ca06142f3987e2aa4debb" have entirely different histories.

115 changed files with 810 additions and 5558 deletions

View File

@ -1,39 +0,0 @@
[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

View File

@ -1,15 +0,0 @@
# 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 dont include it.
- To avoid unnecessary whitespace changes, please enable this setting in Godot: `Editor Settings > Text Editor > Behavior > Files > Trim Trailing Whitespace on Save`

View File

@ -1,21 +0,0 @@
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.

View File

@ -1,128 +0,0 @@
<p align="left">
<img src=".github/logo.png" width=128 alt="LimboConsole logo">
</p>
---
![Limbo Console](.github/demonstration.gif)
![Static Badge](https://img.shields.io/badge/Godot-4.3-blue?style=flat)
[![GitHub License](https://img.shields.io/github/license/limbonaut/limbo_console)](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.
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](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).

View File

@ -1,208 +0,0 @@
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

View File

@ -1 +0,0 @@
uid://coot1au4b7np

View File

@ -1,169 +0,0 @@
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.")

View File

@ -1 +0,0 @@
uid://npao6ckwv7cm

View File

@ -1,130 +0,0 @@
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

View File

@ -1 +0,0 @@
uid://ddtreeatktyov

View File

@ -1,165 +0,0 @@
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

View File

@ -1 +0,0 @@
uid://dc55ouwu3ylf

View File

@ -1,77 +0,0 @@
@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

View File

@ -1 +0,0 @@
uid://dvokj0q23nb50

View File

@ -1,40 +0,0 @@
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

View File

@ -1 +0,0 @@
uid://c7a12a1pe5esr

View File

@ -1,256 +0,0 @@
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()

View File

@ -1 +0,0 @@
uid://cpl5jb0mwxanh

File diff suppressed because it is too large Load Diff

View File

@ -1 +0,0 @@
uid://dyxornv8vwibg

View File

@ -1,7 +0,0 @@
[plugin]
name="LimboConsole"
description="Yet another in-game console with a simple command interpreter."
author="Serhii Snitsaruk"
version="0.4.1"
script="plugin.gd"

View File

@ -1,57 +0,0 @@
@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")

View File

@ -1 +0,0 @@
uid://b4t0bfyjdn8i0

View File

@ -1,176 +0,0 @@
[gd_resource type="Theme" 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")

View File

@ -1,93 +0,0 @@
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.

View File

@ -1,36 +0,0 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://dbbw833hg2v7o"
path="res://.godot/imported/monaspace_argon_bold.otf-5d916059e7810f56d30161557f70d71b.fontdata"
[deps]
source_file="res://addons/limbo_console/res/fonts/monaspace_argon_bold.otf"
dest_files=["res://.godot/imported/monaspace_argon_bold.otf-5d916059e7810f56d30161557f70d71b.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
disable_embedded_bitmaps=true
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
modulate_color_glyphs=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={}

View File

@ -1,36 +0,0 @@
[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
modulate_color_glyphs=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={}

View File

@ -1,36 +0,0 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://dhm45nttm5i3s"
path="res://.godot/imported/monaspace_argon_italic.otf-69d64783adde526699a99a191cd14ed6.fontdata"
[deps]
source_file="res://addons/limbo_console/res/fonts/monaspace_argon_italic.otf"
dest_files=["res://.godot/imported/monaspace_argon_italic.otf-69d64783adde526699a99a191cd14ed6.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
disable_embedded_bitmaps=true
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
modulate_color_glyphs=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={}

View File

@ -1,36 +0,0 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://ds7dvyquauqub"
path="res://.godot/imported/monaspace_argon_medium.otf-2c090420a59a2bfcd1b6b28a7c3469ad.fontdata"
[deps]
source_file="res://addons/limbo_console/res/fonts/monaspace_argon_medium.otf"
dest_files=["res://.godot/imported/monaspace_argon_medium.otf-2c090420a59a2bfcd1b6b28a7c3469ad.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
disable_embedded_bitmaps=true
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
modulate_color_glyphs=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={}

View File

@ -1,36 +0,0 @@
[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
modulate_color_glyphs=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={}

View File

@ -1,119 +0,0 @@
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

View File

@ -1 +0,0 @@
uid://cw6s1es6yjip5

View File

@ -1,6 +1,6 @@
[gd_scene format=3 uid="uid://cpfunq8fyixco"]
[ext_resource type="Script" uid="uid://b5b7ohheyhxr0" path="res://addons/sprint/sprint.gd" id="1_thwo5"]
[ext_resource type="Script" uid="uid://b5b7ohheyhxr0" path="res://addons/logger/logger.gd" id="1_thwo5"]
[node name="Logger" type="CanvasLayer" unique_id=665187677]
process_mode = 3

View File

@ -3,7 +3,7 @@ extends EditorPlugin
func _enable_plugin() -> void:
add_autoload_singleton("SPrint", "res://addons/sprint/sprint.tscn")
add_autoload_singleton("SPrint", "res://addons/logger/logger.tscn")
func _disable_plugin() -> void:

View File

@ -1,10 +0,0 @@
[gd_resource type="Texture2D" script_class="ControllerIconTexture" format=3 uid="uid://bi8jxa3gcbfdn"]
[ext_resource type="Script" uid="uid://ch00l1e1rteyw" path="res://addons/controller_icons/objects/ControllerIconTexture.gd" id="1_lwov1"]
[resource]
resource_local_to_scene = false
resource_name = ""
script = ExtResource("1_lwov1")
path = "ui_accept"
metadata/_custom_type_script = "uid://ch00l1e1rteyw"

View File

@ -1,10 +0,0 @@
[gd_resource type="Texture2D" script_class="ControllerIconTexture" format=3 uid="uid://cjtlrpqb75elt"]
[ext_resource type="Script" uid="uid://ch00l1e1rteyw" path="res://addons/controller_icons/objects/ControllerIconTexture.gd" id="1_11853"]
[resource]
resource_local_to_scene = false
resource_name = ""
script = ExtResource("1_11853")
path = "ui_cancel"
metadata/_custom_type_script = "uid://ch00l1e1rteyw"

View File

@ -1,18 +0,0 @@
shader_type spatial;
#include "uid://cy7b01or0ckgk" // Rhythm Helper
void vertex() {
// Called for every vertex the material is visible on.
}
void fragment() {
float phase = get_bar_phase();
ALPHA = (1.0 - phase) * (UV.y > phase ? 1.0 : 0.0) * (UV.x < 1.0 - get_song_time_ratio() ? 1.0 : 0.0);
ALBEDO.gb = vec2(distance(phase + 0.75, 0.75));
}
//void light() {
// // Called for every pixel for every light affecting the material.
// // Uncomment to replace the default light processing function with this one.
//}

View File

@ -1 +0,0 @@
uid://dlqjg6dby6pdy

View File

@ -1,18 +0,0 @@
shader_type spatial;
#include "uid://cy7b01or0ckgk" // Rhythm Helper
void vertex() {
// Called for every vertex the material is visible on.
}
void fragment() {
float phase = get_beat_phase();
ALPHA = (1.0 - phase) * (UV.y > phase ? 1.0 : 0.0) * (-UV.x < -get_song_time_ratio() ? 1.0 : 0.0);
ALBEDO.gb = vec2(distance(phase + 0.5, 0.5));
}
//void light() {
// // Called for every pixel for every light affecting the material.
// // Uncomment to replace the default light processing function with this one.
//}

View File

@ -1 +0,0 @@
uid://c7n5s8eodlca6

View File

@ -1,24 +0,0 @@
shader_type spatial;
render_mode unshaded, shadows_disabled, cull_disabled;
#include "uid://cy7b01or0ckgk" // Rhythm Helper
uniform vec3 color: source_color = vec3(1.0, 1.0, 1.0);
uniform float alpha_multiplier: hint_range(0.0, 1.0, 0.001) = 0.25;
uniform sampler2D albedo;
uniform float flash_exponent = 3.0;
void vertex() {
// Called for every vertex the material is visible on.
}
void fragment() {
float phase = get_beat_phase();
vec4 tex = texture(albedo, UV);
ALBEDO = mix(tex.rgb, color.rgb, 0.5);
ALPHA = tex.a * ease(1.0 - phase, flash_exponent) * alpha_multiplier;//mix(tex.a, color.a * (1.0 - phase), 0.5);
}
//void light() {
// // Called for every pixel for every light affecting the material.
// // Uncomment to replace the default light processing function with this one.
//}

View File

@ -1 +0,0 @@
uid://c32arbbdbo7m8

View File

@ -1,23 +0,0 @@
shader_type spatial;
render_mode unshaded, shadows_disabled;
#include "uid://cy7b01or0ckgk" // Rhythm Helper
uniform sampler2D albedo;
uniform float flash_exponent = 3.0;
uniform float intensity_multiplier = 1.0;
void vertex() {
// Called for every vertex the material is visible on.
}
void fragment() {
float phase = get_beat_phase();
vec4 tex = texture(albedo, UV);
float intensity = ease((1.0 - phase), flash_exponent) * intensity_multiplier;
ALBEDO = tex.rgb * intensity;
}
//void light() {
// // Called for every pixel for every light affecting the material.
// // Uncomment to replace the default light processing function with this one.
//}

View File

@ -1 +0,0 @@
uid://b46o5a45g58xb

View File

@ -1,106 +0,0 @@
global uniform float beat;
global uniform float bar;
global uniform float song_time;
global uniform float total_song_time;
global uniform float user_offset_ms;
uniform float phase_offset = 0.0;
uniform float phase_multiplier = 1.0;
instance uniform float instance_phase_offset = 0.0;
instance uniform float instance_phase_multiplier = 1.0;
float get_beat_with_offset() {
return (beat * phase_multiplier * instance_phase_multiplier) + phase_offset + instance_phase_offset;// + (user_offset_ms / 1000.0);
}
float get_bar_with_offset() {
return (bar * phase_multiplier * instance_phase_multiplier) + phase_offset + instance_phase_offset;// + (user_offset_ms / 1000.0);
}
float get_beat_phase() {
float beat_offset = get_beat_with_offset();
return beat_offset - floor(beat_offset);
}
float get_bar_phase() {
float bar_offset = get_bar_with_offset();
return bar_offset - floor(bar_offset);
}
float get_song_time_ratio() {
return song_time / max(total_song_time, 0.001);
}
float ease(float x, float c) {
// Godot's source code converted via ChatGPT to GLSL-style
//branchless:
//x = clamp(x, 0.0, 1.0);
//float pos = mix(
//1.0 - pow(1.0 - x, 1.0 / c),
//pow(x, c),
//step(1.0, c)
//);
//
//float k = -c;
//float x2 = x * 2.0;
//float inout_v = mix(
//pow(x2, k) * 0.5,
//(1.0 - pow(2.0 - x2, k)) * 0.5 + 0.5,
//step(0.5, x)
//);
//
//float isPos = step(0.0, c);
//float isNeg = step(c, 0.0);
//
//return pos * isPos + inout_v * isNeg;
// a few branches:
x = clamp(x, 0.0, 1.0);
float pos = mix(
1.0 - pow(1.0 - x, 1.0 / c), // 0 < c < 1
pow(x, c), // c >= 1
step(1.0, c)
);
float k = -c;
float x2 = x * 2.0;
float inout_v = mix(
pow(x2, k) * 0.5,
(1.0 - pow(2.0 - x2, k)) * 0.5 + 0.5,
step(0.5, x)
);
return (c > 0.0) ? pos :
(c < 0.0) ? inout_v :
0.0;
// From godot's source code:
//if (x < 0.0) {
//x = 0.0;
//} else if (x > 1.0) {
//x = 1.0;
//}
//if (c > 0.0) {
//if (c < 1.0) {
//return 1.0 - pow(1.0 - x, 1.0 / c);
//} else {
//return pow(x, c);
//}
//} else if (c < 0.0) {
////inout ease
//
//if (x < 0.5) {
//return pow(x * 2.0, -c) * 0.5;
//} else {
//return (1.0 - pow(1.0 - (x - 0.5) * 2.0, -c)) * 0.5 + 0.5;
//}
//} else {
//return 0.0; // no ease (raw)
//}
}

View File

@ -1 +0,0 @@
uid://cy7b01or0ckgk

View File

@ -1,22 +0,0 @@
[gd_resource type="AudioBusLayout" format=3 uid="uid://jka2h4facmsp"]
[resource]
bus/0/volume_db = -4.002995
bus/1/name = &"SFX"
bus/1/solo = false
bus/1/mute = false
bus/1/bypass_fx = false
bus/1/volume_db = 0.0
bus/1/send = &"Master"
bus/2/name = &"Music"
bus/2/solo = false
bus/2/mute = false
bus/2/bypass_fx = false
bus/2/volume_db = 0.0
bus/2/send = &"Master"
bus/3/name = &"Ambient"
bus/3/solo = false
bus/3/mute = false
bus/3/bypass_fx = false
bus/3/volume_db = 0.0
bus/3/send = &"Master"

View File

@ -1,40 +1,27 @@
KEYS,?plural,en,de,es,ja
KEYS,en,de,es,ja
# UI
CONTINUE,,Continue,Weiterspielen,Continue,Continue
LOAD,,Load,Laden,Load,Load
NEW_GAME,,New Game,Neues Spiel,New Game,New Game
SETTINGS,,Settings,Einstellungen,Settings,Settings
QUIT,,Quit,Beenden,Quit,Quit
LOADING,,[loading]Loading...[/loading],[loading]Laden...[/loading],[loading]Loading...[/loading],[loading]Loading...[/loading]
BACK,,Back,Zurück,Back,Back
CONTINUE,Continue,Weiterspielen,Continue,Continue
LOAD,Load,Laden,Load,Load
NEW_GAME,New Game,Neues Spiel,New Game,New Game
SETTINGS,Settings,Einstellungen,Settings,Settings
QUIT,Quit,Beenden,Quit,Quit
LOADING,[loading]Loading...[/loading],[loading]Laden...[/loading],[loading]Loading...[/loading],[loading]Loading...[/loading]
# Options/Settings
AUDIO,,Audio,Audio,Audio,Audio
MASTER,,Master,Master,Master,Master
SFX,,SFX,SFX,SFX,SFX
MUSIC,,Music,Musik,Music,Music
AMBIENT,,Ambience,Ambiente,Ambience,Ambience
GRAPHICS,,Graphics,Grafik,Graphics,Graphics
FULLSCREEN,,Fullscreen,Vollbild,Fullscreen,Fullscreen
MISC,,Misc,Misc,Misc,Misc
HEADBOBBING,,Headbobbing,Kopfwackeln,Headbobbing,Headbobbing
HEADBOBBING_MULTIPLIER,,Headbobbing Multiplier,Wackel-Multiplikator,Headbobbing Multiplier,Headbobbing Multiplier
WINDOW_FULLSCREEN,,Fullscreen,Vollbild,Fullscreen,Fullscreen
WINDOW_EXCLUSIVE_FULLSCREEN,,Exclusive Fullscreen,Exklusiv-Vollbild,Exclusive Fullscreen,Exclusive Fullscreen
WINDOW_WINDOWED,,Windowed,Fenstermodus,Windowed,Windowed
SETTINGS_CONFIRMATION_TITLE,,Apply Changes?,Änderungen Anwenden?,Apply Changes?,Apply Changes?,Apply Changes?
SETTINGS_APPLY,,Apply,Anwenden,Apply,Apply,Apply
SETTINGS_REVERT,,Revert,Zurücksetzen,Revert,Revert,Revert
?pluralrule,,nplurals=2; plural=(n != 1);,nplurals=2; plural=(n != 1);,nplurals=2; plural=(n != 1);,nplurals=2; plural=(n != 1);
SETTINGS_REVERT_TIME_LEFT,.,Reverting in %s second...,Wird in %s sekunde zurückgesetzt...,Reverting in %s second...,Reverting in %s second...
,,Reverting in %s seconds...,Wird in %s sekunden zurückgesetzt...,Reverting in %s seconds...,Reverting in %s seconds...
AUDIO,Audio,Audio,Audio,Audio
Master,Master,Master,Master,Master
SFX,SFX,SFX,SFX,SFX
MUSIC,Music,Musik,Music,Music
AMBIENT,Ambient,Ambiente,Ambient,Ambient
GRAPHICS,Graphics,Grafik,Graphics,Graphics
FULLSCREEN,Fullscreen,Vollbild,Fullscreen,Fullscreen
Misc,Misc,Misc,Misc,Misc
HEADBOBBING,Headbobbing,Kopfwackeln,Headbobbing,Headbobbing
HEADBOBBING_MULTIPLIER,Headbobbing Multiplier,Wackel-Multiplikator,Headbobbing Multiplier, Headbobbing Multiplier
# Death Types
DEATH_LABEL_SAW,,Killed by a saw,Von einer Säge getötet,Killed by a saw,Killed by a saw
DEATH_MSG_SAW,,Better watch your step next time...,,,
DEATH_LABEL_DOOM_FALL,,Doomed to fall,Zum Fallen verdammt,,
DEATH_MSG_DOOM_FALL,,"Yes, you can fall off the map now...","Ja, du kannst jetzt von der map fallen...",,
DEATH_LABEL_SAW,Killed by a saw,Von einer Säge getötet,Killed by a saw,Killed by a saw
DEATH_MSG_SAW,Better watch your step next time...,,,
DEATH_LABEL_DOOM_FALL,Doomed to fall,Zum Fallen verdammt,,
DEATH_MSG_DOOM_FALL,"Yes, you can fall off the map now...","Ja, du kannst jetzt von der map fallen...",,

Can't render this file because it has a wrong number of fields in line 3.

View File

@ -16,19 +16,12 @@ run/main_scene="uid://ga0d5817hbc8"
config/features=PackedStringArray("4.6", "Forward Plus")
config/icon="res://godot_icon.svg"
[audio]
buses/default_bus_layout="uid://jka2h4facmsp"
[autoload]
SPrint="*uid://cpfunq8fyixco"
VersionDisplay="*uid://bqxtpo2c64h22"
InputManager="*uid://cocp0vmvgd4ln"
ControllerIcons="*uid://bdosbfkp568je"
ShaderGlobals="*uid://d2lr860r1ysrm"
LimboConsole="*uid://dyxornv8vwibg"
SettingsHandler="*uid://dj1t6rc6fpiti"
[debug]
@ -41,7 +34,7 @@ settings/3d/volumetric_defaults/thickness=0.0
[editor_plugins]
enabled=PackedStringArray("res://addons/Todo_Manager/plugin.cfg", "res://addons/anim_player_refactor/plugin.cfg", "res://addons/controller_icons/plugin.cfg", "res://addons/lightmap_probe_grid/plugin.cfg", "res://addons/limbo_console/plugin.cfg", "res://addons/reflection_probe_preview/plugin.cfg", "res://addons/sprint/plugin.cfg", "res://addons/version_display/plugin.cfg")
enabled=PackedStringArray("res://addons/Todo_Manager/plugin.cfg", "res://addons/controller_icons/plugin.cfg", "res://addons/lightmap_probe_grid/plugin.cfg", "res://addons/logger/plugin.cfg", "res://addons/reflection_probe_preview/plugin.cfg", "res://addons/version_display/plugin.cfg")
[file_customization]
@ -173,22 +166,6 @@ toggle_flashlight={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":70,"key_label":0,"unicode":102,"location":0,"echo":false,"script":null)
]
}
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)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194341,"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)
]
}
[internationalization]
@ -210,26 +187,3 @@ locale/translations=PackedStringArray("res://localization/localization.en.transl
rendering_device/driver.windows="d3d12"
anti_aliasing/quality/msaa_3d=3
occlusion_culling/use_occlusion_culling=true
[shader_globals]
song_time={
"type": "float",
"value": 0.0
}
total_song_time={
"type": "float",
"value": 0.0
}
beat={
"type": "float",
"value": 0.0
}
bar={
"type": "float",
"value": 0.0
}
user_offset_ms={
"type": "float",
"value": 0.0
}

View File

@ -1,101 +0,0 @@
extends Node
signal settings_changed
const CONFIG_FILE_PATH: String = "user://settings.ini"
var settings: Dictionary[String, Dictionary] = {
"audio": {
"user_offset_ms": 0.0,
"Master": 0.85,
"Music": 1.0,
"SFX": 1.0,
"Ambient": 1.0,
},
"video": {
"window_mode": DisplayServer.WindowMode.WINDOW_MODE_FULLSCREEN,
},
"gameplay": {
"headbobbing": true,
"headbobbing_multiplier": 1.0,
},
"localization": {
"language": "en_US",
},
"controls": {
"controller_vibration_min_range": 0.0,
},
}
var _previous_settings: Dictionary[String, Dictionary] = settings
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
load_from_file()
commit_settings()
apply_settings()
save_settings()
func load_from_file() -> void:
var config := ConfigFile.new()
if config.load(CONFIG_FILE_PATH) != OK:
return
for section: String in config.get_sections():
if not settings.has(section):
continue
for key: String in config.get_section_keys(section):
var default: Variant = get_setting(section, key)
set_setting(section, key, config.get_value(section, key, default), false)
settings_changed.emit()
func apply_settings() -> void:
# Audio
RhythmPlayer.user_offset_ms = get_setting("audio", "user_offset_ms", 0.0)
AudioServer.set_bus_volume_linear(AudioServer.get_bus_index(&"Master"), get_setting("audio", "Master", 0.85))
AudioServer.set_bus_volume_linear(AudioServer.get_bus_index(&"SFX"), get_setting("audio", "SFX", 1.0))
AudioServer.set_bus_volume_linear(AudioServer.get_bus_index(&"Music"), get_setting("audio", "Music", 1.0))
AudioServer.set_bus_volume_linear(AudioServer.get_bus_index(&"Ambient"), get_setting("audio", "Ambient", 1.0))
# Localization
TranslationServer.set_locale(get_setting("localization", "language", "en_US"))
# Video
if not Engine.is_embedded_in_editor():
DisplayServer.window_set_mode(get_setting("video", "window_mode", DisplayServer.WindowMode.WINDOW_MODE_FULLSCREEN))
func commit_settings() -> void:
_previous_settings = settings.duplicate(true)
func save_settings() -> void:
var config: ConfigFile = Utils.load_config(CONFIG_FILE_PATH)
for section: String in settings.keys():
for key: String in settings.get(section, {}).keys():
config.set_value(section, key, settings[section][key])
config.save(CONFIG_FILE_PATH)
func revert_settings() -> void:
settings = _previous_settings.duplicate(true)
apply_settings()
func set_setting(section: String, key: String, value: Variant, do_save: bool = true) -> void:
settings[section][key] = value
if do_save:
apply_settings()
commit_settings()
save_settings()
settings_changed.emit()
func get_setting(section: String, key: String, default: Variant = null) -> Variant:
return settings.get(section, {}).get(key, default)

View File

@ -1 +0,0 @@
uid://dj1t6rc6fpiti

View File

@ -1,33 +0,0 @@
extends Node
var beat: float = 0.0: set = set_beat
var bar: float = 0.0: set = set_bar
var song_time: float = 0.0: set = set_song_time
var total_song_time: float = 0.0: set = set_total_song_time
var user_offset_ms: float = 0.0
func set_user_offset_ms(offset: float) -> void:
user_offset_ms = offset
RenderingServer.global_shader_parameter_set(&"user_offset_ms", beat)
func set_beat(_beat: float) -> void:
beat = _beat
RenderingServer.global_shader_parameter_set(&"beat", beat)
func set_bar(_bar: float) -> void:
bar = _bar
RenderingServer.global_shader_parameter_set(&"bar", bar)
func set_song_time(time: float) -> void:
song_time = time
RenderingServer.global_shader_parameter_set(&"song_time", time)
func set_total_song_time(time: float) -> void:
total_song_time = time
RenderingServer.global_shader_parameter_set(&"total_song_time", time)

View File

@ -1 +0,0 @@
uid://d2lr860r1ysrm

View File

@ -12,13 +12,14 @@ var time: float = 0.0
var headbobbing_multiplier: float = 1.0 # User setting.
func _ready() -> void:
var _update_settings: Callable = func() -> void:
effect_enabled = SettingsHandler.get_setting("gameplay", "headbobbing", true)
headbobbing_multiplier = SettingsHandler.get_setting("gameplay", "headbobbing_multiplier", 1.0)
SettingsHandler.settings_changed.connect(_update_settings)
_update_settings.call()
#func _ready() -> void:
#var _update_settings: Callable = func() -> void:
#effect_enabled = SettingsManager.get_setting(&"misc", &"headbobbing")
#headbobbing_multiplier = SettingsManager.get_setting(&"misc", &"headbobbing_multiplier")
#
#SettingsManager.settings_changed.connect(_update_settings)
#ProjectSettings.settings_changed.connect(_update_settings)
#_update_settings.call()
func _process_effect(delta: float) -> void:

View File

@ -1,66 +0,0 @@
class_name RhythmListener
extends Node
signal beat_tick(beat_index: int)
signal bar_tick(bar_index: int)
@export_range(0.0, 1.0, 0.001, "or_greater", "or_less") var phase_shift: float = 0.0
var beat: float = 0.0
var bar: float = 0.0
var _last_beat_time: float = 0.0
var _last_beat_index: int = -1
var _last_bar_index: int = -1
var _last_bar_time: float = 0.0
func _process(_delta: float) -> void:
if RhythmPlayer.paused:
_last_beat_index = 0
_last_bar_index = 0
return
beat = RhythmPlayer.beat + phase_shift
bar = RhythmPlayer.bar + phase_shift
var beat_index: int = floori(beat)
var bar_index: int = floori(bar)
if _last_beat_index != beat_index:
on_beat_tick(beat_index)
_last_beat_index = beat_index
_last_beat_time = RhythmPlayer.song_time + phase_shift
if _last_bar_index != bar_index:
on_bar_tick(bar_index)
_last_bar_index = bar_index
_last_bar_time = RhythmPlayer.song_time + phase_shift
#SPrint.print_msgf("Time To Next Bar: %s" % get_time_to_next_bar())
#SPrint.print_msgf("Time To Next Beat: %s" % get_time_to_next_beat())
func get_time_to_next_beat() -> float:
var beat_duration: float = 60.0 / RhythmPlayer.bpm
return (_last_beat_time - (RhythmPlayer.song_time + phase_shift)) + beat_duration
func get_time_to_next_bar() -> float:
var bar_duration: float = 60.0 / (RhythmPlayer.bpm / RhythmPlayer.beats_per_bar)
return (_last_bar_time - (RhythmPlayer.song_time + phase_shift)) + bar_duration
#func get_time_to_beat(target_beat: float) -> float:
#var beat_duration: float = 60.0 / RhythmPlayer.bpm
#
func on_beat_tick(beat_index: int) -> void:
#SPrint.print_msg("Beat received %s" % beat_index, 0.75)
beat_tick.emit(beat_index)
func on_bar_tick(bar_index: int) -> void:
#SPrint.print_msg("Bar received %s" % bar_index, 1.0)
bar_tick.emit(bar_index)

View File

@ -1 +0,0 @@
uid://co7j2qtqpud6b

View File

@ -1,168 +0,0 @@
class_name RhythmPlayer
extends Node
signal beat_tick(beat_index: int)
signal bar_tick(bar_index: int)
static var user_offset_ms: float = 0.0: set = set_user_offset_ms
# Since only one rhythm player should play at any time, these are all static.
static var current_song_info: SongInfo
static var paused: bool = true
static var bpm: float = 120.0
static var beats_per_bar: int = 4
static var song_time: float = 0.0
static var beat: float = 0.0
static var beat_phase: float = 0.0
static var bar: float = 0.0
static var bar_phase: float = 0.0
@export var song_info: SongInfo
@export var autoplay: bool = false
@export var playback_position: float = 0.0
@export_node_path("AudioStreamPlayer", "AudioStreamPlayer2D", "AudioStreamPlayer3D")
var audio_player: NodePath: set = set_audio_player
var _audio_player: Node
var _last_beat: int = -1
var _last_bar: int = -1
static func set_user_offset_ms(offset: float) -> void:
user_offset_ms = offset
ShaderGlobals.set_user_offset_ms(offset)
func _ready() -> void:
set_audio_player(audio_player)
if autoplay:
play(playback_position)
func _process(_delta: float) -> void:
if not is_instance_valid(_audio_player):
return
paused = not _audio_player.playing
if not _audio_player.playing:
_last_beat = -1
_last_bar = -1
return
_update_playback()
func get_song_time() -> float:
if not is_instance_valid(_audio_player):
return 0.0
var time: float = _audio_player.get_playback_position()
time += AudioServer.get_time_since_last_mix()
time -= AudioServer.get_output_latency()
return time
func get_song_time_with_user_offset() -> float:
return get_song_time() + user_offset_ms / 1000.0
func get_beat() -> float:
return song_info.tempo_map.get_beat_at_time(get_song_time_with_user_offset() + song_info.phase_shift)
func get_bar_phase() -> float:
return bar - floorf(bar)
func get_bar() -> float:
return beat / beats_per_bar
## 0.0 -> beat start
## 0.5 -> halfway
## 0.99 -> almost next beat
func get_beat_phase() -> float:
return beat - floorf(beat)
func beat_to_signature() -> int:
return posmod(floori(beat), beats_per_bar) + 1
static func bar_to_signature(bar_index: int, _bar_signature: int) -> int:
return bar_index
#return posmod(bar_index, bar_signature) + 1
func set_audio_player(audio_player_path: NodePath) -> void:
audio_player = audio_player_path
_audio_player = get_node_or_null(audio_player)
if not is_instance_valid(_audio_player):
set_process(false)
return
if (
not _audio_player.has_method(&"play")
or not _audio_player.has_method(&"get_playback_position")
or not &"playing" in _audio_player
):
_audio_player = null
push_error("audio_player is not a valid player.")
return
#set_process(_audio_player.playing)
set_process(true)
func play(position: float = 0.0) -> void:
current_song_info = song_info
_audio_player.stream = song_info.audio_stream
ShaderGlobals.set_total_song_time(song_info.audio_stream.get_length())
bpm = song_info.bpm
beats_per_bar = song_info.beats_per_bar
song_info.create_tempo_map()
_audio_player.play(position)
func stop() -> void:
_audio_player.stop()
func _update_playback() -> void:
beat = get_beat()
beat_phase = get_beat_phase()
bar = get_bar()
bar_phase = get_bar_phase()
song_time = get_song_time_with_user_offset() + song_info.phase_shift
ShaderGlobals.set_song_time(song_time)
var tempo_section: TempoMap.TempoSection = song_info.tempo_map.get_tempo_section_at_time(song_time)
bpm = tempo_section.bpm
beats_per_bar = tempo_section.beats_per_bar
ShaderGlobals.set_beat(beat)
ShaderGlobals.set_bar(bar)
var beat_index: int = floori(beat)
var bar_index: int = floori(bar)
SPrint.print_msgf("Beat: %s (%s) on beat: %s" % [beat_index, beat_to_signature(), beat_phase <= (1.0 / beats_per_bar)])
SPrint.print_msgf("Bar: %s (%s)" % [bar_to_signature(bar_index, beats_per_bar), str(bar_phase).pad_decimals(4)])
#SPrint.print_msgf("Bar: %s (%s) bar on beat: %s" % [bar_to_signature(bar_index, _bars), bar_index, bar_index % _bars == 0])
if _last_beat != beat_index:
beat_tick.emit(beat_index)
_last_beat = beat_index
if _last_bar != bar_index:
bar_tick.emit(bar_index)
_last_bar = bar_index

View File

@ -1 +0,0 @@
uid://bdi06itcm6wfp

View File

@ -1,59 +0,0 @@
class_name RhythmPropertySetter
extends Node
@export var nodes: Array[Node] = []
@export var beat_nodes: Array[Node] = []
@export var bar_nodes: Array[Node] = []
@export var beat_property: StringName = &""
@export var bar_property: StringName = &""
@export var phase_shift: float = 0.0
@export var phase_multiplier: float = 1.0
@export_group("Beat Value", "beat_")
@export var beat_range_min: float = 0.0
@export var beat_range_max: float = 1.0
@export_exp_easing("attenuation") var beat_exponent: float = 1.0
@export_group("Bar Value", "bar_")
@export var bar_range_min: float = 0.0
@export var bar_range_max: float = 1.0
@export_exp_easing("attenuation") var bar_exponent: float = 1.0
func _process(_delta: float) -> void:
if RhythmPlayer.paused:
return
if not beat_property.is_empty():
_update_beat_property()
if not bar_property.is_empty():
_update_bar_property()
func _update_beat_property() -> void:
var beat: float = RhythmPlayer.beat * phase_multiplier + phase_shift
var beat_phase: float = beat - floorf(beat)
var beat_value: float = remap(beat_phase, 0.0, 1.0, beat_range_min, beat_range_max)
beat_value *= ease(1.0 - beat_phase, beat_exponent)
var _nodes: Array[Node] = beat_nodes.duplicate()
_nodes.append_array(nodes)
for node: Node in _nodes:
node.set_indexed(NodePath(beat_property), beat_value)
func _update_bar_property() -> void:
var bar: float = RhythmPlayer.bar * phase_multiplier + phase_shift
var bar_phase: float = bar - floorf(bar)
var bar_value: float = remap(bar_phase, 0.0, 1.0, bar_range_min, bar_range_max)
bar_value *= ease(1.0 - bar_phase, bar_exponent)
var _nodes: Array[Node] = bar_nodes.duplicate()
_nodes.append_array(nodes)
for node: Node in _nodes:
node.set_indexed(NodePath(bar_property), bar_value)

View File

@ -1 +0,0 @@
uid://bvmfdeypbeqsn

View File

@ -1,41 +0,0 @@
class_name SongInfo
extends Resource
#@export_file("*.tres", "*.res", "*.ogg", "*.wav", "*.mp3")
#var audio_path: String
@export var audio_stream: AudioStream
@export var bpm: float = 120
@export_range(0, 8, 1, "or_greater") var beats_per_bar: int = 4
@export_range(0.0, 1.0, 0.001, "or_greater", "or_less") var phase_shift: float = 0.0
#@export_enum("1/2", "1/4", "1/8", "1/16", "1/32", "1/64") var time_signature: int = 1
@export_group("Tempo Sections")
@export var tempo_infos: Array[TempoSectionInfo] = []
@export var tempo_sections_in_seconds: Dictionary[float, float]
@export_subgroup("Tempo Sections Simple")
@export var tempo_section_round_to_bar: bool = true
@export var tempo_section_round_to_beat: bool = false
var tempo_map: TempoMap
func _init() -> void:
create_tempo_map()
func create_tempo_map() -> void:
if tempo_infos.is_empty():
tempo_map = TempoMap.create_from_time_dictionary(
tempo_sections_in_seconds,
bpm,
tempo_section_round_to_bar,
tempo_section_round_to_beat,
beats_per_bar
)
else:
tempo_map = TempoMap.create_from_section_infos(tempo_infos, bpm, beats_per_bar)
#func get_time_signature() -> int:
#return int(pow(2, time_signature + 1))

View File

@ -1 +0,0 @@
uid://c5mqtmsvgt4e8

View File

@ -1,194 +0,0 @@
class_name TempoMap
extends Resource
@export var time_sections: Dictionary[float, float] = {}
@export var section_infos: Array[TempoSectionInfo] = []
var sections: Array[TempoSection] = []
static func create_from_beat_dictionary(
dictionary: Dictionary[float, float],
fallback_bpm: float
) -> TempoMap:
var map := TempoMap.new()
if dictionary.is_empty():
map.add_section(0.0, fallback_bpm)
return map
var keys: Array[float] = dictionary.keys()
keys.sort()
for key: float in keys:
map.add_section(key, dictionary.get(key))
map.recalculate_times()
return map
static func create_from_time_dictionary(
dictionary: Dictionary[float, float],
fallback_bpm: float,
round_to_bars: bool = true,
round_to_whole_beats: bool = false,
beats_per_bar: int = 4
) -> TempoMap:
var map := TempoMap.new()
if dictionary.is_empty():
map.add_section(0.0, fallback_bpm, beats_per_bar)
return map
var times: Array[float] = dictionary.keys()
times.sort()
var current_beat: float = 0.0
var previous_time: float = 0.0
var previous_bpm: float = fallback_bpm
map.add_section(0.0, fallback_bpm)
for time: float in times:
var delta: float = time - previous_time
current_beat += delta * previous_bpm / 60.0
var bpm: float = dictionary[time]
if round_to_bars:
current_beat = roundf(current_beat / beats_per_bar) * beats_per_bar
if round_to_whole_beats:
current_beat = roundf(current_beat)
map.add_section(current_beat, bpm)
previous_time = time
previous_bpm = bpm
map.recalculate_times()
return map
static func create_from_section_infos(infos: Array[TempoSectionInfo], fallback_bpm: float, beats_per_bar_fallback: int) -> TempoMap:
var map := TempoMap.new()
if infos.is_empty():
map.add_section(0.0, fallback_bpm, beats_per_bar_fallback)
return map
var times: Dictionary[float, TempoSectionInfo] = {}
for info: TempoSectionInfo in infos:
times.set(info.time, info)
times.sort()
var current_beat: float = 0.0
var previous_time: float = 0.0
var previous_bpm: float = fallback_bpm
var previous_beats_per_bar: int = beats_per_bar_fallback
map.add_section(0.0, fallback_bpm)
for time: float in times.keys():
var delta: float = time - previous_time
current_beat += delta * previous_bpm / 60.0
var info: TempoSectionInfo = times[time]
var bpm: float = info.bpm
var beats_per_bar: int = info.beats_per_bar
if info.snap_to_previous_beat:
current_beat = roundf(current_beat)
if info.snap_to_previous_bar:
current_beat = roundf(current_beat / previous_beats_per_bar) * previous_beats_per_bar
map.add_section(current_beat, bpm, beats_per_bar)
previous_time = time
previous_bpm = bpm
previous_beats_per_bar = beats_per_bar
map.recalculate_times()
return map
func add_section(beat: float, bpm: float, beats_per_bar: int = 4) -> void:
if not sections.is_empty() and is_equal_approx(sections.back().beat, beat):
sections.back().bpm = bpm
sections.back().beats_per_bar = beats_per_bar
else:
sections.append(TempoSection.new(bpm, beat, beats_per_bar))
func recalculate_times() -> void:
if sections.is_empty():
return
sections.front().time = 0.0
print("[TempoMap] Calculating tempo changes:")
for index: int in range(1, sections.size()):
var previous: TempoSection = sections[index - 1]
var current: TempoSection = sections[index]
var beat_delta: float = current.beat - previous.beat
var seconds: float = beat_delta * 60.0 / previous.bpm
current.time = previous.time + seconds
print_rich("- [b]%s[/b] [i]bpm[/i] ([b]1/%s[/b]) at [b]%s[/b] [i]seconds[/i] (beat delta: [b]%s[/b], seconds delta: [b]%s[/b])." % [current.bpm, current.beats_per_bar, current.time, beat_delta, seconds])
func get_beat_at_time(time: float) -> float:
if sections.is_empty():
return 0.0
# O(log n) (Binary search)
var low: int = 0
var high: int = sections.size() - 1
while low <= high:
@warning_ignore("integer_division")
var mid: int = (low + high) / 2
if sections[mid].time <= time:
low = mid + 1
else:
high = mid - 1
var section: TempoSection = sections[maxi(high, 0)]
SPrint.print_msgf("Section: %s" % section.bpm)
return section.beat + (time - section.time) * section.bpm / 60.0
func get_tempo_section_at_time(time: float) -> TempoSection:
if sections.is_empty():
return null
# O(log n) (Binary search)
var low: int = 0
var high: int = sections.size() - 1
while low <= high:
@warning_ignore("integer_division")
var mid: int = (low + high) / 2
if sections[mid].time <= time:
low = mid + 1
else:
high = mid - 1
var section: TempoSection = sections[maxi(high, 0)]
return section
class TempoSection extends Resource:
var beat: float
var beats_per_bar: int
var bpm: float
var time: float
func _init(_bpm: float, _beat: float, _beats_per_bar: int = 4) -> void:
bpm = _bpm
beat = _beat
beats_per_bar = _beats_per_bar

View File

@ -1 +0,0 @@
uid://b7vjbaiu1nst

View File

@ -1,9 +0,0 @@
class_name TempoSectionInfo
extends Resource
@export var time: float = 0.0
@export var bpm: float = 120.0
@export var beats_per_bar: int = 4
@export var snap_to_previous_beat: bool = true
@export var snap_to_previous_bar: bool = false

View File

@ -1 +0,0 @@
uid://cx1nws4t8hs2u

View File

@ -3,8 +3,6 @@ extends Object
const VEC3_HOR := Vector3(1.0, 0.0, 1.0)
const VEC3_XY := Vector3(1.0, 1.0, 0.0)
const VEC3_YZ := Vector3(0.0, 1.0, 1.0)
static func free_node_safely(node: Node) -> void:
@ -45,13 +43,13 @@ static func get_all_children(node: Node, internal: bool = false) -> Array[Node]:
static func node_distance(
node_a: Node3D, node_b: Node3D, position_multiplier := Vector3.ONE
node_a: Node3D, node_b: Node3D, position_modifier := Vector3.ONE
) -> float:
if not is_instance_valid(node_a) or not is_instance_valid(node_b):
return -1.0
return (node_a.global_position * position_multiplier).distance_to(
node_b.global_position * position_multiplier
return (node_a.global_position * position_modifier).distance_to(
node_b.global_position * position_modifier
)
@ -134,15 +132,3 @@ static func uid_to_res_path(uid: Variant) -> Variant:
return paths
return null
static func create_box_mesh_from_aabb(aabb: AABB) -> BoxMesh:
var mesh := BoxMesh.new()
mesh.size = aabb.size
return mesh
static func load_config(config_path: String) -> ConfigFile:
var config := ConfigFile.new()
config.load(config_path)
return config

View File

@ -10,9 +10,6 @@ extends Node
#static var total_weak_vibration: float = 0.0
#static var total_strong_vibration: float = 0.0
#static var _vibration_handled: bool = false
static var min_weak_magnitude_threshold: float = 0.0
static var min_strong_magnitude_threshold: float = 0.0
static var min_duration_threshold: float = 0.0
## If [code]false[/code], calling [method vibrate] will not start a controller vibration.
@export var enabled: bool = true:
@ -34,6 +31,7 @@ static var min_duration_threshold: float = 0.0
#region Editor tooling
@warning_ignore_start("unused_private_class_variable")
@export var _editor_weak_vibration: bool = false
@export_tool_button("Test Vibration", "InputEventJoypadMotion")
var _editor_test_vibration: Callable = vibrate
@export_tool_button("Stop Vibration", "MissingNode")
@ -57,12 +55,25 @@ var _editor_stop_test_vibration: Callable = stop_vibration
get = get_magnitude_multiplier,
set = set_magnitude_multiplier
@export_group("Weak Controller Overrides", "weak_controller_")
@export var weak_controller_magnitude_multiplier: float = 2.0
@export var weak_controller_duration_multiplier: float = 1.0
var animated_weak_magnitude: float = 0.0:
set(value):
animated_weak_magnitude = clampf(value, 0.0, 1.0)
var animated_strong_magnitude: float = 0.0:
set(value):
animated_strong_magnitude = clampf(value, 0.0, 1.0)
var _ed_was_weak_vibration: bool = false
func _notification(what: int) -> void:
if what == NOTIFICATION_EDITOR_PRE_SAVE:
_ed_was_weak_vibration = _editor_weak_vibration
_editor_weak_vibration = false
elif what == NOTIFICATION_EDITOR_POST_SAVE:
_editor_weak_vibration = _ed_was_weak_vibration
func _process(delta: float) -> void:
@ -70,13 +81,9 @@ func _process(delta: float) -> void:
can_vibrate()
and not is_zero_approx(animated_weak_magnitude + animated_strong_magnitude)
):
var _weak_magnitude: float = animated_weak_magnitude
var _strong_magnitude: float = animated_strong_magnitude
_weak_magnitude = remap(_weak_magnitude, 0.0, 1.0, min_weak_magnitude_threshold * float(_weak_magnitude > 0.0), 1.0)
_strong_magnitude = remap(_strong_magnitude, 0.0, 1.0, min_strong_magnitude_threshold * float(_strong_magnitude > 0.0), 1.0)
Input.start_joy_vibration.call_deferred(device, _weak_magnitude, _strong_magnitude, delta)
Input.start_joy_vibration.call_deferred(
device, animated_weak_magnitude, animated_strong_magnitude, delta
)
animated_weak_magnitude = 0.0
animated_strong_magnitude = 0.0
@ -87,6 +94,13 @@ func vibrate() -> void:
if not can_vibrate():
return
var is_weak_controller: bool
#if Engine.is_editor_hint():
#is_weak_controller = _editor_weak_vibration
#else:
#is_weak_controller = SettingsManager.get_setting(&"controls", &"using_weak_controller")
if delay > 0.0:
await get_tree().create_timer(delay).timeout
@ -96,14 +110,16 @@ func vibrate() -> void:
#SPrint.print_msg("Audio Delay: %s" % (last_mix_time + output_latency))
await get_tree().create_timer(last_mix_time + output_latency).timeout
var _weak_magnitude: float = clampf(weak_magnitude * magnitude_multiplier, 0.0, 1.0)
var _strong_magnitude: float = clampf(strong_magnitude * magnitude_multiplier, 0.0, 1.0)
var _multiplier: float = magnitude_multiplier * (
weak_controller_magnitude_multiplier if is_weak_controller else 1.0
)
_weak_magnitude = remap(_weak_magnitude, 0.0, 1.0, min_weak_magnitude_threshold * float(_weak_magnitude > 0.0), 1.0)
_strong_magnitude = remap(_strong_magnitude, 0.0, 1.0, min_strong_magnitude_threshold * float(_strong_magnitude > 0.0), 1.0)
duration = maxf(duration, min_duration_threshold)
Input.start_joy_vibration(device, _weak_magnitude, _strong_magnitude, duration)
Input.start_joy_vibration(
device,
clampf(weak_magnitude * _multiplier, 0.0, 1.0),
clampf(strong_magnitude * _multiplier, 0.0, 1.0),
duration * (weak_controller_duration_multiplier if is_weak_controller else 1.0)
)
#started_vibration.emit()

View File

@ -1,12 +0,0 @@
[gd_resource type="ShaderMaterial" format=3 uid="uid://dag4adrscn4b7"]
[ext_resource type="Shader" uid="uid://b3d0qp0pjfhmk" path="res://tools/debug_outline.gdshader" id="1_1ceck"]
[resource]
render_priority = 0
shader = ExtResource("1_1ceck")
shader_parameter/base_color = Color(0.3, 1, 0.3, 0.15)
shader_parameter/edge_color = Color(1, 1, 0.3, 0.8)
shader_parameter/edge_power = 3.0
shader_parameter/box_center = Vector3(0, 0, 0)
shader_parameter/box_extents = Vector3(0, 0, 0)

View File

@ -1,10 +0,0 @@
[gd_resource type="ShaderMaterial" format=3 uid="uid://c8jcuss805aie"]
[ext_resource type="Shader" uid="uid://b3d0qp0pjfhmk" path="res://tools/debug_outline.gdshader" id="1_ibo8j"]
[resource]
render_priority = 0
shader = ExtResource("1_ibo8j")
shader_parameter/base_color = Color(0.29803923, 1, 0.29803923, 0.019607844)
shader_parameter/edge_color = Color(1, 1, 0.49803922, 0.06666667)
shader_parameter/edge_power = 2.0

View File

@ -34,7 +34,6 @@ signal loading_finished
#for aabb: AABB in load_aabbs:
#add_gizmo.call_deferred(AABBGizmo.new(aabb, self))
@export_tool_button("Auto Generate AABB", "CSGBox3D") var editor_auto_gen_aabb: Callable = _editor_auto_gen_aabb
@export var editor_visualize_aabbs_with_box: bool = true: set = _editor_set_visualize_aabbs_with_box
var loaded_level: Node
var is_loading_level: bool = false
@ -141,13 +140,6 @@ func precompute_aabb() -> void:
for aabb: AABB in _precomputed_aabbs:
_precomputed_aabb = _precomputed_aabb.merge(aabb)
if Engine.is_editor_hint() and editor_visualize_aabbs_with_box:
setup_debug_meshes()
func get_precomputed_aabbs() -> Array[AABB]:
return _precomputed_aabbs
func load_level() -> void:
_unload_time_left = unload_delay
@ -208,36 +200,6 @@ func is_position_in_bounds(point: Vector3) -> bool:
return false
func setup_debug_meshes(debug_material: Material = load("uid://c8jcuss805aie")) -> void:
var children: Array[Node] = find_children("_DEBUG_VISIBILITY*", "MeshInstance3D", false, false)
if not children.is_empty():
for child: Node in children:
child.free()
var aabbs: Array[AABB] = get_precomputed_aabbs()
for aabb_index: int in range(aabbs.size()):
var aabb: AABB = aabbs[aabb_index]
var mesh: BoxMesh = Utils.create_box_mesh_from_aabb(aabb)
var mesh_instance := MeshInstance3D.new()
mesh_instance.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
mesh_instance.mesh = mesh
mesh_instance.material_override = debug_material
mesh_instance.name = "_DEBUG_VISIBILITY" + str(aabb_index)
add_child(mesh_instance)
mesh_instance.position = to_local(aabb.position + aabb.size / 2.0)
mesh_instance.set_instance_shader_parameter(&"box_extents", aabb.size * 0.5)
mesh_instance.set_instance_shader_parameter(&"box_center", mesh_instance.global_position)
func clear_debug_meshes() -> void:
var children: Array[Node] = find_children("_DEBUG_VISIBILITY*", "MeshInstance3D", false, false)
for child: Node in children:
child.free()
func _draw_debug() -> void:
for aabb: AABB in _precomputed_aabbs:
Game.debug_draw("aabb", [aabb, Color.LAWN_GREEN])
@ -281,11 +243,3 @@ func _editor_auto_gen_aabb() -> void:
ud.add_do_property(self, &"load_aabbs", load_aabbs)
ud.add_undo_property(self, &"load_aabbs", previous_aabbs)
ud.commit_action()
func _editor_set_visualize_aabbs_with_box(value: bool) -> void:
editor_visualize_aabbs_with_box = value
if value:
setup_debug_meshes()
else:
clear_debug_meshes()

View File

@ -5,12 +5,11 @@ signal loaded
signal change_world_requested(new_world_path: String, save_previous: bool, load_from_save: bool)
signal world_unload_requested(do_save: bool)
@export var level_streamers: Array[LevelStreamer] = []
@export var level_streamers: Array[LevelStreamer]
## Reference to the player.
## Should probably have a [method apply_orientation] and [method get_orientation] function,
## since the return value get's put into [member WorldState.player_transform].
@export var player: Node3D
@export_group("Debug", "debug_")
## Only applies when this world is the game's current scene.
@export_custom(PROPERTY_HINT_GROUP_ENABLE, "debug_") var debug_enabled: bool = false
@ -24,10 +23,6 @@ signal world_unload_requested(do_save: bool)
var world_state: WorldState
func _exit_tree() -> void:
LimboConsole.unregister_command("toggle_level_streamer_visibility")
func _ready() -> void:
if debug_enabled:
if get_tree().current_scene == self:
@ -41,8 +36,6 @@ func _ready() -> void:
initialize_level_streamers()
initialize_world_proxies()
register_commands()
loaded.emit()
@ -155,36 +148,6 @@ func get_instance_state(key: String, default: Variant) -> Variant:
return world_state.instance_data.get(key, default)
func register_commands() -> void:
var toggle_streamer_visibility: Callable = func() -> void:
var center_marker_mat := StandardMaterial3D.new()
center_marker_mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
center_marker_mat.no_depth_test = true
center_marker_mat.depth_draw_mode = BaseMaterial3D.DEPTH_DRAW_DISABLED
center_marker_mat.disable_ambient_light = true
center_marker_mat.disable_fog = true
center_marker_mat.disable_specular_occlusion = true
center_marker_mat.disable_receive_shadows = true
for streamer: LevelStreamer in level_streamers:
var children: Array[Node] = streamer.find_children("_DEBUG_VISIBILITY*", "MeshInstance3D", false, false)
if children.is_empty():
streamer.setup_debug_meshes(load("uid://dag4adrscn4b7"))
var center_marker := MeshInstance3D.new()
center_marker.mesh = SphereMesh.new()
center_marker.scale *= 0.2
center_marker.name = "_DEBUG_VISIBILITY_CMARKER" + streamer.level_id
center_marker.material_override = center_marker_mat
streamer.add_child(center_marker)
else:
for child: Node in children:
child.queue_free()
LimboConsole.register_command(toggle_streamer_visibility, "toggle_level_streamer_visibility")
func _load_levels_player_is_in() -> void:
if not is_instance_valid(player):
return

View File

@ -34,7 +34,6 @@ static func debug_draw(draw_shape: String, args: Array) -> void:
func _ready() -> void:
if not Engine.is_editor_hint():
load_main_menu()
register_commands()
func load_game(save_data: SaveData, save_slot: int) -> void:
@ -94,6 +93,7 @@ func unload_world(do_save: bool = true) -> void:
save_world_state()
world.process_mode = Node.PROCESS_MODE_DISABLED
await loading_screen.fade_in()
world.queue_free()
@ -142,27 +142,12 @@ func quit_game() -> void:
get_tree().quit()
func register_commands() -> void:
var load_save_func: Callable = func(save_index: int) -> void:
var path: String = MainMenu.get_save_path(save_index)
if ResourceLoader.exists(path):
load_game(load(path), save_index)
LimboConsole.register_command(load_save_func, "load_save", "Loads the save with the given save slot index.")
LimboConsole.register_command(save_game, "save_game", "Saves the game based on the current save slot.")
func _on_unload_world_request(do_save: bool) -> void:
await unload_world(do_save)
if do_save:
save_game()
if is_instance_valid(world):
world.process_mode = Node.PROCESS_MODE_DISABLED
await loading_screen.fade_in()
unload_world(do_save)
await load_main_menu()
loading_screen.fade_out()

View File

@ -10,7 +10,5 @@ metadata/_custom_type_script = "uid://cl1u038dbrou2"
[node name="WorldContainer" type="Node" parent="." unique_id=1835125942]
unique_name_in_owner = true
[node name="CanvasLayer" type="CanvasLayer" parent="." unique_id=919699501]
[node name="LoadingScreen" parent="CanvasLayer" unique_id=1509462056 instance=ExtResource("2_3msxb")]
[node name="LoadingScreen" parent="." unique_id=1509462056 instance=ExtResource("2_3msxb")]
unique_name_in_owner = true

View File

@ -31,8 +31,6 @@ var has_control: bool = true
var can_run: bool = true
var is_crouching: bool = false
var crouch_speed_affection: float = 0.0
var _noclipping: bool = false
var _flying: bool = false
@onready var head: PlayerHead = %Head
@onready var collision_shape: CollisionShape3D = %CylinderCollider
@ -45,19 +43,12 @@ func _init() -> void:
player = self
func _exit_tree() -> void:
LimboConsole.unregister_command("player_fly")
LimboConsole.unregister_command("player_noclip")
func _ready() -> void:
head.rotation.y = global_rotation.y
global_rotation.y = 0.0
flashlight.manager = flashlight_manager
_register_commands()
func _physics_process(delta: float) -> void:
#var lagspike: bool = randf() < 0.005
@ -179,66 +170,3 @@ func handle_crouching(delta: float) -> void:
"Crouching Speed Affection: %s\nIs Crouching: %s" % [crouch_speed_affection, is_crouching],
true
)
func _register_commands() -> void:
var noclip_func: Callable = func() -> void:
const _NOCLIP_SPEED: float = 15.0
_flying = not _flying
if _flying and _noclipping:
_flying = false
while _flying and is_inside_tree():
if InputManager.is_window_focused():
set_physics_process(false)
var input_dir: Vector3 = get_input_direction()
movement_direction = transform.basis * Vector3(input_dir.x, 1.0, input_dir.z)
movement_direction = (movement_direction * Utils.VEC3_HOR).normalized()
movement_direction = movement_direction.rotated(Vector3.UP, head.global_rotation.y)
if Input.is_action_pressed(ACTION_JUMP):
movement_direction.y = 1.0 * _NOCLIP_SPEED
elif Input.is_action_pressed(ACTION_CROUCH):
movement_direction.y = -1.0 * _NOCLIP_SPEED
speed = _NOCLIP_SPEED
move(movement_direction, get_physics_process_delta_time())
velocity.y = movement_direction.y
global_position += velocity * get_physics_process_delta_time()
await get_tree().process_frame
set_physics_process(true)
var fly_func: Callable = func() -> void:
const _FLY_SPEED: float = 15.0
_flying = not _flying
if _noclipping and _flying:
_noclipping = false
while _flying and is_inside_tree():
if InputManager.is_window_focused():
set_physics_process(false)
var input_dir: Vector3 = get_input_direction()
movement_direction = transform.basis * Vector3(input_dir.x, 1.0, input_dir.z)
movement_direction = (movement_direction * Utils.VEC3_HOR).normalized()
movement_direction = movement_direction.rotated(Vector3.UP, head.global_rotation.y)
if Input.is_action_pressed(ACTION_JUMP):
movement_direction.y = 1.0 * _FLY_SPEED
elif Input.is_action_pressed(ACTION_CROUCH):
movement_direction.y = -1.0 * _FLY_SPEED
speed = _FLY_SPEED
move(movement_direction, get_physics_process_delta_time())
velocity.y = movement_direction.y
move_and_slide()
await get_tree().process_frame
set_physics_process(true)
LimboConsole.register_command(noclip_func, "player_noclip")
LimboConsole.register_command(fly_func, "player_fly")

View File

@ -1,65 +0,0 @@
@tool
class_name Stagelight
extends Node3D
@export var light_color := Color.WHITE: set = set_light_color
@export var lightbeam_material: ShaderMaterial: set = set_lightbeam_material
@export var light_surface_material: ShaderMaterial: set = set_light_surface_material
@export var phase_multiplier: float = 0.5: set = set_phase_multiplier
@export var phase_offset: float = 0.0: set = set_phase_offset
@onready var spot_light: SpotLight3D = %SpotLight
@onready var rhythm_property_setter: RhythmPropertySetter = %RhythmPropertySetter
@onready var lightbeam: MeshInstance3D = %Lightbeam
@onready var cylinder: MeshInstance3D = $StudioSpotLight/Cylinder
func _ready() -> void:
set_light_color(light_color)
set_lightbeam_material(lightbeam_material)
set_light_surface_material(light_surface_material)
func set_light_color(color: Color) -> void:
light_color = color
if not is_node_ready():
await ready
spot_light.light_color = light_color
func set_lightbeam_material(shader_material: ShaderMaterial) -> void:
lightbeam_material = shader_material
if not is_node_ready():
await ready
lightbeam.material_override = lightbeam_material
func set_light_surface_material(shader_material: ShaderMaterial) -> void:
light_surface_material = shader_material
if not is_node_ready():
await ready
cylinder.set_surface_override_material(1, light_surface_material)
func set_phase_multiplier(multiplier: float) -> void:
phase_multiplier = multiplier
if not is_node_ready():
await ready
rhythm_property_setter.phase_multiplier = phase_multiplier
func set_phase_offset(offset: float) -> void:
phase_offset = offset
if not is_node_ready():
await ready
rhythm_property_setter.phase_shift = phase_offset

View File

@ -1 +0,0 @@
uid://cmh1y78o47f06

View File

@ -1,98 +0,0 @@
[gd_scene format=3 uid="uid://bc84qk6pfgctg"]
[ext_resource type="PackedScene" uid="uid://pvll6bliout1" path="res://assets/models/electrics/spot_light/studio_spot_light.glb" id="1_3cilv"]
[ext_resource type="Script" uid="uid://cmh1y78o47f06" path="res://src/gameplay/objects/stagelight/stagelight.gd" id="1_wce40"]
[ext_resource type="Shader" uid="uid://b46o5a45g58xb" path="res://assets/shaders/rhythm/beating_surface.gdshader" id="2_wce40"]
[ext_resource type="PackedScene" uid="uid://bwejp67rw5erj" path="res://assets/models/electrics/spot_light/studio_spot_light_framing_01.glb" id="3_otype"]
[ext_resource type="Shader" uid="uid://c32arbbdbo7m8" path="res://assets/shaders/rhythm/beating_lightbeam.gdshader" id="4_d6ag8"]
[ext_resource type="Script" uid="uid://bvmfdeypbeqsn" path="res://src/core/rhythm/rhythm_property_setter.gd" id="5_uce1x"]
[sub_resource type="Gradient" id="Gradient_wj31t"]
interpolation_mode = 2
offsets = PackedFloat32Array(0, 0.38108107, 0.5054054, 0.7162162)
colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 0.44602686, 1, 1, 1, 0.08750595, 1, 1, 1, 0)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_du0pf"]
gradient = SubResource("Gradient_wj31t")
width = 1
fill_to = Vector2(0, 0.48931623)
[sub_resource type="ShaderMaterial" id="ShaderMaterial_xn83m"]
render_priority = 0
shader = ExtResource("4_d6ag8")
shader_parameter/phase_offset = 0.0
shader_parameter/phase_multiplier = 0.5
shader_parameter/color = Color(1, 1, 1, 1)
shader_parameter/alpha_multiplier = 0.25
shader_parameter/albedo = SubResource("GradientTexture2D_du0pf")
shader_parameter/flash_exponent = 4.0
[sub_resource type="Gradient" id="Gradient_vtuqv"]
interpolation_mode = 1
offsets = PackedFloat32Array(1)
colors = PackedColorArray(1, 1, 1, 1)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_glp4g"]
gradient = SubResource("Gradient_vtuqv")
width = 1
height = 1
[sub_resource type="ShaderMaterial" id="ShaderMaterial_nqu5b"]
render_priority = 0
shader = ExtResource("2_wce40")
shader_parameter/phase_offset = 0.0
shader_parameter/phase_multiplier = 0.5
shader_parameter/albedo = SubResource("GradientTexture2D_glp4g")
shader_parameter/flash_exponent = 4.0
shader_parameter/intensity_multiplier = 8.0
[sub_resource type="CylinderMesh" id="CylinderMesh_787dp"]
top_radius = 0.275
bottom_radius = 2.0
height = 12.0
cap_top = false
cap_bottom = false
[node name="Stagelight" type="Node3D" unique_id=729680764]
script = ExtResource("1_wce40")
lightbeam_material = SubResource("ShaderMaterial_xn83m")
light_surface_material = SubResource("ShaderMaterial_nqu5b")
[node name="StudioSpotLight" parent="." unique_id=1407009402 instance=ExtResource("1_3cilv")]
[node name="Cylinder" parent="StudioSpotLight" index="0" unique_id=1873656815]
surface_material_override/1 = SubResource("ShaderMaterial_nqu5b")
[node name="StudioSpotLightFraming01" parent="." unique_id=166030914 instance=ExtResource("3_otype")]
[node name="LightbeamRoot" type="Node3D" parent="." unique_id=1922157293]
transform = Transform3D(-4.371139e-08, -1, 0, 1, -4.371139e-08, 0, 0, 0, 1, 0.2, 8.742278e-09, 0)
[node name="Lightbeam" type="MeshInstance3D" parent="LightbeamRoot" unique_id=1840014799]
unique_name_in_owner = true
transform = Transform3D(0.5, 0, 0, 0, 0.49999994, 0, 0, 0, 0.5, 0, -2.9999995, 0)
material_override = SubResource("ShaderMaterial_xn83m")
cast_shadow = 0
instance_shader_parameters/instance_phase_offset = 0.0
mesh = SubResource("CylinderMesh_787dp")
[node name="SpotLight" type="SpotLight3D" parent="LightbeamRoot" unique_id=1841045966]
unique_name_in_owner = true
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0, 0.8, 0)
spot_range = 50.0
spot_attenuation = 1.38
spot_angle = 8.75
spot_angle_attenuation = 0.23325838
[node name="RhythmPropertySetter" type="Node" parent="LightbeamRoot" unique_id=1355223878 node_paths=PackedStringArray("nodes")]
unique_name_in_owner = true
script = ExtResource("5_uce1x")
nodes = [NodePath("../SpotLight")]
beat_property = &"light_energy"
phase_multiplier = 0.5
beat_range_min = 10.0
beat_range_max = 0.0
beat_exponent = 4.0
metadata/_custom_type_script = "uid://bvmfdeypbeqsn"
[editable path="StudioSpotLight"]

View File

@ -31,14 +31,6 @@ func _enter_tree() -> void:
_world_proxy = WorldProxy.new()
add_child(_world_proxy)
LimboConsole.register_command(refill_power, "flashlight_refill", "Fill up the flashlight's power by this amount.")
LimboConsole.register_command(drain_power, "flashlight_drain", "Drain the flashlight's power by this amount.")
func _exit_tree() -> void:
LimboConsole.unregister_command(refill_power)
LimboConsole.unregister_command(drain_power)
func _ready() -> void:
world = await _world_proxy.wait_for_world()

View File

@ -23,7 +23,7 @@ func _ready() -> void:
mouse_filter = Control.MOUSE_FILTER_IGNORE
func fade_in(duration: float = fade_in_duration) -> void:
func fade_in() -> void:
color_rect.show()
set_process(true)
fading = true
@ -33,13 +33,13 @@ func fade_in(duration: float = fade_in_duration) -> void:
_tween.kill()
_tween = create_tween()
_tween.tween_property(color_rect, ^"color:a", 1.0, duration)
_tween.tween_property(color_rect, ^"color:a", 1.0, fade_in_duration)
await _tween.finished
set_loading_hints_visible(true)
func fade_out(duration: float = fade_out_duration) -> void:
func fade_out() -> void:
set_loading_hints_visible(false)
mouse_filter = Control.MOUSE_FILTER_IGNORE
fading = false
@ -48,7 +48,7 @@ func fade_out(duration: float = fade_out_duration) -> void:
_tween.kill()
_tween = create_tween()
_tween.tween_property(color_rect, ^"color:a", 0.0, duration)
_tween.tween_property(color_rect, ^"color:a", 0.0, fade_out_duration)
await _tween.finished
color_rect.hide()

View File

@ -11,12 +11,10 @@ const SAVE_DATA_PATH: String = SAVES_DIR + "%s/save_data.tres"
@onready var continue_button: Button = %ContinueButton
@onready var load_button: Button = %LoadButton
@onready var new_game_button: Button = %NewGameButton
@onready var options_button: Button = %OptionsButton
@onready var quit_button: Button = %QuitButton
@onready var saves_scroll_container: ScrollContainer = %SavesScrollContainer
@onready var saves_container: VBoxContainer = %SavesContainer
@onready var settings_menu: SettingsMenu = %SettingsMenu
static func get_save_slots() -> PackedInt32Array:
@ -54,17 +52,15 @@ func _ready() -> void:
continue_button.pressed.connect(_on_continue_pressed)
load_button.pressed.connect(_on_load_pressed)
new_game_button.pressed.connect(_on_new_game_pressed)
options_button.pressed.connect(_on_options_pressed)
quit_button.pressed.connect(_on_quit_pressed)
settings_menu.close_request.connect(_on_settings_close_request)
continue_button.visible = has_save()
func has_save() -> bool:
var config: ConfigFile = Utils.load_config(USER_SETTINGS_PATH)
return config.has_section_key("save", "last_slot")
var config := ConfigFile.new()
config.load(USER_SETTINGS_PATH)
return config.has_section_key("game", "last_slot")
func populate_save_entries() -> void:
@ -78,8 +74,9 @@ func populate_save_entries() -> void:
func _on_continue_pressed() -> void:
var config: ConfigFile = Utils.load_config(USER_SETTINGS_PATH)
var slot: int = config.get_value("save", "last_slot", 0)
var config := ConfigFile.new()
config.load(USER_SETTINGS_PATH)
var slot: int = config.get_value("game", "last_slot", 0)
var path: String = get_save_path(slot)
if not FileAccess.file_exists(path):
@ -89,7 +86,7 @@ func _on_continue_pressed() -> void:
_on_new_game_pressed()
return
config.set_value("save", "last_slot", slot)
config.set_value("game", "last_slot", slot)
config.save(USER_SETTINGS_PATH)
var save_data: SaveData = load(path)
@ -106,22 +103,20 @@ func _on_new_game_pressed() -> void:
var slot_index: int = get_unique_save_slot_index()
load_game_request.emit(save_data, slot_index)
var config: ConfigFile = Utils.load_config(USER_SETTINGS_PATH)
config.set_value("save", "last_slot", slot_index)
var config := ConfigFile.new()
config.load(USER_SETTINGS_PATH)
config.set_value("game", "last_slot", slot_index)
config.save(USER_SETTINGS_PATH)
func _on_options_pressed() -> void:
settings_menu.show()
func _on_quit_pressed() -> void:
quit_request.emit()
func _on_save_entry_pressed(save_slot: int) -> void:
var config: ConfigFile = Utils.load_config(USER_SETTINGS_PATH)
config.set_value("save", "last_slot", save_slot)
var config := ConfigFile.new()
config.load(USER_SETTINGS_PATH)
config.set_value("game", "last_slot", save_slot)
config.save(USER_SETTINGS_PATH)
var save_path: String = get_save_path(save_slot)
@ -132,7 +127,3 @@ func _on_save_entry_pressed(save_slot: int) -> void:
var save_data: SaveData = load(save_path)
load_game_request.emit(save_data, save_slot)
func _on_settings_close_request() -> void:
settings_menu.hide()

View File

@ -2,7 +2,6 @@
[ext_resource type="Script" uid="uid://ckaouf136x7rh" path="res://src/ui/main_menu/main_menu.gd" id="1_2hyyg"]
[ext_resource type="Material" uid="uid://c38215ysnknyk" path="res://assets/dev/dark/dark_01.tres" id="2_4ux21"]
[ext_resource type="PackedScene" uid="uid://cxm2uc8fw031f" path="res://src/ui/settings_menu/settings_menu.tscn" id="2_osdni"]
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_4ux21"]
sky_horizon_color = Color(0.66224277, 0.6717428, 0.6867428, 1)
@ -68,11 +67,6 @@ unique_name_in_owner = true
layout_mode = 2
text = "NEW_GAME"
[node name="OptionsButton" type="Button" parent="MainButtons" unique_id=666018363]
unique_name_in_owner = true
layout_mode = 2
text = "SETTINGS"
[node name="QuitButton" type="Button" parent="MainButtons" unique_id=851953121]
unique_name_in_owner = true
layout_mode = 2
@ -117,22 +111,6 @@ text = "Save 1"
layout_mode = 2
text = "Save 1"
[node name="SettingsMenu" parent="." unique_id=8078639 instance=ExtResource("2_osdni")]
unique_name_in_owner = true
visible = false
layout_mode = 1
[node name="ColorRect" type="ColorRect" parent="SettingsMenu" unique_id=1002221363]
show_behind_parent = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
color = Color(0, 0, 0, 0.6901961)
[node name="Background" type="Node3D" parent="." unique_id=877986421]
[node name="WorldEnvironment" type="WorldEnvironment" parent="Background" unique_id=3797920]

View File

@ -5,25 +5,16 @@ extends Control
@export var world: World
@onready var continue_button: Button = %ContinueButton
@onready var settings_button: Button = %SettingsButton
@onready var main_menu_button: Button = %MainMenuButton
@onready var settings_menu: SettingsMenu = %SettingsMenu
func _ready() -> void:
continue_button.pressed.connect(set_paused.bind(false))
settings_button.pressed.connect(_on_settings_button_pressed)
main_menu_button.pressed.connect(_on_main_menu_button_pressed)
settings_menu.close_request.connect(_on_settings_menu_close_requested)
func _input(event: InputEvent) -> void:
if event.is_action_pressed(&"pause"):
if settings_menu.is_visible_in_tree():
settings_menu.close()
else:
toggle_pause()
@ -40,13 +31,5 @@ func toggle_pause() -> void:
set_paused(not is_paused())
func _on_settings_button_pressed() -> void:
settings_menu.show()
func _on_settings_menu_close_requested() -> void:
settings_menu.hide()
func _on_main_menu_button_pressed() -> void:
world.request_world_unload()

View File

@ -1,7 +1,6 @@
[gd_scene format=3 uid="uid://yuthv3c7rx8"]
[ext_resource type="Script" uid="uid://brd2041bv6vdk" path="res://src/ui/pause_menu/pause_menu.gd" id="1_fsbbv"]
[ext_resource type="PackedScene" uid="uid://cxm2uc8fw031f" path="res://src/ui/settings_menu/settings_menu.tscn" id="2_u4p1g"]
[node name="PauseMenu" type="Control" unique_id=90120455]
process_mode = 3
@ -14,15 +13,6 @@ grow_vertical = 2
script = ExtResource("1_fsbbv")
metadata/_custom_type_script = "uid://brd2041bv6vdk"
[node name="ColorRect" type="ColorRect" parent="." unique_id=1353751197]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0, 0, 0, 0.6901961)
[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=905531263]
layout_mode = 1
anchors_preset = 8
@ -42,28 +32,7 @@ unique_name_in_owner = true
layout_mode = 2
text = "Continue"
[node name="SettingsButton" type="Button" parent="VBoxContainer" unique_id=1154942305]
unique_name_in_owner = true
layout_mode = 2
text = "Options"
[node name="MainMenuButton" type="Button" parent="VBoxContainer" unique_id=2079918606]
unique_name_in_owner = true
layout_mode = 2
text = "Main Menu"
[node name="SettingsMenu" parent="." unique_id=8078639 instance=ExtResource("2_u4p1g")]
unique_name_in_owner = true
visible = false
layout_mode = 1
[node name="ColorRect" type="ColorRect" parent="SettingsMenu" unique_id=660173424]
show_behind_parent = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
color = Color(0, 0, 0, 0.6901961)

View File

@ -1,190 +0,0 @@
class_name SettingsMenu
extends Control
signal close_request
var _previously_focused_control: Control
@onready var back_button: Button = %BackButton
# Confirmation
@onready var revert_timer: Timer = %RevertTimer
@onready var commit_settings: Button = %CommitSettings
@onready var revert_settings: Button = %RevertSettings
@onready var confirmation_dialog: Control = %ConfirmationDialog
@onready var time_left_label: Label = %TimeLeftLabel
# Gameplay
@onready var headbobbing: CheckButton = %Headbobbing
@onready var headbobbing_slider: HSlider = %HeadbobbingSlider
# Localization
@onready var language: OptionButton = %Language
# Video Settings
@onready var window_mode: OptionButton = %WindowMode
# Audio Settings
@onready var rhythm_delay: Button = %RhythmDelay
@onready var master_slider: HSlider = %MasterSlider
@onready var sfx_slider: HSlider = %SFXSlider
@onready var music_slider: HSlider = %MusicSlider
@onready var ambience_slider: HSlider = %AmbienceSlider
@onready var rhythm_setup_menu: RhythmSetupMenu = $RhythmSetupMenu
func _ready() -> void:
update_controls_from_settings()
back_button.pressed.connect(close_request.emit)
# Confirmation
revert_timer.timeout.connect(_on_revert_timer_timeout)
commit_settings.pressed.connect(_on_commit_settings_pressed)
revert_settings.pressed.connect(_on_revert_settings_pressed)
# Gameplay
headbobbing.toggled.connect(_on_headbobbing_toggled)
headbobbing_slider.value_changed.connect(_on_headbobbing_intensity_value_changed)
# Localization
language.item_selected.connect(_on_language_selected)
# Video settings
window_mode.item_selected.connect(_on_window_mode_item_selected)
# Audio Settings
rhythm_delay.pressed.connect(_on_configure_rhythm_delay_pressed)
rhythm_setup_menu.confirmed.connect(_on_configure_rhythm_confirmed)
master_slider.value_changed.connect(_on_volume_slider_changed.bind(&"Master"))
sfx_slider.value_changed.connect(_on_volume_slider_changed.bind(&"SFX"))
music_slider.value_changed.connect(_on_volume_slider_changed.bind(&"Music"))
ambience_slider.value_changed.connect(_on_volume_slider_changed.bind(&"Ambient"))
visibility_changed.connect(func() -> void: if is_visible_in_tree(): update_controls_from_settings())
set_process(false)
func _process(_delta: float) -> void:
if not revert_timer.is_stopped():
var second: int = ceili(revert_timer.time_left)
time_left_label.text = tr_n("SETTINGS_REVERT_TIME_LEFT", "", second) % second
func close() -> void:
if rhythm_setup_menu.is_visible_in_tree():
rhythm_setup_menu.close()
else:
close_request.emit()
func update_controls_from_settings() -> void:
# Gameplay
headbobbing.set_pressed_no_signal(SettingsHandler.get_setting("gameplay", "headbobbing", true))
headbobbing_slider.set_value_no_signal(SettingsHandler.get_setting("gameplay", "headbobbing_multiplier", 1.0))
# Localization
match SettingsHandler.get_setting("localization", "language", "en_US"):
"en_US":
language.select(0)
"de_DE":
language.select(1)
"es_ES":
language.select(2)
"ja_JA":
language.select(3)
# Video
match SettingsHandler.get_setting("video", "window_mode", 3):
3:
window_mode.select(0)
4:
window_mode.select(1)
0:
window_mode.select(2)
# Audio
master_slider.set_value_no_signal(SettingsHandler.get_setting("audio", "Master", 0.85))
sfx_slider.set_value_no_signal(SettingsHandler.get_setting("audio", "SFX", 1.0))
music_slider.set_value_no_signal(SettingsHandler.get_setting("audio", "Music", 1.0))
ambience_slider.set_value_no_signal(SettingsHandler.get_setting("audio", "Ambient", 1.0))
func ask_for_confirmation() -> void:
_previously_focused_control = get_viewport().gui_get_focus_owner()
confirmation_dialog.show()
revert_settings.grab_focus()
revert_timer.start()
set_process(true)
func _on_commit_settings_pressed() -> void:
SettingsHandler.commit_settings()
SettingsHandler.save_settings()
revert_timer.stop()
confirmation_dialog.hide()
set_process(false)
if is_instance_valid(_previously_focused_control):
_previously_focused_control.grab_focus()
func _on_revert_settings_pressed() -> void:
revert_timer.stop()
_on_revert_timer_timeout()
func _on_revert_timer_timeout() -> void:
SettingsHandler.revert_settings()
confirmation_dialog.hide()
update_controls_from_settings()
set_process(false)
if is_instance_valid(_previously_focused_control):
_previously_focused_control.grab_focus()
func _on_headbobbing_toggled(toggled_on: bool) -> void:
SettingsHandler.set_setting("gameplay", "headbobbing", toggled_on)
func _on_headbobbing_intensity_value_changed(value: float) -> void:
SettingsHandler.set_setting("gameplay", "headbobbing_multiplier", value)
func _on_language_selected(index: int) -> void:
var language_string: String = "en_US"
match index:
0:
language_string = "en_US"
1:
language_string = "de_DE"
2:
language_string = "es_ES"
3:
language_string = "ja_JA"
SettingsHandler.set_setting("localization", "language", language_string, false)
SettingsHandler.apply_settings()
ask_for_confirmation()
func _on_window_mode_item_selected(index: int) -> void:
var mode: DisplayServer.WindowMode
match index:
0:
mode = DisplayServer.WindowMode.WINDOW_MODE_FULLSCREEN
1:
mode = DisplayServer.WindowMode.WINDOW_MODE_EXCLUSIVE_FULLSCREEN
2:
mode = DisplayServer.WindowMode.WINDOW_MODE_WINDOWED
_:
mode = DisplayServer.WindowMode.WINDOW_MODE_FULLSCREEN
SettingsHandler.set_setting("video", "window_mode", mode, false)
SettingsHandler.apply_settings()
ask_for_confirmation()
func _on_configure_rhythm_delay_pressed() -> void:
rhythm_setup_menu.show()
func _on_configure_rhythm_confirmed() -> void:
rhythm_setup_menu.hide()
func _on_volume_slider_changed(value: float, bus: StringName) -> void:
SettingsHandler.set_setting("audio", bus, value)

View File

@ -1 +0,0 @@
uid://cll4odq5p7iuv

View File

@ -1,414 +0,0 @@
[gd_scene format=3 uid="uid://cxm2uc8fw031f"]
[ext_resource type="Script" uid="uid://cll4odq5p7iuv" path="res://src/ui/settings_menu/settings_menu.gd" id="1_3iv5y"]
[ext_resource type="Script" uid="uid://bfpr421kg4s" path="res://src/ui/settings_menu/slider_label.gd" id="2_axtgf"]
[ext_resource type="PackedScene" uid="uid://b41bjano4bw6s" path="res://src/ui/setup/rhythm/rhythm_setup_menu.tscn" id="3_kktd5"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_axtgf"]
content_margin_left = 0.0
content_margin_top = 0.0
content_margin_right = 0.0
content_margin_bottom = 0.0
bg_color = Color(0.1, 0.1, 0.1, 0.6)
border_width_left = 4
border_width_top = 4
border_width_right = 4
border_width_bottom = 4
corner_radius_top_left = 3
corner_radius_top_right = 3
corner_radius_bottom_right = 3
corner_radius_bottom_left = 3
corner_detail = 5
[node name="SettingsMenu" type="Control" unique_id=8078639]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_3iv5y")
metadata/_custom_type_script = "uid://cll4odq5p7iuv"
[node name="PanelContainer" type="PanelContainer" parent="." unique_id=747218901]
layout_mode = 1
anchors_preset = -1
anchor_left = 0.5
anchor_top = 0.05
anchor_right = 0.5
anchor_bottom = 0.95
offset_left = -200.0
offset_right = 200.0
grow_horizontal = 2
grow_vertical = 2
[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer" unique_id=481159029]
layout_mode = 2
[node name="ScrollContainer" type="ScrollContainer" parent="PanelContainer/VBoxContainer" unique_id=1719442009]
layout_mode = 2
size_flags_vertical = 3
follow_focus = true
[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/VBoxContainer/ScrollContainer" unique_id=1482670452]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/margin_left = 12
theme_override_constants/margin_top = 12
theme_override_constants/margin_right = 12
theme_override_constants/margin_bottom = 12
[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer" unique_id=1149761963]
layout_mode = 2
[node name="GameplaySettings" type="Control" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer" unique_id=2072631132]
layout_mode = 2
[node name="GameplayLabel" type="Label" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer" unique_id=1726505815]
layout_mode = 2
theme_override_font_sizes/font_size = 32
text = "Gameplay"
horizontal_alignment = 1
[node name="HSeparator" type="HSeparator" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer" unique_id=136767090]
layout_mode = 2
[node name="Headbobbing" type="CheckButton" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer" unique_id=1121847591]
unique_name_in_owner = true
layout_mode = 2
button_pressed = true
text = "Headbobbing"
[node name="HeadbobbingLabel" type="Label" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer" unique_id=1435927865]
layout_mode = 2
text = "Headbobbing-Intensity"
[node name="HeadbobbingMultiplier" type="HBoxContainer" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer" unique_id=883105611]
layout_mode = 2
[node name="HeadbobbingSlider" type="HSlider" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer/HeadbobbingMultiplier" unique_id=1792577413]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
max_value = 1.0
step = 0.01
value = 1.0
tick_count = 11
ticks_on_borders = true
[node name="SliderLabel" type="Label" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer/HeadbobbingMultiplier" unique_id=790300279 node_paths=PackedStringArray("slider")]
custom_minimum_size = Vector2(32, 0)
layout_mode = 2
text = "1.00"
horizontal_alignment = 2
script = ExtResource("2_axtgf")
slider = NodePath("../HeadbobbingSlider")
[node name="LocalizationSettings" type="Control" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer" unique_id=1387881590]
layout_mode = 2
[node name="LocalizationLabel" type="Label" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer" unique_id=1225964665]
layout_mode = 2
theme_override_font_sizes/font_size = 32
text = "Localization"
horizontal_alignment = 1
[node name="HSeparator2" type="HSeparator" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer" unique_id=282328287]
layout_mode = 2
[node name="Language" type="OptionButton" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer" unique_id=210620308]
unique_name_in_owner = true
layout_mode = 2
flat = true
selected = 0
item_count = 4
popup/item_0/text = "English"
popup/item_0/id = 0
popup/item_1/text = "German"
popup/item_1/id = 1
popup/item_2/text = "Spanish"
popup/item_2/id = 2
popup/item_3/text = "Japanese"
popup/item_3/id = 3
[node name="VideoSettings" type="Control" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer" unique_id=421589929]
layout_mode = 2
[node name="VideoLabel" type="Label" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer" unique_id=80166823]
layout_mode = 2
theme_override_font_sizes/font_size = 32
text = "Video"
horizontal_alignment = 1
[node name="HSeparator3" type="HSeparator" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer" unique_id=188845275]
layout_mode = 2
[node name="WindowMode" type="OptionButton" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer" unique_id=512024521]
unique_name_in_owner = true
layout_mode = 2
selected = 0
item_count = 3
popup/item_0/text = "WINDOW_FULLSCREEN"
popup/item_0/id = 1
popup/item_1/text = "WINDOW_EXCLUSIVE_FULLSCREEN"
popup/item_1/id = 2
popup/item_2/text = "WINDOW_WINDOWED"
popup/item_2/id = 0
[node name="AudioSettings" type="Control" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer" unique_id=1454411793]
layout_mode = 2
[node name="AudioLabel" type="Label" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer" unique_id=1475246011]
layout_mode = 2
theme_override_font_sizes/font_size = 32
text = "Audio"
horizontal_alignment = 1
[node name="HSeparator4" type="HSeparator" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer" unique_id=1708002596]
layout_mode = 2
[node name="RhythmDelay" type="Button" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer" unique_id=1954746002]
unique_name_in_owner = true
layout_mode = 2
text = "Configure Rhythm Delay"
[node name="AudioSliders" type="VBoxContainer" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer" unique_id=1541515152]
layout_mode = 2
[node name="MasterLabel" type="Label" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer/AudioSliders" unique_id=1666127429]
layout_mode = 2
text = "MASTER"
[node name="MasterContainer" type="HBoxContainer" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer/AudioSliders" unique_id=63007160]
layout_mode = 2
[node name="MasterSlider" type="HSlider" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer/AudioSliders/MasterContainer" unique_id=43103749]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
max_value = 1.0
step = 0.01
value = 0.85
tick_count = 11
ticks_on_borders = true
[node name="SliderLabel" type="Label" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer/AudioSliders/MasterContainer" unique_id=1539756970 node_paths=PackedStringArray("slider")]
custom_minimum_size = Vector2(32, 0)
layout_mode = 2
text = "85"
horizontal_alignment = 2
script = ExtResource("2_axtgf")
slider = NodePath("../MasterSlider")
pad_decimals = 0
do_remap = true
remap_output_max = 100.0
[node name="SFXLabel" type="Label" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer/AudioSliders" unique_id=96391289]
layout_mode = 2
text = "SFX"
[node name="SFXContainer" type="HBoxContainer" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer/AudioSliders" unique_id=1321350084]
layout_mode = 2
[node name="SFXSlider" type="HSlider" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer/AudioSliders/SFXContainer" unique_id=954640158]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
max_value = 1.0
step = 0.01
value = 1.0
tick_count = 11
ticks_on_borders = true
[node name="SliderLabel" type="Label" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer/AudioSliders/SFXContainer" unique_id=1852166469 node_paths=PackedStringArray("slider")]
custom_minimum_size = Vector2(32, 0)
layout_mode = 2
text = "100"
horizontal_alignment = 2
script = ExtResource("2_axtgf")
slider = NodePath("../SFXSlider")
pad_decimals = 0
do_remap = true
remap_output_max = 100.0
[node name="MusicLabel" type="Label" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer/AudioSliders" unique_id=1699726056]
layout_mode = 2
text = "MUSIC"
[node name="MusicContainer" type="HBoxContainer" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer/AudioSliders" unique_id=1571401865]
layout_mode = 2
[node name="MusicSlider" type="HSlider" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer/AudioSliders/MusicContainer" unique_id=1554055762]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
max_value = 1.0
step = 0.01
value = 1.0
tick_count = 11
ticks_on_borders = true
[node name="SliderLabel" type="Label" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer/AudioSliders/MusicContainer" unique_id=722315379 node_paths=PackedStringArray("slider")]
custom_minimum_size = Vector2(32, 0)
layout_mode = 2
text = "100"
horizontal_alignment = 2
script = ExtResource("2_axtgf")
slider = NodePath("../MusicSlider")
pad_decimals = 0
do_remap = true
remap_output_max = 100.0
[node name="AmbienceLabel" type="Label" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer/AudioSliders" unique_id=679820709]
layout_mode = 2
text = "AMBIENT"
[node name="AmbienceContainer" type="HBoxContainer" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer/AudioSliders" unique_id=852171003]
layout_mode = 2
[node name="AmbienceSlider" type="HSlider" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer/AudioSliders/AmbienceContainer" unique_id=1743356533]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
max_value = 1.0
step = 0.01
value = 1.0
tick_count = 11
ticks_on_borders = true
[node name="SliderLabel" type="Label" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer/AudioSliders/AmbienceContainer" unique_id=1842144008 node_paths=PackedStringArray("slider")]
custom_minimum_size = Vector2(32, 0)
layout_mode = 2
text = "100"
horizontal_alignment = 2
script = ExtResource("2_axtgf")
slider = NodePath("../AmbienceSlider")
pad_decimals = 0
do_remap = true
remap_output_max = 100.0
[node name="ControlsSettings" type="Control" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer" unique_id=738451135]
layout_mode = 2
[node name="ControlsLabel" type="Label" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer" unique_id=997989979]
layout_mode = 2
theme_override_font_sizes/font_size = 32
text = "Controls"
horizontal_alignment = 1
[node name="HSeparator5" type="HSeparator" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer" unique_id=1211938154]
layout_mode = 2
[node name="VibrationConfig" type="Button" parent="PanelContainer/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer" unique_id=253824648]
unique_name_in_owner = true
layout_mode = 2
text = "Configure Vibrations"
[node name="BackButton" type="Button" parent="PanelContainer/VBoxContainer" unique_id=1438707398]
unique_name_in_owner = true
layout_mode = 2
text = "BACK"
[node name="ConfirmationDialog" type="Control" parent="." unique_id=1084371767]
unique_name_in_owner = true
visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="RevertTimer" type="Timer" parent="ConfirmationDialog" unique_id=494220922]
unique_name_in_owner = true
wait_time = 5.0
one_shot = true
[node name="ColorRect" type="ColorRect" parent="ConfirmationDialog" unique_id=87130800]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0, 0, 0, 0.7254902)
[node name="ConfirmationPanel" type="PanelContainer" parent="ConfirmationDialog" unique_id=1058124629]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -132.5
offset_top = -85.5
offset_right = 132.5
offset_bottom = 85.5
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_axtgf")
[node name="MarginContainer" type="MarginContainer" parent="ConfirmationDialog/ConfirmationPanel" unique_id=1476949599]
layout_mode = 2
theme_override_constants/margin_left = 12
theme_override_constants/margin_top = 12
theme_override_constants/margin_right = 12
theme_override_constants/margin_bottom = 12
[node name="VBoxContainer" type="VBoxContainer" parent="ConfirmationDialog/ConfirmationPanel/MarginContainer" unique_id=1736269226]
layout_mode = 2
theme_override_constants/separation = 8
[node name="Label" type="Label" parent="ConfirmationDialog/ConfirmationPanel/MarginContainer/VBoxContainer" unique_id=458855790]
layout_mode = 2
theme_override_font_sizes/font_size = 32
text = "SETTINGS_CONFIRMATION_TITLE"
horizontal_alignment = 1
[node name="HSeparator" type="HSeparator" parent="ConfirmationDialog/ConfirmationPanel/MarginContainer/VBoxContainer" unique_id=1567845501]
layout_mode = 2
[node name="TimeLeftLabel" type="Label" parent="ConfirmationDialog/ConfirmationPanel/MarginContainer/VBoxContainer" unique_id=130409981]
unique_name_in_owner = true
layout_mode = 2
text = "SETTINGS_REVERT_TIME_LEFT"
horizontal_alignment = 1
[node name="HSeparator2" type="HSeparator" parent="ConfirmationDialog/ConfirmationPanel/MarginContainer/VBoxContainer" unique_id=11885534]
layout_mode = 2
[node name="Control" type="Control" parent="ConfirmationDialog/ConfirmationPanel/MarginContainer/VBoxContainer" unique_id=1620955474]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="ConfirmationDialog/ConfirmationPanel/MarginContainer/VBoxContainer" unique_id=1766652365]
layout_mode = 2
alignment = 1
[node name="CommitSettings" type="Button" parent="ConfirmationDialog/ConfirmationPanel/MarginContainer/VBoxContainer/HBoxContainer" unique_id=1797911803]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "SETTINGS_APPLY"
[node name="RevertSettings" type="Button" parent="ConfirmationDialog/ConfirmationPanel/MarginContainer/VBoxContainer/HBoxContainer" unique_id=1089460477]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "SETTINGS_REVERT"
[node name="RhythmSetupMenu" parent="." unique_id=552044082 instance=ExtResource("3_kktd5")]
visible = false
layout_mode = 1
mouse_filter = 0
[node name="ColorRect" type="ColorRect" parent="RhythmSetupMenu" unique_id=539350747]
modulate = Color(0, 0, 0, 1)
show_behind_parent = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2

Some files were not shown because too many files have changed in this diff Show More