added the game

This commit is contained in:
JHDev2006
2025-09-13 16:30:32 +01:00
parent 5ef689109b
commit 3773bdaf64
3616 changed files with 263702 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/mod_tool/interface/config_editor/json_editor.gd" type="Script" id=1]
[node name="Mod Config Editor" type="PanelContainer"]
visible = false
anchor_right = 1.0
anchor_bottom = 1.0
offset_top = 24.0
[node name="VBox" type="VBoxContainer" parent="."]
offset_left = 7.0
offset_top = 7.0
offset_right = 1907.0
offset_bottom = 891.0
[node name="HBox" type="HBoxContainer" parent="VBox"]
offset_right = 1900.0
offset_bottom = 40.0
[node name="Label" type="Label" parent="VBox/HBox"]
offset_top = 13.0
offset_right = 1533.0
offset_bottom = 27.0
size_flags_horizontal = 3
text = "Default json for user mod configuration"
[node name="ErrorLabel" type="Label" parent="VBox/HBox"]
unique_name_in_owner = true
offset_left = 1537.0
offset_top = 13.0
offset_right = 1617.0
offset_bottom = 27.0
text = "JSON is valid"
[node name="ShouldValidate" type="CheckButton" parent="VBox/HBox"]
unique_name_in_owner = true
offset_left = 1621.0
offset_right = 1697.0
offset_bottom = 40.0
pressed = true
flat = true
[node name="VSeparator" type="VSeparator" parent="VBox/HBox"]
offset_left = 1701.0
offset_right = 1705.0
offset_bottom = 40.0
[node name="SaveConfig" type="Button" parent="VBox/HBox"]
offset_left = 1709.0
offset_right = 1900.0
offset_bottom = 40.0
text = "Save config to manifest.json"
[node name="ConfigEditor" type="TextEdit" parent="VBox"]
unique_name_in_owner = true
offset_top = 44.0
offset_right = 1900.0
offset_bottom = 884.0
size_flags_vertical = 3
text = "{
}"
highlight_current_line = true
syntax_highlighter = true
show_line_numbers = true
fold_gutter = true
highlight_all_occurrences = true
smooth_scrolling = true
hiding_enabled = true
script = ExtResource( 1 )
[node name="ValidationDelay" type="Timer" parent="VBox/ConfigEditor"]
one_shot = true
[connection signal="cursor_changed" from="VBox/ConfigEditor" to="VBox/ConfigEditor" method="_on_cursor_changed"]
[connection signal="text_changed" from="VBox/ConfigEditor" to="VBox/ConfigEditor" method="_on_text_changed"]
[connection signal="timeout" from="VBox/ConfigEditor/ValidationDelay" to="VBox/ConfigEditor" method="_on_ValidationDelay_timeout"]

View File

@@ -0,0 +1,226 @@
@tool
extends TextEdit
signal discard_last_console_error
var base_theme: Theme: set = set_base_theme
var editor_settings: EditorSettings: set = set_editor_settings
var last_text := ""
var last_selection: TextSelection
var highlight_settings: PackedStringArray = [
"string_color", "background_color", "line_number_color",
"text_selected_color", "selection_color", "brace_mismatch_color",
"current_line_color", "word_highlighted_color", "number_color",
"code_folding_color", "symbol_color"
]
var autobrace_pairs := {
"(": ")",
"{": "}",
"[": "]",
'"': '"',
":": ",",
}
func _ready() -> void:
last_text = text
$"%ShouldValidate".connect("pressed", Callable(self, "validate"))
validate()
func set_base_theme(p_base_theme: Theme) -> void:
base_theme = p_base_theme
add_theme_font_override("font", base_theme.get_font("source", "EditorFonts"))
func set_editor_settings(p_editor_settings: EditorSettings) -> void:
editor_settings = p_editor_settings
# TODO -> syntax_highlighter = get_setting_bool("text_editor/highlighting/syntax_highlighter")
highlight_all_occurrences = get_setting_bool("text_editor/highlighting/highlight_all_occurrences")
highlight_current_line = get_setting_bool("text_editor/highlighting/highlight_current_line")
draw_tabs = get_setting_bool("text_editor/indent/draw_tabs")
draw_spaces = get_setting_bool("text_editor/indent/draw_spaces")
$ValidationDelay.wait_time = editor_settings.get_setting("text_editor/completion/idle_parse_delay")
# TODO -> add_color_region('"', '"', get_highlight_color("string_color"))
for highlight in highlight_settings:
add_theme_color_override(highlight, get_highlight_color(highlight))
func get_highlight_color(name: String) -> Color:
var color = editor_settings.get_setting("text_editor/highlighting/" + name)
return color if color is Color else null
func get_setting_bool(setting: String) -> bool:
var is_set = editor_settings.get_setting(setting)
return is_set if is_set is bool else false
func validate() -> void:
if not $"%ShouldValidate".pressed:
$"%ErrorLabel".text = "Validation off"
return
var test_json_conv = JSON.new()
test_json_conv.parse(text)
var parsed := test_json_conv.get_data()
if not parsed.error == OK:
$"%ErrorLabel".text = "Line %s: %s" % [parsed.error_line +1, parsed.error_string]
emit_signal("discard_last_console_error")
return
$"%ErrorLabel".text = "JSON is valid"
func _on_cursor_changed() -> void:
if get_selected_text().length() > 0:
last_selection = TextSelection.from_text_edit(self)
else:
last_selection = null
func _on_text_changed() -> void:
$ValidationDelay.stop()
$ValidationDelay.start()
if get_setting_bool("text_editor/completion/auto_brace_complete"):
autobrace()
last_text = text
func autobrace() -> void:
var line := get_line(get_caret_line())
var char_before_cursor := ""
if get_caret_column() > 0:
char_before_cursor = line[get_caret_column()-1]
var char_after_cursor := ""
if get_caret_column() < line.length():
char_after_cursor = line[get_caret_column()]
# When deleting, also delete the autobraced character
if Input.is_key_pressed(KEY_BACKSPACE):
if char_after_cursor in autobrace_pairs.values():
var deleted_character := first_different_character(text, last_text)
if autobrace_pairs.has(deleted_character) and autobrace_pairs[deleted_character] == char_after_cursor:
delete_character_after_cursor()
# If we encounter a closing brace, "skip" over it
# Since the character is written already, just delete the next one
elif is_matching_closing_brace(char_before_cursor, char_after_cursor):
delete_character_after_cursor()
# If a character is in the autoclose dict, close it
elif char_before_cursor in autobrace_pairs.keys():
var closing_char: String = autobrace_pairs[char_before_cursor]
var last_cursor_column := get_caret_column()
if not last_selection:
insert_text_at_caret(closing_char)
set_caret_column(last_cursor_column)
return
# If there is a selection, surround that with the bracing characters
# Pressing the alt key moves the selection left by one character
if Input.is_key_pressed(KEY_ALT):
if last_cursor_column == last_selection.from_col +1:
# If selected right to left, it can be fixed by offsetting it right
select(
last_selection.from_line, last_selection.from_col +1,
last_selection.to_line, last_selection.to_col +1
)
insert_text_at_caret(last_selection.enclosed_text + closing_char)
set_caret_column(last_selection.to_col +1)
else:
# If selected left to right, something else goes wrong as well,
# but it can be fixed by inserting the whole selection with braces
# and removing the leftover trailing brace behind it afterwards
insert_text_at_caret(char_before_cursor + last_selection.enclosed_text + closing_char)
delete_character_after_cursor()
set_caret_column(last_selection.to_col +1)
else:
insert_text_at_caret(last_selection.enclosed_text + closing_char)
set_caret_column(last_selection.to_col +1)
last_selection = null
func is_matching_closing_brace(new_character: String, char_after_cursor: String) -> bool:
if not new_character == char_after_cursor:
return false
# Opening and closing brace are the same -> ""
if char_after_cursor in autobrace_pairs.keys():
return true
# Opening and closing brace are different -> ()
if new_character in autobrace_pairs.values():
return true
return false
func delete_character_after_cursor() -> void:
var line_text := get_line(get_caret_line())
var cursor_col := get_caret_column() +1
var text_length := len(line_text)
if cursor_col < 1 or cursor_col > text_length:
return
var left_text := line_text.substr(0, cursor_col - 1)
var right_text := line_text.substr(cursor_col, text_length - cursor_col)
set_line(get_caret_line(), left_text + right_text)
set_caret_column(cursor_col - 1)
func first_different_character(str1: String, str2: String) -> String:
var len1 := str1.length()
var len2 := str2.length()
if len1 == 0:
return str2[0]
if len2 == 0:
return str1[0]
for i in min(len1, len2):
if not str1[i] == str2[i]:
return str2[i]
return ""
func _on_ValidationDelay_timeout() -> void:
validate()
class TextSelection:
var enclosed_text: String
var from_line: int
var from_col: int
var to_line: int
var to_col: int
func _init(p_enclosed_text: String, p_from_line: int, p_from_col: int, p_to_line: int, p_to_col: int) -> void:
enclosed_text = p_enclosed_text
from_line = p_from_line
from_col = p_from_col
to_line = p_to_line
to_col = p_to_col
static func from_text_edit(text_edit: TextEdit) -> TextSelection:
return TextSelection.new(
text_edit.get_selection_text(),
text_edit.get_selection_from_line(), text_edit.get_selection_from_column(),
text_edit.get_selection_to_line(), text_edit.get_selection_to_column()
)
func _to_string() -> String:
return "%s %s %s" % [ Vector2(from_line, from_col), enclosed_text, Vector2(to_line, to_col) ]

View File

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

View File

@@ -0,0 +1,153 @@
@tool
extends Window
signal mod_dir_created
const DIR_NAME_DEFAULT_TEMPLATE = "default"
const DIR_NAME_MINIMAL_TEMPLATE = "minimal"
@onready var mod_tool_store: ModToolStore = get_node_or_null("/root/ModToolStore")
@onready var mod_namespace: ModToolInterfaceInputString = $"%Namespace"
@onready var mod_name: ModToolInterfaceInputString = $"%ModName"
@onready var mod_id: ModToolInterfaceInputString = $"%ModId"
@onready var mod_template: ModToolInterfaceInputOptions = $"%ModTemplate"
func _ready() -> void:
mod_namespace.show_error_if_not(false)
mod_name.show_error_if_not(false)
mod_id.show_error_if_not(false)
func add_mod() -> void:
# Validate mod-id
if not mod_tool_store.manifest_data.is_mod_id_valid(mod_tool_store.name_mod_dir, mod_tool_store.name_mod_dir, "", true):
ModToolUtils.output_error('Invalid name or namespace: "%s". You may only use letters, numbers, underscores and at least 3 characters for each.' % mod_tool_store.name_mod_dir)
return
# Check if mod dir exists
if not _ModLoaderFile.dir_exists(mod_tool_store.path_mod_dir):
# If not - create it
var success := ModToolUtils.make_dir_recursive(mod_tool_store.path_mod_dir)
if not success:
return
# Get Template files
var template_paths := ModToolUtils.get_flat_view_dict(mod_tool_store.path_current_template_dir, "", [], false, true)
# Copy current selected template dir files and folders to res://mods-unpacked
for path in template_paths:
var template_local_path := path.trim_prefix(mod_tool_store.path_current_template_dir) as String
if _ModLoaderFile.file_exists(path):
ModToolUtils.file_copy(path, mod_tool_store.path_mod_dir.path_join(template_local_path))
else:
ModToolUtils.make_dir_recursive(mod_tool_store.path_mod_dir.path_join(template_local_path))
# Update FileSystem
mod_tool_store.editor_file_system.scan()
# Wait for the scan to finish
await mod_tool_store.editor_file_system.filesystem_changed
# Navigate to the new mod dir in the FileSystem pannel
EditorInterface.get_file_system_dock().navigate_to_path(mod_tool_store.path_mod_dir.path_join("mod_main.gd"))
# Output info
ModToolUtils.output_info("Added base mod files to " + mod_tool_store.path_mod_dir)
# Open mod_main.gd in the code editor
var mod_main_script := load(mod_tool_store.path_mod_dir.path_join("mod_main.gd"))
EditorInterface.edit_script(mod_main_script)
EditorInterface.set_main_screen_editor("Script")
# Split the new mod id
var name_mod_dir_split: Array = mod_tool_store.name_mod_dir.split("-")
# Update the namespace in the manifest
mod_tool_store.manifest_data.mod_namespace = name_mod_dir_split[0]
# Update the mod name in the manifest
mod_tool_store.manifest_data.name = name_mod_dir_split[1]
# Update manifest editor ui
mod_tool_store.editor_plugin.tools_panel.manifest_editor.update_ui()
# Open manifest editor
mod_tool_store.editor_plugin.tools_panel.show_manifest_editor()
# Save the manifest
mod_tool_store.editor_plugin.tools_panel.manifest_editor.save_manifest()
else:
# If so - show error and ask if user wants to connect with the mod instead
ModToolUtils.output_error("Mod directory at %s already exists." % mod_tool_store.path_mod_dir)
# TODO: Ask user to connect with the mod instead
return
func clear_mod_id_input() -> void:
mod_id.input_text = ""
func get_template_options() -> Array[String]:
var mod_template_options: Array[String] = []
var template_dirs := _ModLoaderPath.get_dir_paths_in_dir(mod_tool_store.PATH_TEMPLATES_DIR)
# Add the default templates
mod_template_options.push_back(DIR_NAME_DEFAULT_TEMPLATE)
mod_template_options.push_back(DIR_NAME_MINIMAL_TEMPLATE)
for template_dir in template_dirs:
var template_dir_name: String = template_dir.split("/")[-1]
# Skip if its one of the default templates
if (
template_dir_name == DIR_NAME_DEFAULT_TEMPLATE or
template_dir_name == DIR_NAME_MINIMAL_TEMPLATE
):
continue
# Add all the custom templates
mod_template_options.push_back(template_dir_name)
return mod_template_options
func _on_Namespace_value_changed(new_value: String, input_node: ModToolInterfaceInputString) -> void:
input_node.validate(mod_tool_store.manifest_data.is_name_or_namespace_valid(new_value, true))
mod_id.input_text = "%s-%s" % [mod_namespace.get_input_value(), mod_name.get_input_value()]
func _on_ModName_value_changed(new_value: String, input_node: ModToolInterfaceInputString) -> void:
input_node.validate(mod_tool_store.manifest_data.is_name_or_namespace_valid(new_value, true))
mod_id.input_text = "%s-%s" % [mod_namespace.get_input_value(), mod_name.get_input_value()]
func _on_ModId_value_changed(new_value: String, input_node: ModToolInterfaceInputString) -> void:
input_node.validate(mod_tool_store.manifest_data.is_mod_id_valid(new_value, new_value, "", true))
mod_tool_store.name_mod_dir = new_value
func _on_btn_create_mod_pressed() -> void:
add_mod()
emit_signal("mod_dir_created")
func _on_CreateMod_about_to_show() -> void:
# Reset Inputs
mod_namespace.input_text = ""
mod_name.input_text = ""
# Reset Template
mod_tool_store.path_current_template_dir = mod_tool_store.PATH_TEMPLATES_DIR + "default"
# Get all Template options
mod_template.input_options = get_template_options()
func _on_ModTemplate_value_changed(new_value: String, input_node: ModToolInterfaceInputOptions) -> void:
mod_tool_store.path_current_template_dir = mod_tool_store.PATH_TEMPLATES_DIR + new_value
func _on_close_requested() -> void:
hide()

View File

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

View File

@@ -0,0 +1,90 @@
[gd_scene load_steps=4 format=3 uid="uid://glui2s46v4x4"]
[ext_resource type="Script" path="res://addons/mod_tool/interface/create_mod/create_mod.gd" id="1"]
[ext_resource type="PackedScene" path="res://addons/mod_tool/interface/global/input_string.tscn" id="2"]
[ext_resource type="PackedScene" uid="uid://dyunxqcmy4esi" path="res://addons/mod_tool/interface/global/input_options.tscn" id="3"]
[node name="CreateMod" type="Window"]
position = Vector2i(0, 36)
script = ExtResource("1")
[node name="MarginContainer" type="MarginContainer" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 25
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
[node name="Settings" type="VBoxContainer" parent="MarginContainer"]
custom_minimum_size = Vector2(300, 0)
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 0.5
[node name="Scroll" type="ScrollContainer" parent="MarginContainer/Settings"]
layout_mode = 2
size_flags_vertical = 3
[node name="VBox" type="VBoxContainer" parent="MarginContainer/Settings/Scroll"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 5
[node name="Namespace" parent="MarginContainer/Settings/Scroll/VBox" instance=ExtResource("2")]
unique_name_in_owner = true
layout_mode = 2
input_placeholder = "Namespace"
is_required = true
key = "namespace"
label_text = "Namespace ( Author Name )"
[node name="ModName" parent="MarginContainer/Settings/Scroll/VBox" instance=ExtResource("2")]
unique_name_in_owner = true
layout_mode = 2
input_placeholder = "ModName"
is_required = true
key = "name"
label_text = "Mod Name"
[node name="ModId" parent="MarginContainer/Settings/Scroll/VBox" instance=ExtResource("2")]
unique_name_in_owner = true
layout_mode = 2
is_editable = false
input_placeholder = "Namespace-ModName"
is_required = true
key = "mod_id"
label_text = "Mod ID"
[node name="Space" type="Control" parent="MarginContainer/Settings/Scroll/VBox"]
custom_minimum_size = Vector2(0, 15)
layout_mode = 2
[node name="ModTemplate" parent="MarginContainer/Settings/Scroll/VBox" instance=ExtResource("3")]
unique_name_in_owner = true
layout_mode = 2
input_options = Array[String]([])
is_required = true
key = "mod_template"
label_text = "Template"
[node name="Buttons" type="PanelContainer" parent="MarginContainer/Settings"]
layout_mode = 2
[node name="VBox" type="VBoxContainer" parent="MarginContainer/Settings/Buttons"]
layout_mode = 2
[node name="btn_create_mod" type="Button" parent="MarginContainer/Settings/Buttons/VBox"]
layout_mode = 2
text = "Create"
[connection signal="about_to_popup" from="." to="." method="_on_CreateMod_about_to_show"]
[connection signal="close_requested" from="." to="." method="_on_close_requested"]
[connection signal="value_changed" from="MarginContainer/Settings/Scroll/VBox/Namespace" to="." method="_on_Namespace_value_changed"]
[connection signal="value_changed" from="MarginContainer/Settings/Scroll/VBox/ModName" to="." method="_on_ModName_value_changed"]
[connection signal="value_changed" from="MarginContainer/Settings/Scroll/VBox/ModId" to="." method="_on_ModId_value_changed"]
[connection signal="value_changed" from="MarginContainer/Settings/Scroll/VBox/ModTemplate" to="." method="_on_ModTemplate_value_changed"]
[connection signal="pressed" from="MarginContainer/Settings/Buttons/VBox/btn_create_mod" to="." method="_on_btn_create_mod_pressed"]

View File

@@ -0,0 +1,567 @@
class_name FileSystemContextActions
extends Control
var mod_tool_store: ModToolStore
class ContextActionOptions:
extends Resource
var icon: StringName
var title: String
var meta_key: StringName
var tooltip: String
func _init(_icon, _title, _meta_key, _tooltip) -> void:
icon = _icon
title = _title
meta_key = _meta_key
tooltip = _tooltip
func _init(_mod_tool_store: ModToolStore, file_system_dock: FileSystemDock) -> void:
mod_tool_store = _mod_tool_store
connect_file_system_context_actions(file_system_dock)
func connect_file_system_context_actions(file_system : FileSystemDock) -> void:
var file_tree : Tree
var file_list : ItemList
for node in file_system.get_children():
if is_instance_of(node, SplitContainer):
file_tree = node.get_child(0)
file_list = node.get_child(1).get_child(1)
break
for node in file_system.get_children():
var context_menu : PopupMenu = node as PopupMenu
if not context_menu:
continue
context_menu.id_pressed.connect(_on_file_system_context_menu_pressed.bind(context_menu))
var signals := context_menu.get_signal_connection_list(&"id_pressed")
if not signals.is_empty():
match signals[0]["callable"].get_method():
&"FileSystemDock::_tree_rmb_option":
context_menu.about_to_popup.connect(_on_file_tree_context_actions_about_to_popup.bind(context_menu, file_tree))
&"FileSystemDock::_file_list_rmb_option":
context_menu.about_to_popup.connect(_on_file_list_context_actions_about_to_popup.bind(context_menu, file_tree))
# Called every time the file system context actions pop up
# Since they are dynamic, they are cleared every time and need to be refilled
func add_custom_context_actions(context_menu: PopupMenu, file_paths: Array[String]) -> void:
if file_paths.is_empty():
return
var script_paths: Array[String] = []
var asset_override_paths: Array[String] = []
for file_path in file_paths:
if DirAccess.dir_exists_absolute(file_path):
continue
if FileAccess.file_exists(file_path):
if file_path.ends_with(".gd"):
script_paths.append(file_path)
continue
if file_path.ends_with(".tscn") or file_path.ends_with(".tres"):
continue
asset_override_paths.append(file_path)
if script_paths.size() > 0 or asset_override_paths.size() > 0:
context_menu.add_separator()
if script_paths.size() > 0:
add_script_extension_context_action(context_menu, script_paths)
add_mod_hook_file_context_action(context_menu, script_paths)
var script_with_hook_count := ModToolUtils.check_for_hooked_script(script_paths, mod_tool_store)
if script_with_hook_count == script_paths.size():
add_restore_context_action(context_menu, script_paths)
elif script_with_hook_count > 0:
add_restore_context_action(context_menu, script_paths)
add_hooks_context_action(context_menu, script_paths)
else:
add_hooks_context_action(context_menu, script_paths)
if asset_override_paths.size() > 0:
add_asset_override_context_action(context_menu, script_paths)
func create_script_extension(file_path: String) -> String:
if not mod_tool_store.name_mod_dir:
ModToolUtils.output_error("Select an existing mod or create a new one to create script overrides")
return ""
var file_directory := file_path.get_base_dir().trim_prefix("res://")
var extension_directory: String = mod_tool_store.path_mod_dir.path_join("extensions").path_join(file_directory)
ModToolUtils.make_dir_recursive(extension_directory)
var extension_path := extension_directory.path_join(file_path.get_file())
var file := FileAccess.open(extension_path, FileAccess.WRITE)
if not FileAccess.file_exists(extension_path):
file.store_line('extends "%s"' % file_path)
file.close()
ModToolUtils.output_info('Created script extension of "%s" at path %s' % [file_path.get_file(), extension_path])
mod_tool_store.editor_file_system.scan()
EditorInterface.get_file_system_dock().navigate_to_path(extension_path)
# Load the new extension script
var extension_script: Script = load(extension_path)
# Open the new extension script in the script editor
EditorInterface.edit_script(extension_script)
return extension_path
func create_mod_hook_file(file_path: String) -> String:
if not mod_tool_store.name_mod_dir:
ModToolUtils.output_error("Select an existing mod or create a new one to create script overrides")
return ""
var file_directory := file_path.get_base_dir().trim_prefix("res://")
var extension_directory: String = mod_tool_store.path_mod_dir.path_join("extensions").path_join(file_directory)
ModToolUtils.make_dir_recursive(extension_directory)
var hook_file_name := "%s.hooks.%s" % [file_path.get_file().get_basename(), file_path.get_extension()]
var extension_path := extension_directory.path_join(hook_file_name)
print(extension_path)
var file := FileAccess.open(extension_path, FileAccess.WRITE)
if not FileAccess.file_exists(extension_path):
file.store_line('extends Object')
file.close()
ModToolUtils.output_info('Created mod hook file for "%s" at path %s' % [file_path.get_file(), extension_path])
mod_tool_store.editor_file_system.scan()
EditorInterface.get_file_system_dock().navigate_to_path(extension_path)
# Load the new extension script
var extension_script: Script = load(extension_path)
# Open the new extension script in the script editor
EditorInterface.edit_script(extension_script)
return extension_path
func add_script_extension_to_mod_main(extension_path: String) -> void:
var main_script_path: String = mod_tool_store.path_mod_dir.path_join("mod_main.gd")
var file := FileAccess.open(main_script_path, FileAccess.READ_WRITE)
if not file:
ModToolUtils.output_error("Failed to open mod_main.gd with error \"%s\"" % error_string(FileAccess.get_open_error()))
if not ModToolUtils.script_has_method(main_script_path, "install_script_extensions"):
ModToolUtils.output_error('To automatically add new script extensions to "mod_main.gd", add "func install_script_extensions():" to it.')
return
var file_content := file.get_as_text()
var index_find_from := file_content.find("func install_script_extensions")
var mod_extensions_dir_path_index := file_content.find("extensions_dir_path", index_find_from)
# Construct the line required to install the extension. If the standard way is used and a
# variable "extensions_dir_path" is found, use that variable in combination with path_join
var extension_install_line := "\tModLoaderMod.install_script_extension(%s)\n"
if mod_extensions_dir_path_index == -1:
extension_install_line = extension_install_line % ModToolUtils.quote_string(extension_path)
else:
extension_path = extension_path.trim_prefix(mod_tool_store.path_mod_dir.path_join("extensions/"))
extension_install_line = extension_install_line % "extensions_dir_path.path_join(%s)" % ModToolUtils.quote_string(extension_path)
# Check if that file was already used as script extension
if extension_install_line.strip_edges() in file_content:
return
var last_install_line_index := file_content.rfind("ModLoaderMod.install_script_extension")
if last_install_line_index == -1:
# If there is no ModLoaderMod.install_script_extension yet, put it at the end of install_script_extensions
var insertion_index := ModToolUtils.get_index_at_method_end("install_script_extensions", file_content)
file_content = file_content.insert(insertion_index, "\n" + extension_install_line)
else:
var last_install_line_end_index := file_content.find("\n", last_install_line_index)
file_content = file_content.insert(last_install_line_end_index +1, extension_install_line)
file.store_string(file_content)
file.close()
ModToolUtils.output_info('Added script extension "%s" to mod "%s"' % [extension_path, main_script_path.get_base_dir().get_file()])
func add_hook_file_to_mod_main(vanilla_path: String, extension_path: String) -> void:
var main_script_path: String = mod_tool_store.path_mod_dir.path_join("mod_main.gd")
var file := FileAccess.open(main_script_path, FileAccess.READ_WRITE)
if not file:
ModToolUtils.output_error("Failed to open mod_main.gd with error \"%s\"" % error_string(FileAccess.get_open_error()))
if not ModToolUtils.script_has_method(main_script_path, "install_script_hook_files"):
ModToolUtils.output_error('To automatically add new script hook files to "mod_main.gd", add "func install_script_hook_files():" to it.')
return
var file_content := file.get_as_text()
var index_find_from := file_content.find("func install_script_hook_files")
var mod_extensions_dir_path_index := file_content.find("extensions_dir_path", index_find_from)
# Construct the line required to install the extension. If the standard way is used and a
# variable "extensions_dir_path" is found, use that variable in combination with path_join
var extension_install_line := "\tModLoaderMod.install_script_hooks(" + ModToolUtils.quote_string(vanilla_path) + ", %s)\n"
if mod_extensions_dir_path_index == -1:
extension_install_line = extension_install_line % ModToolUtils.quote_string(extension_path)
else:
extension_path = extension_path.trim_prefix(mod_tool_store.path_mod_dir.path_join("extensions/"))
extension_install_line = extension_install_line % "extensions_dir_path.path_join(%s)" % ModToolUtils.quote_string(extension_path)
# Check if that file was already used as script extension
if extension_install_line.strip_edges() in file_content:
return
var last_install_line_index := file_content.rfind("ModLoaderMod.install_script_hooks")
if last_install_line_index == -1:
# If there is no ModLoaderMod.install_script_hooks yet, put it at the end of install_script_hook_files
var insertion_index := ModToolUtils.get_index_at_method_end("install_script_hook_files", file_content)
file_content = file_content.insert(insertion_index, "\n" + extension_install_line)
else:
var last_install_line_end_index := file_content.find("\n", last_install_line_index)
file_content = file_content.insert(last_install_line_end_index +1, extension_install_line)
file.store_string(file_content)
file.close()
ModToolUtils.output_info('Added mod hooks file "%s" to mod "%s"' % [extension_path, main_script_path.get_base_dir().get_file()])
func create_overwrite_asset(file_path: String) -> String:
if not mod_tool_store.name_mod_dir:
ModToolUtils.output_error("Select an existing mod or create a new one to overwrite assets")
return ""
var file_directory := file_path.get_base_dir().trim_prefix("res://")
var overwrite_directory: String = mod_tool_store.path_mod_dir.path_join("overwrites").path_join(file_directory)
ModToolUtils.make_dir_recursive(overwrite_directory)
var overwrite_path := overwrite_directory.path_join(file_path.get_file())
if not FileAccess.file_exists(overwrite_path):
DirAccess.copy_absolute(file_path, overwrite_path)
ModToolUtils.output_info('Copied asset "%s" as overwrite to path %s' % [file_path.get_file(), overwrite_path])
EditorInterface.get_resource_filesystem().scan()
EditorInterface.get_file_system_dock().navigate_to_path(overwrite_path)
return overwrite_path
func add_asset_overwrite_to_overwrites(vanilla_asset_path: String, asset_path: String) -> void:
var overwrites_script_path: String = mod_tool_store.path_mod_dir.path_join("overwrites.gd")
var overwrites_script: GDScript
var overwrites_script_new: Node
var overwrites_script_syntax_tempalte := """extends Node
var vanilla_file_paths: Array[String] = {%VANILLA_FILE_PATHS%}
var overwrite_file_paths: Array[String] = {%OVERWRITE_FILE_PATHS%}
var overwrite_resources := []
func _init():
for i in overwrite_file_paths.size():
var vanilla_path := vanilla_file_paths[i]
var overwrite_path := overwrite_file_paths[i]
var overwrite_resource := load(overwrite_path)
overwrite_resources.push_back(overwrite_resource)
overwrite_resource.take_over_path(vanilla_path)
"""
# overwrite.gd does not exist yet
if not FileAccess.file_exists(overwrites_script_path):
overwrites_script = GDScript.new()
overwrites_script.source_code = overwrites_script_syntax_tempalte.format({
"%VANILLA_FILE_PATHS%": "[]",
"%OVERWRITE_FILE_PATHS%": "[]",
})
var success := ResourceSaver.save(overwrites_script, overwrites_script_path)
if not success == OK:
ModToolUtils.output_error("Failed to save overwrite.gd with error \"%s\"" % error_string(FileAccess.get_open_error()))
overwrites_script = load(overwrites_script_path)
overwrites_script_new = overwrites_script.new()
# Check if the overwrites script has the neccessary props
if (
not ModToolUtils.script_has_method(overwrites_script_path, "vanilla_file_paths") or
not ModToolUtils.script_has_method(overwrites_script_path, "overwrite_file_paths")
):
ModToolUtils.output_error("The 'overwrites.gd' file has an unexpected format. To proceed, please delete the existing 'overwrites.gd' file and allow the tool to regenerate it automatically.")
return
# Check if that asset is already being overwritten
if asset_path in overwrites_script_new.overwrite_file_paths:
return
overwrites_script_new.vanilla_file_paths.push_back(vanilla_asset_path)
overwrites_script_new.overwrite_file_paths.push_back(asset_path)
overwrites_script.source_code = overwrites_script_syntax_tempalte.format({
"%VANILLA_FILE_PATHS%": JSON.stringify(overwrites_script_new.vanilla_file_paths, "\t"),
"%OVERWRITE_FILE_PATHS%": JSON.stringify(overwrites_script_new.overwrite_file_paths, "\t"),
})
ResourceSaver.save(overwrites_script)
overwrites_script_new.free()
# Open the overwrites script in the script editor
EditorInterface.edit_script(overwrites_script)
ModToolUtils.output_info('Added asset overwrite "%s" to mod "%s"' % [asset_path, overwrites_script_path.get_base_dir().get_file()])
func add_context_action(context_menu: PopupMenu, script_paths: Array[String], options: ContextActionOptions) -> void:
context_menu.add_icon_item(
mod_tool_store.editor_base_control.get_theme_icon(options.icon, &"EditorIcons"),
"ModTool: %s" % options.title + ("s (%s)" % script_paths.size() if script_paths.size() > 1 else "")
)
context_menu.set_item_metadata(
context_menu.get_item_count() -1,
{ options.meta_key: script_paths }
)
context_menu.set_item_tooltip(
context_menu.get_item_count() -1,
"%s: \n%s" %
[options.tooltip, str(script_paths).trim_prefix("[").trim_suffix("]").replace(", ", "\n")]
)
func add_script_extension_context_action(context_menu: PopupMenu, script_paths: Array[String]) -> void:
add_context_action(
context_menu,
script_paths,
ContextActionOptions.new(
&"ScriptExtend",
"Create Script Extension",
&"mod_tool_script_paths",
"Will add extensions for"
)
)
func add_mod_hook_file_context_action(context_menu: PopupMenu, script_paths: Array[String]) -> void:
add_context_action(
context_menu,
script_paths,
ContextActionOptions.new(
&"ScriptExtend",
"Create Mod Hook File",
&"mod_tool_mod_hook_file_paths",
"Will add mod hook files for"
)
)
func add_restore_context_action(context_menu: PopupMenu, script_paths: Array[String]) -> void:
var script_paths_to_restore: Array[String] = script_paths.filter(
func(script_path): return mod_tool_store.hooked_scripts.has(script_path)
)
add_context_action(
context_menu,
script_paths_to_restore,
ContextActionOptions.new(
&"UndoRedo",
"Restore script to unhooked version",
&"mod_tool_restore_script_paths",
"Will restore the non hooked script for"
)
)
func add_asset_override_context_action(context_menu: PopupMenu, script_paths: Array[String]) -> void:
add_context_action(
context_menu,
script_paths,
ContextActionOptions.new(
&"Override",
"Create Asset Overwrite",
&"mod_tool_override_paths",
"Will overwrite assets"
)
)
func add_hooks_context_action(context_menu: PopupMenu, script_paths: Array[String]) -> void:
var script_paths_to_add_hooks: Array[String] = script_paths.filter(
func(script_path): return not mod_tool_store.hooked_scripts.has(script_path)
)
add_context_action(
context_menu,
script_paths_to_add_hooks,
ContextActionOptions.new(
&"ShaderGlobalsOverride",
"Convert script to hooked version",
&"mod_tool_hook_script_paths",
"Will add mod hooks for"
)
)
func handle_script_extension_creation(metadata: Dictionary) -> void:
var file_paths = metadata.mod_tool_script_paths
var mod_main_path := mod_tool_store.path_mod_dir.path_join("mod_main.gd")
for file_path in file_paths:
var extension_path := create_script_extension(file_path)
if extension_path:
add_script_extension_to_mod_main(extension_path)
# We navigate to the created script extension in `create_script_extension()`, so we should never
# instantly refresh the mod main script here. If we call `ModToolUtils.reload_script()`
# after this, it's possible that the `mod_main` content gets copied into the
# newly created extension script. If that script is then saved, we
# unintentionally overwrite the original script content.
mod_tool_store.pending_reloads.push_back(mod_main_path)
#Switch to the script screen
EditorInterface.set_main_screen_editor("Script")
func handle_mod_hook_file_creation(metadata: Dictionary) -> void:
var file_paths = metadata.mod_tool_mod_hook_file_paths
var mod_main_path := mod_tool_store.path_mod_dir.path_join("mod_main.gd")
for file_path in file_paths:
var extension_path := create_mod_hook_file(file_path)
if extension_path:
add_hook_file_to_mod_main(file_path, extension_path)
# We navigate to the created script extension in `create_script_extension()`, so we should never
# instantly refresh the mod main script here. If we call `ModToolUtils.reload_script()`
# after this, it's possible that the `mod_main` content gets copied into the
# newly created extension script. If that script is then saved, we
# unintentionally overwrite the original script content.
mod_tool_store.pending_reloads.push_back(mod_main_path)
#Switch to the script screen
EditorInterface.set_main_screen_editor("Script")
func handle_override_creation(metadata: Dictionary) -> void:
var file_paths: Array[String] = metadata.mod_tool_override_paths
var current_script: GDScript
var overwrites_path := mod_tool_store.path_mod_dir.path_join("overwrites.gd")
for file_path in file_paths:
var asset_path := create_overwrite_asset(file_path)
if asset_path:
add_asset_overwrite_to_overwrites(file_path, asset_path)
current_script = EditorInterface.get_script_editor().get_current_script()
mod_tool_store.pending_reloads.push_back(overwrites_path)
if current_script.resource_path == overwrites_path:
ModToolUtils.reload_script(current_script, mod_tool_store)
#Switch to the script screen
EditorInterface.set_main_screen_editor("Script")
func handle_mod_hook_creation(metadata: Dictionary) -> void:
var file_paths: Array[String] = metadata.mod_tool_hook_script_paths
var current_script: GDScript
for file_path in file_paths:
var error := ModToolHookGen.transform_one(file_path, mod_tool_store)
if not error == OK:
ModToolUtils.output_error("Error creating mod hooks for script at path: \"%s\" error: \"%s\" " % [file_path, error_string(error)])
return
mod_tool_store.pending_reloads.push_back(file_path)
current_script = EditorInterface.get_script_editor().get_current_script()
if current_script.resource_path == file_path:
ModToolUtils.reload_script(current_script, mod_tool_store)
ModToolUtils.output_info("Mod Hooks created for script at path: \"%s\"" % file_path)
func handle_mod_hook_restore(metadata: Dictionary) -> void:
var file_paths: Array[String] = metadata.mod_tool_restore_script_paths
var current_script: GDScript
for file_path in file_paths:
var error := ModToolHookGen.restore(file_path, mod_tool_store)
if not error == OK:
ModToolUtils.output_error("ERROR: Restoring script: \"%s\" with error: \"%s\"" % [file_path, error_string(error)])
return
mod_tool_store.pending_reloads.push_back(file_path)
current_script = EditorInterface.get_script_editor().get_current_script()
if current_script.resource_path == file_path:
ModToolUtils.reload_script(current_script, mod_tool_store)
func _on_file_tree_context_actions_about_to_popup(context_menu: PopupMenu, tree: Tree) -> void:
var selected := tree.get_next_selected(null)
if not selected: # Empty space was clicked
return
# multiselection
var file_paths: Array[String] = []
while selected:
var file_path = selected.get_metadata(0)
if file_path is String:
file_paths.append(file_path)
selected = tree.get_next_selected(selected)
add_custom_context_actions(context_menu, file_paths)
func _on_file_list_context_actions_about_to_popup(context_menu: PopupMenu, list: ItemList) -> void:
if not list.get_selected_items().size() > 0: # Empty space was clicked
return
var file_paths := []
for item_index in list.get_selected_items():
var file_path = list.get_item_metadata(item_index)
if file_path is String:
file_paths.append(file_path)
add_custom_context_actions(context_menu, file_paths)
func _on_file_system_context_menu_pressed(id: int, context_menu: PopupMenu) -> void:
var file_paths: PackedStringArray
var metadata = context_menu.get_item_metadata(id)
var current_script: GDScript
# Ensure that the metadata is actually set by the ModTool
# Since id and index of the item can always change
if metadata is Dictionary and metadata.has("mod_tool_script_paths"):
handle_script_extension_creation(metadata)
if metadata is Dictionary and metadata.has("mod_tool_override_paths"):
handle_override_creation(metadata)
if metadata is Dictionary and metadata.has("mod_tool_mod_hook_file_paths"):
handle_mod_hook_file_creation(metadata)
if metadata is Dictionary and metadata.has("mod_tool_hook_script_paths"):
handle_mod_hook_creation(metadata)
if metadata is Dictionary and metadata.has("mod_tool_restore_script_paths"):
handle_mod_hook_restore(metadata)

View File

@@ -0,0 +1 @@
uid://83v2dur6o2g6

View File

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

View File

@@ -0,0 +1,35 @@
@tool
extends Window
signal dir_selected(dir_path)
@onready var directory_list: VBoxContainer = $"%DirectoryList"
func generate_dir_buttons(dir_path: String) -> void:
clear_directory_list()
var dir_paths := _ModLoaderPath.get_dir_paths_in_dir(dir_path)
for path in dir_paths:
var dir_name: String = path.split('/')[-1]
var dir_btn := Button.new()
dir_btn.text = dir_name
directory_list.add_child(dir_btn)
dir_btn.pressed.connect(_on_dir_btn_dir_selected.bind(path))
func clear_directory_list() -> void:
for child in directory_list.get_children():
directory_list.remove_child(child)
child.queue_free()
func _on_dir_btn_dir_selected(path: String) -> void:
dir_selected.emit(path)
func _on_close_requested() -> void:
hide()

View File

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

View File

@@ -0,0 +1,31 @@
[gd_scene load_steps=2 format=3 uid="uid://du17jjwqtopix"]
[ext_resource type="Script" path="res://addons/mod_tool/interface/global/directory_selection/select_directory.gd" id="1"]
[node name="SelectDirectory" type="Window"]
initial_position = 2
size = Vector2i(400, 250)
script = ExtResource("1")
[node name="MarginContainer" type="MarginContainer" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 25
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="DirectoryList" type="VBoxContainer" parent="MarginContainer/ScrollContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[connection signal="close_requested" from="." to="." method="_on_close_requested"]

View File

@@ -0,0 +1,66 @@
@tool
class_name ModToolInterfaceInput
extends HBoxContainer
signal value_changed(new_value, input_node)
@export var is_required: bool:
set = set_is_required
@export var key: String
@export var label_text: String:
set = set_label_text
@export var editor_icon_name: String = "NodeWarning"
@export var hint_text: String:
set = set_hint_text
var is_valid := true: set = set_is_valid
func _ready() -> void:
# Set up warning icons to show if a field is invalid
set_editor_icon(editor_icon_name)
func set_is_required(required: bool) -> void:
is_required = required
set_label_text(label_text)
func set_is_valid(new_is_valid: bool) -> void:
is_valid = new_is_valid
show_error_if_not(is_valid)
func set_label_text(new_text: String) -> void:
label_text = new_text
$Label.text = new_text if is_required else new_text + " (optional)"
func set_hint_text(new_text: String) -> void:
hint_text = new_text
tooltip_text = new_text
mouse_default_cursor_shape = CURSOR_ARROW if new_text == "" else CURSOR_HELP
func set_editor_icon(icon_name: String) -> void:
var mod_tool_store: ModToolStore = get_node_or_null("/root/ModToolStore")
if icon_name and mod_tool_store:
set_error_icon(mod_tool_store.editor_base_control.get_theme_icon(icon_name, "EditorIcons"))
func set_error_icon(icon: Texture2D) -> void:
$"%ErrorIcon".texture = icon
func show_error_if_not(condition: bool) -> void:
if not condition:
$"%ErrorIcon".self_modulate = Color.WHITE
else:
$"%ErrorIcon".self_modulate = Color.TRANSPARENT
func validate(_condition: bool) -> bool:
printerr("Implement a validation method")
return false

View File

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

View File

@@ -0,0 +1,42 @@
@tool
class_name ModToolInterfaceInputOptions
extends ModToolInterfaceInput
@export var input_options: Array[String]: set = set_input_options
func set_input_options(new_options: Array[String]) -> void:
input_options = new_options
var input: OptionButton = get_node_or_null("%Input") as OptionButton
if not input or new_options.is_empty(): return # node can't be found directly after reloading the plugin
input.clear()
for option in input_options:
input.add_item(option)
input.select(0)
func get_input_value() -> int:
return ($"%Input" as OptionButton).get_selected_id()
func get_input_string() -> String:
if get_input_value() == -1:
return ""
return input_options[get_input_value()]
func validate(condition: bool) -> bool:
# Check if input is required and empty
if is_required and get_input_value() == -1:
is_valid = false
return false
# Invalidate field if the condition is not met
is_valid = condition
return is_valid
func _on_Input_item_selected(index: int) -> void:
emit_signal("value_changed", get_input_string(), self)

View File

@@ -0,0 +1 @@
uid://8rtbm4ypixcs

View File

@@ -0,0 +1,43 @@
[gd_scene load_steps=4 format=3 uid="uid://dyunxqcmy4esi"]
[ext_resource type="Script" path="res://addons/mod_tool/interface/global/input_options.gd" id="1"]
[sub_resource type="Image" id="Image_43403"]
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, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 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": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_xi7aw"]
image = SubResource("Image_43403")
[node name="InputOptions" type="HBoxContainer"]
anchors_preset = 10
anchor_right = 1.0
offset_bottom = 32.0
script = ExtResource("1")
[node name="Label" type="Label" parent="."]
layout_mode = 2
size_flags_horizontal = 3
text = " (optional)"
clip_text = true
[node name="ErrorIcon" type="TextureRect" parent="."]
unique_name_in_owner = true
self_modulate = Color(1, 1, 1, 0)
custom_minimum_size = Vector2(32, 32)
layout_mode = 2
texture = SubResource("ImageTexture_xi7aw")
expand_mode = 1
stretch_mode = 6
[node name="Input" type="OptionButton" parent="."]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[connection signal="item_selected" from="Input" to="." method="_on_Input_item_selected"]

View File

@@ -0,0 +1,63 @@
@tool
class_name ModToolInterfaceInputString
extends ModToolInterfaceInput
@export var is_editable := true: set = set_is_editable
@export var input_text: String: set = set_input_text
@export var input_placeholder: String: set = set_input_placeholder
func set_input_text(new_text: String) -> void:
input_text = new_text
$"%Input".text = new_text
emit_signal("value_changed", new_text, self)
func set_input_placeholder(new_text: String) -> void:
input_placeholder = new_text
$"%Input".placeholder_text = new_text
func set_is_editable(new_is_editable: bool) -> void:
is_editable = new_is_editable
$"%Input".editable = new_is_editable
func get_input_value() -> String:
return $"%Input".text.strip_edges()
# Gets the values of a comma separated string as an Array,
# strips any white space contained in this values.
func get_input_as_array_from_comma_separated_string() -> Array:
var string_split := get_input_value().split(",", false)
var array := []
for string in string_split:
array.append(string.strip_edges())
return array
func validate(condition: bool) -> bool:
# Check if input is required and empty
if is_required and get_input_value() == "":
is_valid = false
return false
# Invalidate field if the condition is not met
self.is_valid = condition
return is_valid
func emit_value_changed() -> void:
emit_signal("value_changed", get_input_value(), self)
func _on_Input_text_changed(new_text: String) -> void:
emit_value_changed()
func _on_Mutiline_Input_text_changed() -> void:
emit_value_changed()

View File

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

View File

@@ -0,0 +1,43 @@
[gd_scene load_steps=4 format=3 uid="uid://icwo58h0rdb5"]
[ext_resource type="Script" path="res://addons/mod_tool/interface/global/input_string.gd" id="1"]
[sub_resource type="Image" id="Image_6wlu1"]
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, 43, 6, 255, 250, 10, 183, 255, 252, 10, 179, 255, 255, 0, 5, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 250, 14, 123, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 115, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 8, 30, 255, 250, 10, 245, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 242, 255, 255, 19, 26, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 250, 10, 174, 255, 250, 10, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 250, 10, 255, 255, 250, 12, 166, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 252, 13, 71, 255, 250, 10, 255, 255, 250, 10, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 250, 10, 255, 255, 250, 10, 255, 255, 254, 10, 65, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 43, 6, 255, 252, 10, 218, 255, 250, 10, 255, 255, 250, 10, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 213, 255, 255, 0, 5, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 250, 14, 123, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 115, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 8, 30, 255, 250, 10, 245, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 242, 255, 255, 19, 26, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 250, 10, 174, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 12, 166, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 252, 13, 71, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 254, 10, 65, 255, 255, 255, 0, 255, 255, 255, 0, 255, 250, 10, 213, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 207, 255, 255, 255, 0, 255, 255, 255, 0, 255, 250, 12, 191, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 250, 10, 255, 255, 252, 10, 187, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 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": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_5jdd3"]
image = SubResource("Image_6wlu1")
[node name="InputString" type="HBoxContainer"]
anchors_preset = 10
anchor_right = 1.0
offset_bottom = 24.0
script = ExtResource("1")
[node name="Label" type="Label" parent="."]
layout_mode = 2
size_flags_horizontal = 3
text = " (optional)"
clip_text = true
[node name="ErrorIcon" type="TextureRect" parent="."]
unique_name_in_owner = true
self_modulate = Color(1, 1, 1, 0)
custom_minimum_size = Vector2(32, 32)
layout_mode = 2
texture = SubResource("ImageTexture_5jdd3")
expand_mode = 1
stretch_mode = 6
[node name="Input" type="LineEdit" parent="."]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[connection signal="text_changed" from="Input" to="." method="_on_Input_text_changed"]

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
@tool
class_name ModToolInterfaceInputStringWithButton
extends ModToolInterfaceInputString
signal button_pressed

View File

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

View File

@@ -0,0 +1,65 @@
[gd_scene load_steps=4 format=2]
[ext_resource path="res://addons/mod_tool/interface/global/input_string_with_button.gd" type="Script" id=1]
[sub_resource type="Image" id=3]
data = {
"data": PackedByteArray( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 204, 51, 0, 255, 220, 100, 0, 255, 219, 100, 0, 255, 204, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 204, 51, 0, 255, 204, 51, 5, 255, 220, 100, 178, 255, 219, 100, 182, 255, 204, 51, 5, 255, 204, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 217, 98, 0, 255, 219, 99, 115, 255, 221, 101, 255, 255, 221, 101, 255, 255, 219, 99, 115, 255, 216, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 215, 98, 0, 255, 215, 98, 26, 255, 220, 100, 241, 255, 221, 101, 255, 255, 221, 101, 255, 255, 220, 100, 241, 255, 214, 91, 25, 255, 214, 91, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 221, 98, 0, 255, 221, 100, 165, 255, 221, 101, 255, 255, 221, 101, 0, 255, 221, 101, 0, 255, 221, 101, 255, 255, 221, 100, 165, 255, 221, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 206, 80, 0, 255, 221, 97, 60, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 0, 255, 221, 101, 0, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 97, 60, 255, 206, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 191, 63, 0, 255, 191, 63, 4, 255, 221, 100, 210, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 0, 255, 221, 101, 0, 255, 221, 101, 255, 255, 221, 101, 255, 255, 220, 100, 209, 255, 191, 63, 4, 255, 191, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 219, 98, 0, 255, 220, 99, 110, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 0, 255, 221, 101, 0, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 220, 99, 110, 255, 219, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 218, 97, 0, 255, 218, 97, 21, 255, 220, 100, 239, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 0, 255, 221, 101, 0, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 220, 100, 239, 255, 218, 97, 21, 255, 218, 97, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 218, 100, 0, 255, 219, 100, 160, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 219, 100, 160, 255, 218, 100, 0, 0, 0, 0, 0, 255, 218, 100, 0, 255, 218, 100, 56, 255, 220, 100, 254, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 0, 255, 221, 101, 0, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 220, 100, 254, 255, 218, 100, 56, 255, 218, 100, 0, 255, 220, 100, 0, 255, 220, 100, 193, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 0, 255, 221, 101, 0, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 220, 100, 193, 255, 220, 100, 0, 255, 221, 100, 0, 255, 221, 100, 165, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 101, 255, 255, 221, 100, 165, 255, 221, 100, 0, 0, 0, 0, 0, 255, 221, 100, 0, 255, 221, 101, 0, 255, 221, 101, 0, 255, 221, 101, 0, 255, 221, 101, 0, 255, 221, 101, 0, 255, 221, 101, 0, 255, 221, 101, 0, 255, 221, 101, 0, 255, 221, 101, 0, 255, 221, 101, 0, 255, 221, 101, 0, 255, 221, 101, 0, 255, 221, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id=2]
flags = 0
flags = 0
image = SubResource( 3 )
size = Vector2( 16, 16 )
[node name="InputStringWithButton" type="HBoxContainer"]
anchor_right = 1.0
offset_bottom = 24.0
script = ExtResource( 1 )
[node name="Label" type="Label" parent="."]
offset_top = 9.0
offset_right = 492.0
offset_bottom = 23.0
size_flags_horizontal = 3
text = " (optional)"
clip_text = true
[node name="ErrorIcon" type="TextureRect" parent="."]
unique_name_in_owner = true
self_modulate = Color( 1, 1, 1, 0 )
offset_left = 496.0
offset_right = 528.0
offset_bottom = 32.0
custom_minimum_size = Vector2( 32, 32 )
texture = SubResource( 2 )
expand = true
stretch_mode = 6
[node name="HBoxContainer" type="HBoxContainer" parent="."]
offset_left = 532.0
offset_right = 1024.0
offset_bottom = 32.0
size_flags_horizontal = 3
[node name="Input" type="LineEdit" parent="HBoxContainer"]
unique_name_in_owner = true
offset_right = 456.0
offset_bottom = 32.0
size_flags_horizontal = 3
placeholder_alpha = 0.5
[node name="Button" type="Button" parent="HBoxContainer"]
offset_left = 460.0
offset_right = 492.0
offset_bottom = 32.0
size_flags_vertical = 3
text = " ... "
[connection signal="text_changed" from="HBoxContainer/Input" to="." method="_on_Input_text_changed"]
[connection signal="pressed" from="HBoxContainer/Button" to="." method="emit_signal" binds= [ "button_pressed" ]]

View File

@@ -0,0 +1,40 @@
@tool
extends VSplitContainer
var previous_size := 0
var is_pressed := false
@onready var text_edit := get_child(0) as TextEdit
@onready var min_size_y := text_edit.custom_minimum_size.y as int
func _ready() -> void:
connect("dragged", Callable(self, "_on_dragged"))
connect("gui_input", Callable(self, "_on_gui_input"))
if get_child_count() < 2:
add_child(Control.new())
func _on_dragged(offset: int) -> void:
# offset is cumulative for the whole drag move
var new_size := previous_size + offset
if new_size < min_size_y:
text_edit.custom_minimum_size.y = min_size_y
else:
text_edit.custom_minimum_size.y = new_size
func _on_gui_input(event: InputEvent) -> void:
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT:
if event.pressed and not is_pressed:
is_pressed = true
previous_size = text_edit.custom_minimum_size.y
else:
is_pressed = false
func _get_configuration_warnings() -> PackedStringArray:
if not get_child(0) is TextEdit:
return ["First child needs to be a TextEdit"]
return [""]

View File

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

View File

@@ -0,0 +1,63 @@
@tool
class_name ModToolInterfaceHookGen
extends Window
signal hooks_exist_pressed
@onready var mod_tool_store: ModToolStore = get_node_or_null("/root/ModToolStore")
@onready var info_output: RichTextLabel = %InfoOutput
@onready var restart: Window = %Restart
@onready var button_gen_start: Button = %ButtonGenStart
func generate_hooks() -> void:
# Get all script not in addons or mods-unpacked
var all_script_file_paths := ModToolUtils.get_flat_view_dict("res://", "", [&"gd"], false, false, [&"addons", &"mods-unpacked"])
for script_file_path in all_script_file_paths:
if mod_tool_store.hooked_scripts.has(script_file_path):
info_output.add_text("Skipping - Hooks already exists for \"%s\" \n" % script_file_path)
continue
var error := ModToolHookGen.transform_one(script_file_path, mod_tool_store)
if not error == OK:
info_output.add_text("ERROR: Accessing file at path \"%s\" failed with error: %s \n" % [script_file_path, error_string(error)])
else:
info_output.add_text("Added Hooks for \"%s\" \n" % script_file_path)
mod_tool_store.is_hook_generation_done = true
info_output.add_text("Mod Hook generation completed successfully!\n")
mod_tool_store.save_store()
restart.show()
func _on_button_pressed() -> void:
button_gen_start.disabled = true
generate_hooks()
func _on_close_requested() -> void:
hide()
func _on_button_restart_now_pressed() -> void:
await get_tree().create_timer(1.0).timeout
EditorInterface.restart_editor()
func _on_button_restart_later_pressed() -> void:
restart.hide()
hide()
func _on_restart_close_requested() -> void:
restart.hide()
func _on_button_hooks_exist_pressed() -> void:
mod_tool_store.is_hook_generation_done = true
hooks_exist_pressed.emit()
hide()

View File

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

View File

@@ -0,0 +1,105 @@
[gd_scene load_steps=2 format=3 uid="uid://cpll5clcnemyj"]
[ext_resource type="Script" path="res://addons/mod_tool/interface/hook_gen/hook_gen.gd" id="1_lrahv"]
[node name="HookGen" type="Window"]
title = "Mod Dev Tool"
initial_position = 1
size = Vector2i(640, 375)
wrap_controls = true
script = ExtResource("1_lrahv")
[node name="Restart" type="Window" parent="."]
unique_name_in_owner = true
title = "Mod Dev Tool"
initial_position = 1
size = Vector2i(440, 117)
visible = false
wrap_controls = true
[node name="MarginContainer" type="MarginContainer" parent="Restart"]
offset_right = 40.0
offset_bottom = 40.0
theme_override_constants/margin_left = 20
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 20
theme_override_constants/margin_bottom = 10
[node name="VBC" type="VBoxContainer" parent="Restart/MarginContainer"]
layout_mode = 2
theme_override_constants/separation = 20
[node name="LabelInfoText" type="RichTextLabel" parent="Restart/MarginContainer/VBC"]
custom_minimum_size = Vector2(400, 0)
layout_mode = 2
text = "Successfully generated mod hooks.
To start modding, a restart of the editor is required."
fit_content = true
[node name="HBC" type="HBoxContainer" parent="Restart/MarginContainer/VBC"]
layout_mode = 2
size_flags_horizontal = 4
theme_override_constants/separation = 30
[node name="ButtonRestartNow" type="Button" parent="Restart/MarginContainer/VBC/HBC"]
layout_mode = 2
text = "Restart Now"
[node name="ButtonRestartLater" type="Button" parent="Restart/MarginContainer/VBC/HBC"]
layout_mode = 2
text = "Restart Later"
[node name="MarginContainer" type="MarginContainer" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 20
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 20
theme_override_constants/margin_bottom = 15
[node name="VBC" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
theme_override_constants/separation = 20
[node name="LabelInfoText" type="RichTextLabel" parent="MarginContainer/VBC"]
custom_minimum_size = Vector2(600, 0)
layout_mode = 2
text = "This will modify all existing scripts in the project, so please make sure to save your files before continuing.
The process may take some time depending on the number of scripts."
fit_content = true
[node name="VBC" type="VBoxContainer" parent="MarginContainer/VBC"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="ButtonGenStart" type="Button" parent="MarginContainer/VBC/VBC"]
unique_name_in_owner = true
layout_mode = 2
text = "Generate mod hooks"
[node name="ButtonHooksExist" type="Button" parent="MarginContainer/VBC/VBC"]
unique_name_in_owner = true
layout_mode = 2
text = "Hooks already exist"
[node name="VBC2" type="VBoxContainer" parent="MarginContainer/VBC"]
layout_mode = 2
theme_override_constants/separation = 15
[node name="InfoOutput" type="RichTextLabel" parent="MarginContainer/VBC/VBC2"]
unique_name_in_owner = true
custom_minimum_size = Vector2(600, 100)
layout_mode = 2
size_flags_vertical = 3
scroll_following = true
[connection signal="close_requested" from="." to="." method="_on_close_requested"]
[connection signal="close_requested" from="Restart" to="." method="_on_restart_close_requested"]
[connection signal="pressed" from="Restart/MarginContainer/VBC/HBC/ButtonRestartNow" to="." method="_on_button_restart_now_pressed"]
[connection signal="pressed" from="Restart/MarginContainer/VBC/HBC/ButtonRestartLater" to="." method="_on_button_restart_later_pressed"]
[connection signal="pressed" from="MarginContainer/VBC/VBC/ButtonGenStart" to="." method="_on_button_pressed"]
[connection signal="pressed" from="MarginContainer/VBC/VBC/ButtonHooksExist" to="." method="_on_button_hooks_exist_pressed"]

View File

@@ -0,0 +1,59 @@
@tool
class_name ModToolInterfaceHookRestore
extends Window
@onready var mod_tool_store: ModToolStore = get_node_or_null("/root/ModToolStore")
@onready var info_output: RichTextLabel = %InfoOutput
@onready var restart: Window = %Restart
@onready var button_restore_start: Button = %ButtonRestoreStart
func start_restore() -> void:
# Get all script not in addons or mods-unpacked
var all_script_file_paths := ModToolUtils.get_flat_view_dict("res://", "", [&"gd"], false, false, [&"addons", &"mods-unpacked"])
var encountered_error := false
for script_path in mod_tool_store.hooked_scripts.keys():
var error := ModToolHookGen.restore(script_path, mod_tool_store)
if not error == OK:
encountered_error = true
info_output.add_text("ERROR: Accessing file at path \"%s\" failed with error: %s \n" % [script_path, error_string(error)])
break
info_output.add_text("Restored \"%s\" \n" % script_path)
if encountered_error:
info_output.add_text("ERROR: Restore aborted.\n")
else:
mod_tool_store.is_hook_generation_done = false
info_output.add_text("Restore completed successfully!\n")
mod_tool_store.save_store()
restart.show()
func _on_close_requested() -> void:
hide()
func _on_button_restart_now_pressed() -> void:
await get_tree().create_timer(1.0).timeout
EditorInterface.restart_editor()
func _on_button_restart_later_pressed() -> void:
restart.hide()
hide()
func _on_restart_close_requested() -> void:
restart.hide()
func _on_button_restore_start_pressed() -> void:
button_restore_start.disabled = true
start_restore()

View File

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

View File

@@ -0,0 +1,100 @@
[gd_scene load_steps=2 format=3 uid="uid://camcc83bvu086"]
[ext_resource type="Script" path="res://addons/mod_tool/interface/hook_restore/hook_restore.gd" id="1_wq3ld"]
[node name="HookRestore" type="Window"]
title = "Mod Dev Tool"
initial_position = 1
size = Vector2i(640, 375)
wrap_controls = true
script = ExtResource("1_wq3ld")
[node name="Restart" type="Window" parent="."]
unique_name_in_owner = true
title = "Mod Dev Tool"
initial_position = 1
size = Vector2i(440, 117)
visible = false
wrap_controls = true
[node name="MarginContainer" type="MarginContainer" parent="Restart"]
offset_right = 40.0
offset_bottom = 40.0
theme_override_constants/margin_left = 20
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 20
theme_override_constants/margin_bottom = 10
[node name="VBC" type="VBoxContainer" parent="Restart/MarginContainer"]
layout_mode = 2
theme_override_constants/separation = 20
alignment = 1
[node name="LabelInfoText" type="RichTextLabel" parent="Restart/MarginContainer/VBC"]
custom_minimum_size = Vector2(400, 0)
layout_mode = 2
text = "Successfully restored all scripts.
A restart of the editor is required."
fit_content = true
[node name="HBC" type="HBoxContainer" parent="Restart/MarginContainer/VBC"]
layout_mode = 2
size_flags_horizontal = 4
theme_override_constants/separation = 30
[node name="ButtonRestartNow" type="Button" parent="Restart/MarginContainer/VBC/HBC"]
layout_mode = 2
text = "Restart Now"
[node name="ButtonRestartLater" type="Button" parent="Restart/MarginContainer/VBC/HBC"]
layout_mode = 2
text = "Restart Later"
[node name="MarginContainer" type="MarginContainer" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 20
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 20
theme_override_constants/margin_bottom = 15
[node name="VBC" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
theme_override_constants/separation = 20
[node name="LabelInfoText" type="RichTextLabel" parent="MarginContainer/VBC"]
custom_minimum_size = Vector2(600, 0)
layout_mode = 2
text = "This will restore all scripts to their state before mod hooks were added. Any changes made to these scripts will be lost!
The process may take some time depending on the number of scripts."
fit_content = true
[node name="VBC" type="VBoxContainer" parent="MarginContainer/VBC"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="ButtonRestoreStart" type="Button" parent="MarginContainer/VBC/VBC"]
unique_name_in_owner = true
layout_mode = 2
text = "Start Restore"
[node name="VBC2" type="VBoxContainer" parent="MarginContainer/VBC"]
layout_mode = 2
theme_override_constants/separation = 15
[node name="InfoOutput" type="RichTextLabel" parent="MarginContainer/VBC/VBC2"]
unique_name_in_owner = true
custom_minimum_size = Vector2(600, 100)
layout_mode = 2
size_flags_vertical = 3
scroll_following = true
[connection signal="close_requested" from="." to="." method="_on_close_requested"]
[connection signal="close_requested" from="Restart" to="." method="_on_restart_close_requested"]
[connection signal="pressed" from="Restart/MarginContainer/VBC/HBC/ButtonRestartNow" to="." method="_on_button_restart_now_pressed"]
[connection signal="pressed" from="Restart/MarginContainer/VBC/HBC/ButtonRestartLater" to="." method="_on_button_restart_later_pressed"]
[connection signal="pressed" from="MarginContainer/VBC/VBC/ButtonRestoreStart" to="." method="_on_button_restore_start_pressed"]

View File

@@ -0,0 +1,278 @@
@tool
extends PanelContainer
var input_fields := []
@onready var mod_tool_store: ModToolStore = get_node_or_null("/root/ModToolStore")
@onready var manifest_input_vbox := $"%InputVBox"
@onready var input_incompatibilities: ModToolInterfaceInputString = $"%Incompatibilities"
@onready var input_dependencies: ModToolInterfaceInputString = $"%Dependencies"
@onready var input_optional_dependencies: ModToolInterfaceInputString = $"%OptionalDependencies"
@onready var input_load_before: ModToolInterfaceInputString = $"%LoadBefore"
func _ready() -> void:
$VBox/Panel.add_theme_stylebox_override("panel", ThemeDB.get_default_theme().get_stylebox("bg", "ItemList"))
# Setup input fields
for node in manifest_input_vbox.get_children():
if node is ModToolInterfaceInputString:
input_fields.append(node)
func load_manifest() -> void:
var manifest_dict_json := _ModLoaderFile.get_json_as_dict(mod_tool_store.path_manifest)
mod_tool_store.manifest_data = ModManifest.new(manifest_dict_json, mod_tool_store.path_mod_dir)
ModToolUtils.output_info("Loaded manifest from " + mod_tool_store.path_manifest)
func save_manifest() -> void:
var invalid_inputs := get_invalid()
if invalid_inputs.size() == 0:
var _is_success := ModToolUtils.save_to_manifest_json(mod_tool_store.manifest_data, mod_tool_store.path_manifest)
else:
ModToolUtils.output_error('Invalid Manifest - Manifest not saved! Please check your inputs in the following fields -> ' + ", ".join(invalid_inputs))
func update_ui() -> void:
# For each input field
for input in input_fields:
# Check if the key used in the ModToolInterfaceInputString instance is in the data_dict.
if mod_tool_store.manifest_data.get(input.key):
var value = mod_tool_store.manifest_data.get(input.key)
# If the value is an Array create a comma separated list
if value is PackedStringArray:
input.input_text = ", ".join(value)
# Else convert the value to a string
else:
input.input_text = str(value)
# If the key is not in the data clear the input
else:
input.input_text = ""
# Returns an array of invalid fields
func get_invalid() -> Array:
var invalid_fields := []
for input in input_fields:
if not input.is_valid:
invalid_fields.append(input.label_text)
return invalid_fields
func _update_manifest_value(input: ModToolInterfaceInputString, new_value) -> void:
if mod_tool_store.manifest_data:
mod_tool_store.manifest_data.set(input.key, new_value)
func _on_SaveManifest_pressed() -> void:
save_manifest()
# Validated StringInputs
# =============================================================================
func _on_ModName_value_changed(new_text: String, input_node: ModToolInterfaceInputString) -> void:
_update_manifest_value(input_node, new_text)
input_node.validate(mod_tool_store.manifest_data.is_name_or_namespace_valid(new_text, true))
func _on_Namespace_value_changed(new_text: String, input_node: ModToolInterfaceInputString) -> void:
_update_manifest_value(input_node, new_text)
input_node.validate(mod_tool_store.manifest_data.is_name_or_namespace_valid(new_text, true))
func _on_Version_value_changed(new_text: String, input_node: ModToolInterfaceInputString) -> void:
_update_manifest_value(input_node, new_text)
input_node.validate(mod_tool_store.manifest_data.is_semver_valid("", new_text, "version", true))
# When dealing with Inputs that depend on other Inputs, the `input_node` is not utilized.
# This is because the `value_changed` signal is connected to this method for all relevant inputs.
# As a result, the input_node would retrieve multiple different nodes, which should not be updated but rather revalidated.
# In such cases, the input node is directly referenced to prevent overwriting the values in other input fields.
func _on_Dependencies_value_changed(new_text: String, input_node: ModToolInterfaceInputString, validate_only: bool) -> void:
var dependencies: PackedStringArray
if validate_only:
dependencies = mod_tool_store.manifest_data.dependencies
else:
dependencies = input_dependencies.get_input_as_array_from_comma_separated_string()
_update_manifest_value(input_dependencies, dependencies)
var is_id_array_valid := mod_tool_store.manifest_data.is_mod_id_array_valid(mod_tool_store.name_mod_dir, dependencies, "dependencies", true)
var is_distinct_mod_id_incompatibilities := mod_tool_store.manifest_data.validate_distinct_mod_ids_in_arrays(
mod_tool_store.name_mod_dir,
dependencies,
mod_tool_store.manifest_data.incompatibilities,
["dependencies", "incompatibilities"],
"",
true
)
var is_distinct_mod_id_optional_dependencies := mod_tool_store.manifest_data.validate_distinct_mod_ids_in_arrays(
mod_tool_store.name_mod_dir,
dependencies,
mod_tool_store.manifest_data.optional_dependencies,
["dependencies", "optional_dependencies"],
"",
true
)
input_dependencies.validate(
is_id_array_valid and
is_distinct_mod_id_incompatibilities and
is_distinct_mod_id_optional_dependencies
)
# When dealing with Inputs that depend on other Inputs, the `input_node` is not utilized.
# This is because the `value_changed` signal is connected to this method for all relevant inputs.
# As a result, the input_node would retrieve multiple different nodes, which should not be updated but rather revalidated.
# In such cases, the input node is directly referenced to prevent overwriting the values in other input fields.
func _on_OptionalDependencies_value_changed(new_text: String, input_node: ModToolInterfaceInputString, validate_only: bool) -> void:
var optional_dependencies: PackedStringArray
if validate_only:
optional_dependencies = mod_tool_store.manifest_data.optional_dependencies
else:
optional_dependencies = input_optional_dependencies.get_input_as_array_from_comma_separated_string()
_update_manifest_value(input_optional_dependencies, optional_dependencies)
var is_id_array_valid := mod_tool_store.manifest_data.is_mod_id_array_valid(mod_tool_store.name_mod_dir, optional_dependencies, "optional_dependencies", true)
var is_distinct_mod_id_incompatibilities := mod_tool_store.manifest_data.validate_distinct_mod_ids_in_arrays(
mod_tool_store.name_mod_dir,
optional_dependencies,
mod_tool_store.manifest_data.incompatibilities,
["optional_dependencies", "incompatibilities"],
"",
true
)
var is_distinct_mod_id_dependencies := mod_tool_store.manifest_data.validate_distinct_mod_ids_in_arrays(
mod_tool_store.name_mod_dir,
optional_dependencies,
mod_tool_store.manifest_data.dependencies,
["optional_dependencies", "dependencies"],
"",
true
)
input_optional_dependencies.validate(
is_id_array_valid and
is_distinct_mod_id_incompatibilities and
is_distinct_mod_id_dependencies
)
func _on_CompatibleModLoaderVersions_value_changed(new_text: String, input_node: ModToolInterfaceInputString) -> void:
var compatible_modloader_versions := input_node.get_input_as_array_from_comma_separated_string()
_update_manifest_value(input_node, compatible_modloader_versions)
input_node.validate(mod_tool_store.manifest_data.is_semver_version_array_valid(mod_tool_store.name_mod_dir, compatible_modloader_versions, "Compatible ModLoader Versions", true))
# When dealing with Inputs that depend on other Inputs, the `input_node` is not utilized.
# This is because the `value_changed` signal is connected to this method for all relevant inputs.
# As a result, the input_node would retrieve multiple different nodes, which should not be updated but rather revalidated.
# In such cases, the input node is directly referenced to prevent overwriting the values in other input fields.
func _on_Incompatibilities_value_changed(new_text: String, input_node: ModToolInterfaceInputString, validate_only: bool) -> void:
var incompatibilities: PackedStringArray
if validate_only:
incompatibilities = mod_tool_store.manifest_data.incompatibilities
else:
incompatibilities = input_incompatibilities.get_input_as_array_from_comma_separated_string()
_update_manifest_value(input_incompatibilities, incompatibilities)
var is_mod_id_array_valid := mod_tool_store.manifest_data.is_mod_id_array_valid(mod_tool_store.name_mod_dir, incompatibilities, "incompatibilities", true)
var is_distinct_mod_id_dependencies := mod_tool_store.manifest_data.validate_distinct_mod_ids_in_arrays(
mod_tool_store.name_mod_dir,
mod_tool_store.manifest_data.dependencies,
incompatibilities,
["dependencies", "incompatibilities"],
"",
true
)
var is_distinct_mod_id_optional_dependencies := mod_tool_store.manifest_data.validate_distinct_mod_ids_in_arrays(
mod_tool_store.name_mod_dir,
mod_tool_store.manifest_data.optional_dependencies,
incompatibilities,
["optional_dependencies", "incompatibilities"],
"",
true
)
input_incompatibilities.validate(
is_mod_id_array_valid and
is_distinct_mod_id_dependencies and
is_distinct_mod_id_optional_dependencies
)
# When dealing with Inputs that depend on other Inputs, the `input_node` is not utilized.
# This is because the `value_changed` signal is connected to this method for all relevant inputs.
# As a result, the input_node would retrieve multiple different nodes, which should not be updated but rather revalidated.
# In such cases, the input node is directly referenced to prevent overwriting the values in other input fields.
func _on_LoadBefore_value_changed(new_text: String, input_node: ModToolInterfaceInputString, validate_only: bool) -> void:
var load_before: PackedStringArray
if validate_only:
load_before = mod_tool_store.manifest_data.load_before
else:
load_before = input_load_before.get_input_as_array_from_comma_separated_string()
_update_manifest_value(input_load_before, load_before)
var is_mod_id_array_valid := mod_tool_store.manifest_data.is_mod_id_array_valid(mod_tool_store.name_mod_dir, load_before, "load_before", true)
var is_distinct_mod_id_dependencies := mod_tool_store.manifest_data.validate_distinct_mod_ids_in_arrays(
mod_tool_store.name_mod_dir,
load_before,
mod_tool_store.manifest_data.dependencies,
["load_before", "dependencies"],
"\"load_before\" should be handled as optional dependency adding it to \"dependencies\" will cancel out the desired effect.",
true
)
var is_distinct_mod_id_optional_dependencies := mod_tool_store.manifest_data.validate_distinct_mod_ids_in_arrays(
mod_tool_store.name_mod_dir,
load_before,
mod_tool_store.manifest_data.optional_dependencies,
["load_before", "optional_dependencies"],
"\"load_before\" can be viewed as optional dependency, please remove the duplicate mod-id.",
true
)
input_load_before.validate(
is_mod_id_array_valid and
is_distinct_mod_id_dependencies and
is_distinct_mod_id_optional_dependencies
)
# Non Validated StringInputs
# =============================================================================
func _on_WebsiteUrl_value_changed(new_text: String, input_node: ModToolInterfaceInputString) -> void:
_update_manifest_value(input_node, new_text)
func _on_Description_value_changed(new_text: String, input_node: ModToolInterfaceInputString) -> void:
_update_manifest_value(input_node, new_text)
func _on_Authors_value_changed(new_text: String, input_node: ModToolInterfaceInputString) -> void:
var authors := input_node.get_input_as_array_from_comma_separated_string()
_update_manifest_value(input_node, authors)
func _on_CompatibleGameVersions_value_changed(new_text: String, input_node: ModToolInterfaceInputString) -> void:
var compatible_game_versions := input_node.get_input_as_array_from_comma_separated_string()
_update_manifest_value(input_node, compatible_game_versions)
func _on_Tags_value_changed(new_text: String, input_node: ModToolInterfaceInputString) -> void:
var tags := input_node.get_input_as_array_from_comma_separated_string()
_update_manifest_value(input_node, tags)

View File

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

View File

@@ -0,0 +1,289 @@
[gd_scene load_steps=5 format=3 uid="uid://hpefgw6k5qpq"]
[ext_resource type="PackedScene" path="res://addons/mod_tool/interface/global/input_string_multiline.tscn" id="1"]
[ext_resource type="PackedScene" path="res://addons/mod_tool/interface/global/input_string.tscn" id="2"]
[ext_resource type="Script" path="res://addons/mod_tool/interface/manifest_editor/manifest_editor.gd" id="4"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_pun0q"]
content_margin_left = 4.0
content_margin_top = 4.0
content_margin_right = 4.0
content_margin_bottom = 4.0
bg_color = Color(1, 0.365, 0.365, 1)
draw_center = false
border_width_left = 2
border_width_top = 2
border_width_right = 2
border_width_bottom = 2
corner_detail = 1
[node name="ModManifest" type="PanelContainer"]
unique_name_in_owner = true
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource("4")
[node name="VBox" type="VBoxContainer" parent="."]
layout_mode = 2
[node name="HBox2" type="HBoxContainer" parent="VBox"]
layout_mode = 2
[node name="Label" type="Label" parent="VBox/HBox2"]
layout_mode = 2
size_flags_horizontal = 3
text = "Metadata required for your mod"
[node name="ErrorLabel" type="Label" parent="VBox/HBox2"]
unique_name_in_owner = true
layout_mode = 2
text = "Manifest is valid"
[node name="ShouldValidate" type="CheckButton" parent="VBox/HBox2"]
unique_name_in_owner = true
layout_mode = 2
flat = true
[node name="VSeparator" type="VSeparator" parent="VBox/HBox2"]
layout_mode = 2
[node name="SaveManifest" type="Button" parent="VBox/HBox2"]
layout_mode = 2
text = "Save to manifest.json"
[node name="Panel" type="PanelContainer" parent="VBox"]
layout_mode = 2
size_flags_vertical = 3
theme_override_styles/panel = SubResource("StyleBoxFlat_pun0q")
[node name="ScrollContainer" type="ScrollContainer" parent="VBox/Panel"]
layout_mode = 2
size_flags_vertical = 3
follow_focus = true
[node name="InputVBox" type="VBoxContainer" parent="VBox/Panel/ScrollContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="Category" type="LineEdit" parent="VBox/Panel/ScrollContainer/InputVBox"]
layout_mode = 2
mouse_filter = 2
mouse_default_cursor_shape = 0
text = "Manifest"
editable = false
context_menu_enabled = false
virtual_keyboard_enabled = false
shortcut_keys_enabled = false
middle_mouse_paste_enabled = false
[node name="ModName" parent="VBox/Panel/ScrollContainer/InputVBox" instance=ExtResource("2")]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Name of the Mod.
Only letters, numbers and underscores allowed."
mouse_default_cursor_shape = 16
is_required = true
key = "name"
label_text = "Mod Name"
hint_text = "Name of the Mod.
Only letters, numbers and underscores allowed."
[node name="Namespace" parent="VBox/Panel/ScrollContainer/InputVBox" instance=ExtResource("2")]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Namespace of the Mod.
Often just the main author or team name.
Only letters, numbers and underscores allowed."
mouse_default_cursor_shape = 16
is_required = true
key = "mod_namespace"
label_text = "Namespace"
hint_text = "Namespace of the Mod.
Often just the main author or team name.
Only letters, numbers and underscores allowed."
[node name="Version" parent="VBox/Panel/ScrollContainer/InputVBox" instance=ExtResource("2")]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Semantic version string.
Only integers and periods allowed.
Format: {major}.{minor}.{patch}
For reference, see https://semver.org"
mouse_default_cursor_shape = 16
input_text = "0.0.1"
is_required = true
key = "version_number"
label_text = "Version"
hint_text = "Semantic version string.
Only integers and periods allowed.
Format: {major}.{minor}.{patch}
For reference, see https://semver.org"
[node name="WebsiteUrl" parent="VBox/Panel/ScrollContainer/InputVBox" instance=ExtResource("2")]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "URL for your website or repository."
mouse_default_cursor_shape = 16
input_placeholder = "https://example.com"
key = "website_url"
label_text = "Mod website URL"
hint_text = "URL for your website or repository."
[node name="Dependencies" parent="VBox/Panel/ScrollContainer/InputVBox" instance=ExtResource("2")]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Comma-separated list of mod IDs.
Only letters, numbers and underscores allowed.
A single dash in the middle is required.
Format: {namespace}-{name}
Dependencies can't be in Incompatibilities or Optional Dependencies.
"
mouse_default_cursor_shape = 16
input_placeholder = "Namespace-ModName, Author-Name"
key = "dependencies"
label_text = "Dependencies"
hint_text = "Comma-separated list of mod IDs.
Only letters, numbers and underscores allowed.
A single dash in the middle is required.
Format: {namespace}-{name}
Dependencies can't be in Incompatibilities or Optional Dependencies.
"
[node name="Description" parent="VBox/Panel/ScrollContainer/InputVBox" instance=ExtResource("1")]
unique_name_in_owner = true
layout_mode = 2
key = "description"
label_text = "Description"
[node name="Category2" type="LineEdit" parent="VBox/Panel/ScrollContainer/InputVBox"]
layout_mode = 2
mouse_filter = 2
mouse_default_cursor_shape = 0
text = "Manifest Extra"
editable = false
context_menu_enabled = false
virtual_keyboard_enabled = false
shortcut_keys_enabled = false
middle_mouse_paste_enabled = false
[node name="Authors" parent="VBox/Panel/ScrollContainer/InputVBox" instance=ExtResource("2")]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Comma-separated list of Authors"
mouse_default_cursor_shape = 16
input_placeholder = "Author1, Autor2"
key = "authors"
label_text = "Authors"
hint_text = "Comma-separated list of Authors"
[node name="Incompatibilities" parent="VBox/Panel/ScrollContainer/InputVBox" instance=ExtResource("2")]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Comma-separated list of mod IDs.
Only letters, numbers and underscores allowed.
A single dash in the middle is required.
Format: {namespace}-{name}
Incompatible Mods can't be in dependencies or optional dependencies."
mouse_default_cursor_shape = 16
input_placeholder = "Namespace-ModName, Author-Name"
key = "incompatibilities"
label_text = "Incompatible Mods"
hint_text = "Comma-separated list of mod IDs.
Only letters, numbers and underscores allowed.
A single dash in the middle is required.
Format: {namespace}-{name}
Incompatible Mods can't be in dependencies or optional dependencies."
[node name="OptionalDependencies" parent="VBox/Panel/ScrollContainer/InputVBox" instance=ExtResource("2")]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Comma-separated list of mod IDs.
Only letters, numbers and underscores allowed.
A single dash in the middle is required.
Format: {namespace}-{name}
Optional Dependencies can't be in Incompatibilities or Dependencies."
mouse_default_cursor_shape = 16
input_placeholder = "Namespace-ModName, Author-Name"
key = "optional_dependencies"
label_text = "Optional Dependencies"
hint_text = "Comma-separated list of mod IDs.
Only letters, numbers and underscores allowed.
A single dash in the middle is required.
Format: {namespace}-{name}
Optional Dependencies can't be in Incompatibilities or Dependencies."
[node name="LoadBefore" parent="VBox/Panel/ScrollContainer/InputVBox" instance=ExtResource("2")]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Comma-separated list of mod IDs.
Only letters, numbers and underscores allowed.
A single dash in the middle is required.
Format: {namespace}-{name}
Should be handled as optional dependency adding it to \"dependencies\" will cancel out the desired effect.
Can be viewed as optional dependency, please remove duplicate mod-id from \"optional_dependencies\"."
mouse_default_cursor_shape = 16
input_placeholder = "Namespace-ModName, Author-Name"
key = "load_before"
label_text = "Load Before"
hint_text = "Comma-separated list of mod IDs.
Only letters, numbers and underscores allowed.
A single dash in the middle is required.
Format: {namespace}-{name}
Should be handled as optional dependency adding it to \"dependencies\" will cancel out the desired effect.
Can be viewed as optional dependency, please remove duplicate mod-id from \"optional_dependencies\"."
[node name="CompatibleGameVersions" parent="VBox/Panel/ScrollContainer/InputVBox" instance=ExtResource("2")]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Comma-separated list of valid game versions."
mouse_default_cursor_shape = 16
input_placeholder = "1.0.0, 1.2.0"
key = "compatible_game_version"
label_text = "Compatible Game Versions"
hint_text = "Comma-separated list of valid game versions."
[node name="CompatibleModLoaderVersions" parent="VBox/Panel/ScrollContainer/InputVBox" instance=ExtResource("2")]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Comma-separated list of ModLoader versions."
mouse_default_cursor_shape = 16
input_placeholder = "5.0.0, 4.1.0"
is_required = true
key = "compatible_mod_loader_version"
label_text = "Compatible Mod Loader Versions"
hint_text = "Comma-separated list of ModLoader versions."
[node name="Tags" parent="VBox/Panel/ScrollContainer/InputVBox" instance=ExtResource("2")]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Comma-separated list of tags that describe your mod.."
mouse_default_cursor_shape = 16
input_placeholder = "Tag1, Tag2"
key = "tags"
label_text = "Tags"
hint_text = "Comma-separated list of tags that describe your mod.."
[connection signal="pressed" from="VBox/HBox2/SaveManifest" to="." method="_on_SaveManifest_pressed"]
[connection signal="value_changed" from="VBox/Panel/ScrollContainer/InputVBox/ModName" to="." method="_on_ModName_value_changed"]
[connection signal="value_changed" from="VBox/Panel/ScrollContainer/InputVBox/Namespace" to="." method="_on_Namespace_value_changed"]
[connection signal="value_changed" from="VBox/Panel/ScrollContainer/InputVBox/Version" to="." method="_on_Version_value_changed"]
[connection signal="value_changed" from="VBox/Panel/ScrollContainer/InputVBox/WebsiteUrl" to="." method="_on_WebsiteUrl_value_changed"]
[connection signal="value_changed" from="VBox/Panel/ScrollContainer/InputVBox/Dependencies" to="." method="_on_OptionalDependencies_value_changed" flags=3 binds= [true]]
[connection signal="value_changed" from="VBox/Panel/ScrollContainer/InputVBox/Dependencies" to="." method="_on_Incompatibilities_value_changed" flags=3 binds= [true]]
[connection signal="value_changed" from="VBox/Panel/ScrollContainer/InputVBox/Dependencies" to="." method="_on_Dependencies_value_changed" binds= [false]]
[connection signal="value_changed" from="VBox/Panel/ScrollContainer/InputVBox/Description" to="." method="_on_Description_value_changed"]
[connection signal="value_changed" from="VBox/Panel/ScrollContainer/InputVBox/Authors" to="." method="_on_Authors_value_changed"]
[connection signal="value_changed" from="VBox/Panel/ScrollContainer/InputVBox/Incompatibilities" to="." method="_on_OptionalDependencies_value_changed" flags=3 binds= [true]]
[connection signal="value_changed" from="VBox/Panel/ScrollContainer/InputVBox/Incompatibilities" to="." method="_on_Incompatibilities_value_changed" binds= [false]]
[connection signal="value_changed" from="VBox/Panel/ScrollContainer/InputVBox/Incompatibilities" to="." method="_on_Dependencies_value_changed" flags=3 binds= [true]]
[connection signal="value_changed" from="VBox/Panel/ScrollContainer/InputVBox/OptionalDependencies" to="." method="_on_OptionalDependencies_value_changed" binds= [false]]
[connection signal="value_changed" from="VBox/Panel/ScrollContainer/InputVBox/OptionalDependencies" to="." method="_on_Incompatibilities_value_changed" flags=3 binds= [true]]
[connection signal="value_changed" from="VBox/Panel/ScrollContainer/InputVBox/OptionalDependencies" to="." method="_on_Dependencies_value_changed" flags=3 binds= [true]]
[connection signal="value_changed" from="VBox/Panel/ScrollContainer/InputVBox/LoadBefore" to="." method="_on_OptionalDependencies_value_changed" flags=3 binds= [true]]
[connection signal="value_changed" from="VBox/Panel/ScrollContainer/InputVBox/LoadBefore" to="." method="_on_LoadBefore_value_changed" binds= [false]]
[connection signal="value_changed" from="VBox/Panel/ScrollContainer/InputVBox/LoadBefore" to="." method="_on_Dependencies_value_changed" flags=3 binds= [true]]
[connection signal="value_changed" from="VBox/Panel/ScrollContainer/InputVBox/CompatibleGameVersions" to="." method="_on_CompatibleGameVersions_value_changed"]
[connection signal="value_changed" from="VBox/Panel/ScrollContainer/InputVBox/CompatibleModLoaderVersions" to="." method="_on_CompatibleModLoaderVersions_value_changed"]
[connection signal="value_changed" from="VBox/Panel/ScrollContainer/InputVBox/Tags" to="." method="_on_Tags_value_changed"]

View File

@@ -0,0 +1,210 @@
@tool
class_name ModToolsPanel
extends Control
# passed from the EditorPlugin
var mod_tool_store: ModToolStore
var editor_plugin: EditorPlugin: set = set_editor_plugin
var context_actions: FileSystemContextActions
var tab_parent_bottom_panel: PanelContainer
var log_richtext_label: RichTextLabel
var log_dock_button: Button
@onready var mod_tool_store_node: ModToolStore = get_node_or_null("/root/ModToolStore")
@onready var tab_container := $"%TabContainer"
@onready var create_mod := $"%CreateMod"
@onready var select_mod := $"%SelectMod"
@onready var label_output := $"%Output"
@onready var mod_id := $"%ModId"
@onready var manifest_editor := $"%Manifest Editor"
@onready var export_path := $"%ExportPath"
@onready var file_dialog := $"%FileDialog"
@onready var hook_gen: ModToolInterfaceHookGen = %HookGen
@onready var hook_restore: ModToolInterfaceHookRestore = %HookRestore
@onready var button_add_hooks: Button = %AddHooks
@onready var button_restore: Button = %Restore
func _ready() -> void:
tab_parent_bottom_panel = get_parent().get_parent() as PanelContainer
get_log_nodes()
if mod_tool_store:
if mod_tool_store.is_hook_generation_done:
button_add_hooks.hide()
else:
button_restore.hide()
# Load manifest.json file
if _ModLoaderFile.file_exists(mod_tool_store.path_manifest):
manifest_editor.load_manifest()
manifest_editor.update_ui()
else:
# Load template Manifest
var template_manifest_data := _ModLoaderFile.get_json_as_dict("res://addons/mod_tool/templates/minimal/manifest.json")
mod_tool_store.manifest_data = ModManifest.new(template_manifest_data, "")
_update_ui()
func set_editor_plugin(plugin: EditorPlugin) -> void:
editor_plugin = plugin
mod_tool_store.editor_plugin = editor_plugin
mod_tool_store.editor_file_system = EditorInterface.get_resource_filesystem()
mod_tool_store.editor_base_control = EditorInterface.get_base_control()
context_actions = FileSystemContextActions.new(
mod_tool_store,
EditorInterface.get_file_system_dock()
)
func get_log_nodes() -> void:
var editor_log := get_parent().get_child(0)
log_richtext_label = editor_log.get_child(1) as RichTextLabel
if not log_richtext_label:
# on project load it can happen that these nodes don't exist yet, wait for parent
await get_parent().ready
log_richtext_label = editor_log.get_child(1) as RichTextLabel
# The button hbox should be last, but here it is second from last for some reason
var dock_tool_button_bar: HBoxContainer = get_parent().get_child(get_parent().get_child_count() -2)
log_dock_button = dock_tool_button_bar.get_child(0).get_child(0)
# Removes the last error line from the output console as if nothing happened
# used in the json validation since the error is displayed right there and
# it causes a lot of clutter otherwise
func discard_last_console_error() -> void:
# If the console is flooded anyway, ignore
var line_count := log_richtext_label.get_line_count()
if line_count > 1000:
return
# The last line is an empty line, remove the one before that
log_richtext_label.remove_line(line_count -2)
log_richtext_label.add_text("\n")
# If there is an error in the console already, leave the circle on the tool button
# All error lines have a space in the beginnig to separate from the circle image
# Not the safest way to check, but it's the only one it seems
for line in log_richtext_label.text.split("\n"):
if (line as String).begins_with(" "):
return
# If there were no other error lines, remove the icon
# Setting to null will crash the editor occasionally, this does not
if log_dock_button:
log_dock_button.icon = CompressedTexture2D.new()
func show_manifest_editor() -> void:
tab_container.current_tab = 0
func show_config_editor() -> void:
tab_container.current_tab = 1
func _update_ui() -> void:
if not mod_tool_store:
return
mod_id.input_text = mod_tool_store.name_mod_dir
export_path.input_text = mod_tool_store.path_export_dir
func _is_mod_dir_valid() -> bool:
# Check if Mod ID is given
if mod_tool_store.name_mod_dir == '':
ModToolUtils.output_error("Please provide a Mod ID")
return false
# Check if mod dir exists
if not _ModLoaderFile.dir_exists(mod_tool_store.path_mod_dir):
ModToolUtils.output_error("Mod folder %s does not exist" % mod_tool_store.path_mod_dir)
return false
return true
func load_mod(name_mod_dir: String) -> void:
# Set the dir name
mod_tool_store.name_mod_dir = name_mod_dir
# Load Manifest
manifest_editor.load_manifest()
manifest_editor.update_ui()
# TODO: Load Mod Config if existing
ModToolUtils.output_info("Mod \"%s\" loaded." % name_mod_dir)
func _on_export_pressed() -> void:
if _is_mod_dir_valid():
var zipper := ModToolZipBuilder.new()
zipper.build_zip(mod_tool_store)
func _on_clear_output_pressed() -> void:
label_output.clear()
func _on_copy_output_pressed() -> void:
DisplayServer.clipboard_set(label_output.text)
func _on_save_manifest_pressed() -> void:
manifest_editor.save_manifest()
func _on_export_settings_create_new_mod_pressed() -> void:
create_mod.popup_centered()
create_mod.clear_mod_id_input()
func _on_CreateMod_mod_dir_created() -> void:
create_mod.hide()
_update_ui()
manifest_editor.load_manifest()
manifest_editor.update_ui()
func _on_ConnectMod_pressed() -> void:
# Opens a popup that displays the mod directory names in the mods-unpacked directory
select_mod.generate_dir_buttons(ModLoaderMod.get_unpacked_dir())
select_mod.popup_centered()
func _on_SelectMod_dir_selected(dir_path: String) -> void:
var mod_dir_name := dir_path.split("/")[-1]
load_mod(mod_dir_name)
select_mod.hide()
_update_ui()
func _on_ButtonExportPath_pressed() -> void:
file_dialog.current_path = mod_tool_store.path_export_dir
file_dialog.popup_centered()
func _on_FileDialog_dir_selected(dir: String) -> void:
mod_tool_store.path_export_dir = dir
export_path.input_text = dir
file_dialog.hide()
func _on_add_hooks_pressed() -> void:
hook_gen.show()
func _on_restore_pressed() -> void:
hook_restore.show()
func _on_hook_gen_hooks_exist_pressed() -> void:
button_add_hooks.hide()
button_restore.show()

View File

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

View File

@@ -0,0 +1,242 @@
[gd_scene load_steps=13 format=3 uid="uid://jgayt8758anm"]
[ext_resource type="PackedScene" uid="uid://glui2s46v4x4" path="res://addons/mod_tool/interface/create_mod/create_mod.tscn" id="1"]
[ext_resource type="PackedScene" uid="uid://hpefgw6k5qpq" path="res://addons/mod_tool/interface/manifest_editor/manifest_editor.tscn" id="2"]
[ext_resource type="Script" path="res://addons/mod_tool/interface/panel/tools_panel.gd" id="3"]
[ext_resource type="PackedScene" uid="uid://icwo58h0rdb5" path="res://addons/mod_tool/interface/global/input_string.tscn" id="4"]
[ext_resource type="PackedScene" uid="uid://dyunxqcmy4esi" path="res://addons/mod_tool/interface/global/input_options.tscn" id="6"]
[ext_resource type="PackedScene" uid="uid://du17jjwqtopix" path="res://addons/mod_tool/interface/global/directory_selection/select_directory.tscn" id="7"]
[ext_resource type="PackedScene" path="res://addons/mod_tool/interface/global/input_string_with_button.tscn" id="8"]
[ext_resource type="PackedScene" uid="uid://cpll5clcnemyj" path="res://addons/mod_tool/interface/hook_gen/hook_gen.tscn" id="8_k13cs"]
[ext_resource type="PackedScene" uid="uid://camcc83bvu086" path="res://addons/mod_tool/interface/hook_restore/hook_restore.tscn" id="9_2cgta"]
[sub_resource type="StyleBoxEmpty" id="14"]
[sub_resource type="Shortcut" id="12"]
resource_name = "Copy Selection"
[sub_resource type="Shortcut" id="13"]
resource_name = "Clear Output"
[node name="ModToolsPanel" type="Control"]
custom_minimum_size = Vector2(0, 400)
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("3")
[node name="Panel" type="PanelContainer" parent="."]
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -4.0
offset_top = -6.0
offset_right = 4.0
offset_bottom = 4.0
[node name="VSplit" type="VSplitContainer" parent="Panel"]
layout_mode = 2
[node name="TabContainer" type="TabContainer" parent="Panel/VSplit"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
theme_override_styles/panel = SubResource("14")
current_tab = 0
[node name="Manifest Editor" parent="Panel/VSplit/TabContainer" instance=ExtResource("2")]
layout_mode = 2
metadata/_tab_index = 0
[node name="Export" type="PanelContainer" parent="Panel/VSplit"]
layout_mode = 2
[node name="HSplit" type="HSplitContainer" parent="Panel/VSplit/Export"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="Console" type="VBoxContainer" parent="Panel/VSplit/Export/HSplit"]
visible = false
layout_mode = 2
size_flags_horizontal = 3
[node name="HBox" type="HBoxContainer" parent="Panel/VSplit/Export/HSplit/Console"]
layout_mode = 2
[node name="Label" type="Label" parent="Panel/VSplit/Export/HSplit/Console/HBox"]
layout_mode = 2
size_flags_horizontal = 3
text = "Output:"
[node name="CopyOutput" type="Button" parent="Panel/VSplit/Export/HSplit/Console/HBox"]
layout_mode = 2
shortcut = SubResource("12")
text = "Copy"
[node name="ClearOutput" type="Button" parent="Panel/VSplit/Export/HSplit/Console/HBox"]
layout_mode = 2
shortcut = SubResource("13")
text = "Clear"
[node name="Output" type="RichTextLabel" parent="Panel/VSplit/Export/HSplit/Console"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
focus_mode = 2
bbcode_enabled = true
scroll_following = true
selection_enabled = true
[node name="Settings" type="HBoxContainer" parent="Panel/VSplit/Export/HSplit"]
custom_minimum_size = Vector2(300, 0)
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 0.5
[node name="VBox" type="VBoxContainer" parent="Panel/VSplit/Export/HSplit/Settings"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 5
[node name="Category" type="LineEdit" parent="Panel/VSplit/Export/HSplit/Settings/VBox"]
layout_mode = 2
mouse_filter = 2
mouse_default_cursor_shape = 0
text = "Export"
editable = false
context_menu_enabled = false
virtual_keyboard_enabled = false
shortcut_keys_enabled = false
middle_mouse_paste_enabled = false
[node name="ModId" parent="Panel/VSplit/Export/HSplit/Settings/VBox" instance=ExtResource("4")]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "ID of the mod to be exported.
Format: Namespace-ModName
(Often Author-ModName)"
mouse_default_cursor_shape = 16
is_editable = false
input_text = "Test-Test"
is_required = true
label_text = "Mod ID"
editor_icon_name = ""
hint_text = "ID of the mod to be exported.
Format: Namespace-ModName
(Often Author-ModName)"
[node name="ExportType" parent="Panel/VSplit/Export/HSplit/Settings/VBox" instance=ExtResource("6")]
unique_name_in_owner = true
visible = false
layout_mode = 2
is_required = true
key = "export_type"
label_text = "Export Type"
[node name="ExportPath" parent="Panel/VSplit/Export/HSplit/Settings/VBox" instance=ExtResource("8")]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "The directory to which the final mod zip is exported."
mouse_default_cursor_shape = 16
input_text = "C:/Users/Kai/Documents/godot/Brotato/mods/Mods/exports"
is_required = true
key = "path_export_dir"
label_text = "Export Path"
hint_text = "The directory to which the final mod zip is exported."
[node name="Align" type="HBoxContainer" parent="Panel/VSplit/Export/HSplit/Settings/VBox"]
layout_mode = 2
theme_override_constants/separation = 10
alignment = 2
[node name="ExportStatus" type="Label" parent="Panel/VSplit/Export/HSplit/Settings/VBox/Align"]
unique_name_in_owner = true
layout_mode = 2
text = "Export Status OK"
[node name="Export" type="Button" parent="Panel/VSplit/Export/HSplit/Settings/VBox/Align"]
layout_mode = 2
text = "Export Mod"
[node name="VBox" type="VBoxContainer" parent="Panel/VSplit/Export/HSplit"]
custom_minimum_size = Vector2(300, 0)
layout_mode = 2
theme_override_constants/separation = 5
[node name="Category" type="LineEdit" parent="Panel/VSplit/Export/HSplit/VBox"]
layout_mode = 2
mouse_filter = 2
mouse_default_cursor_shape = 0
text = "More Actions"
editable = false
context_menu_enabled = false
virtual_keyboard_enabled = false
shortcut_keys_enabled = false
middle_mouse_paste_enabled = false
[node name="CreateMod" type="Button" parent="Panel/VSplit/Export/HSplit/VBox"]
layout_mode = 2
text = "Create new Mod"
[node name="ConnectMod" type="Button" parent="Panel/VSplit/Export/HSplit/VBox"]
layout_mode = 2
text = "Connect existing Mod"
[node name="AddHooks" type="Button" parent="Panel/VSplit/Export/HSplit/VBox"]
unique_name_in_owner = true
layout_mode = 2
text = "Add Hooks to all Scripts"
[node name="Restore" type="Button" parent="Panel/VSplit/Export/HSplit/VBox"]
unique_name_in_owner = true
layout_mode = 2
text = "Restore non Hooked Scripts"
[node name="CreateMod" parent="." instance=ExtResource("1")]
unique_name_in_owner = true
initial_position = 2
size = Vector2i(400, 300)
visible = false
[node name="SelectMod" parent="." instance=ExtResource("7")]
unique_name_in_owner = true
visible = false
[node name="SelectModTemplate" parent="." instance=ExtResource("7")]
unique_name_in_owner = true
visible = false
[node name="HookGen" parent="." instance=ExtResource("8_k13cs")]
unique_name_in_owner = true
visible = false
[node name="HookRestore" parent="." instance=ExtResource("9_2cgta")]
unique_name_in_owner = true
visible = false
[node name="FileDialog" type="FileDialog" parent="."]
unique_name_in_owner = true
title = "Open a Directory"
initial_position = 1
size = Vector2i(800, 500)
ok_button_text = "Select Current Folder"
file_mode = 2
access = 2
[connection signal="pressed" from="Panel/VSplit/Export/HSplit/Console/HBox/CopyOutput" to="." method="_on_copy_output_pressed"]
[connection signal="pressed" from="Panel/VSplit/Export/HSplit/Console/HBox/ClearOutput" to="." method="_on_clear_output_pressed"]
[connection signal="button_pressed" from="Panel/VSplit/Export/HSplit/Settings/VBox/ExportPath" to="." method="_on_ButtonExportPath_pressed"]
[connection signal="pressed" from="Panel/VSplit/Export/HSplit/Settings/VBox/Align/Export" to="." method="_on_export_pressed"]
[connection signal="pressed" from="Panel/VSplit/Export/HSplit/VBox/CreateMod" to="." method="_on_export_settings_create_new_mod_pressed"]
[connection signal="pressed" from="Panel/VSplit/Export/HSplit/VBox/ConnectMod" to="." method="_on_ConnectMod_pressed"]
[connection signal="pressed" from="Panel/VSplit/Export/HSplit/VBox/AddHooks" to="." method="_on_add_hooks_pressed"]
[connection signal="pressed" from="Panel/VSplit/Export/HSplit/VBox/Restore" to="." method="_on_restore_pressed"]
[connection signal="mod_dir_created" from="CreateMod" to="." method="_on_CreateMod_mod_dir_created"]
[connection signal="dir_selected" from="SelectMod" to="." method="_on_SelectMod_dir_selected"]
[connection signal="hooks_exist_pressed" from="HookGen" to="." method="_on_hook_gen_hooks_exist_pressed"]
[connection signal="dir_selected" from="FileDialog" to="." method="_on_FileDialog_dir_selected"]