Basic game template addon
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 6s
Create tag and build when new code gets to main / Export (push) Successful in 1m1s

This commit is contained in:
2026-01-30 19:45:56 +01:00
parent b923f6bec2
commit 44f251ed66
406 changed files with 12602 additions and 1 deletions

View File

@@ -0,0 +1,86 @@
@tool
extends Node
## Script for comparing the version of a plugin to the latest release on GitHub.
signal new_version_detected(version: String)
signal versions_matched
signal failed
const APIClient = MaaacksGameTemplatePlugin.APIClient
const API_RELEASES_URL := "https://api.github.com/repos/%s/%s/releases"
## The directory of the plugin to update. Typically in res://addons/.
@export var plugin_directory : String
## The URL of the GitHub repo to pull new releases.
@export var plugin_github_url : String :
set(value):
plugin_github_url = value
_update_urls()
@export_group("Advanced")
## If true, automatically check for a new version when ready.
@export var auto_start : bool = false
## Text to remove from the tag before comparing versions.
@export var replace_tag_name : String = "v"
## The default lowest version to display.
@export var default_version : String = "0.0.0"
## If true, test comparing versions.
## Replace with @export_tool_button for Godot 4.4+
@export var _test_action : bool = false :
set(value):
if value and Engine.is_editor_hint():
compare_versions()
@onready var _api_client : APIClient = $APIClient
var _zipball_url : String
func get_plugin_version() -> String :
if not plugin_directory.is_empty():
for enabled_plugin in ProjectSettings.get_setting("editor_plugins/enabled"):
if enabled_plugin.contains(plugin_directory):
var config := ConfigFile.new()
var error = config.load(enabled_plugin)
if error != OK:
return default_version
return config.get_value("plugin", "version", default_version)
return default_version
func _update_urls() -> void:
if plugin_github_url.is_empty(): return
if _api_client == null: return
var regex := RegEx.create_from_string("https:\\/\\/github\\.com\\/([\\w-]+)\\/([\\w-]+)\\/*")
var regex_match := regex.search(plugin_github_url)
if regex_match == null: return
var username := regex_match.get_string(1)
var repository := regex_match.get_string(2)
_api_client.api_url = API_RELEASES_URL % [username, repository]
func _on_api_client_request_failed(error) -> void:
failed.emit()
queue_free()
func _on_api_client_response_received(response_body) -> void:
if response_body is not Array or response_body.is_empty():
failed.emit()
queue_free()
return
var latest_release : Dictionary = response_body.front()
var tag_name := default_version
if latest_release.has("tag_name"):
tag_name = latest_release["tag_name"]
if replace_tag_name:
tag_name = tag_name.replacen(replace_tag_name, "")
var current_tag_name = get_plugin_version()
if tag_name != current_tag_name:
new_version_detected.emit(tag_name)
else:
versions_matched.emit()
queue_free()
func compare_versions() -> void:
_api_client.request()
func _ready() -> void:
if auto_start:
compare_versions()

View File

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

View File

@@ -0,0 +1,16 @@
[gd_scene load_steps=3 format=3 uid="uid://depfw7i463ojc"]
[ext_resource type="Script" uid="uid://chjaeg7rfnixu" path="res://addons/maaacks_game_template/installer/check_plugin_version.gd" id="1_aqelj"]
[ext_resource type="PackedScene" uid="uid://cf0hkngq1mgfy" path="res://addons/maaacks_game_template/utilities/api_client.tscn" id="2_5myc0"]
[node name="CheckPluginVersion" type="Node"]
script = ExtResource("1_aqelj")
plugin_directory = "maaacks_game_template"
plugin_github_url = "https://github.com/Maaack/Godot-Minimal-Game-Template"
[node name="APIClient" parent="." instance=ExtResource("2_5myc0")]
api_url = "https://api.github.com/repos/Maaack/Godot-Minimal-Game-Template/releases"
request_method = 0
[connection signal="request_failed" from="APIClient" to="." method="_on_api_client_request_failed"]
[connection signal="response_received" from="APIClient" to="." method="_on_api_client_response_received"]

View File

@@ -0,0 +1,175 @@
@tool
extends Node
## Script for automatically copying Godot scenes and scripts without UIDs.
signal canceled
signal completed(target_path : String)
const UID_PREG_MATCH = r'uid="uid:\/\/[0-9a-z]+" '
const RUNNING_CHECK_DELAY : float = 0.25
const RESAVING_DELAY : float = 1.0
const RAW_COPY_EXTENSIONS : Array = ["gd", "md", "txt"]
const OMIT_COPY_EXTENSIONS : Array = ["uid"]
const REPLACE_CONTENT_EXTENSIONS : Array = ["gd", "tscn", "tres", "md"]
@onready var destination_dialog : FileDialog = $DestinationDialog
@export_dir var relative_path : String :
set(value):
relative_path = value
if not relative_path.ends_with("/"):
relative_path += "/"
@export var replace_strings_map : Dictionary
@export var visible : bool = true :
set(value):
visible = value
if is_inside_tree():
destination_dialog.visible = visible
func show() -> void:
visible = true
func hide() -> void:
visible = false
func close() -> void:
queue_free()
func _remove_uids(content : String) -> String:
var regex = RegEx.new()
regex.compile(UID_PREG_MATCH)
return regex.sub(content, "", true)
func _replace_paths(content : String, target_path : String) -> String:
return content.replace(relative_path.trim_prefix("res://"), target_path.trim_prefix("res://"))
func _replace_strings(content : String) -> String:
for key in replace_strings_map:
var value : String = replace_strings_map[key]
content = content.replace(key, value)
return content
func _replace_content(content : String, target_path : String) -> String:
var replaced_content : String
replaced_content = _remove_uids(content)
replaced_content = _replace_paths(replaced_content, target_path)
replaced_content = _replace_strings(replaced_content)
return replaced_content
func _replace_file_contents(file_path : String, target_path : String) -> void:
var extension : String = file_path.get_extension()
if extension not in REPLACE_CONTENT_EXTENSIONS:
return
var file = FileAccess.open(file_path, FileAccess.READ)
if file == null:
push_error("plugin error - null file: `%s`" % file_path)
return
var original_content = file.get_as_text()
file.close()
var replaced_content := _replace_content(original_content, target_path)
if replaced_content == original_content: return
file = FileAccess.open(file_path, FileAccess.WRITE)
file.store_string(replaced_content)
file.close()
func _save_resource(resource_path : String, resource_destination : String, whitelisted_extensions : PackedStringArray = []) -> Error:
var extension : String = resource_path.get_extension()
if whitelisted_extensions.size() > 0:
if not extension in whitelisted_extensions:
return OK
if extension == "import":
# skip import files
return OK
var file_object = load(resource_path)
if file_object is Resource:
var possible_extensions = ResourceSaver.get_recognized_extensions(file_object)
if possible_extensions.has(extension):
return ResourceSaver.save(file_object, resource_destination, ResourceSaver.FLAG_CHANGE_PATH)
else:
return ERR_FILE_UNRECOGNIZED
else:
return ERR_FILE_UNRECOGNIZED
return OK
func _raw_copy_file_path(file_path : String, destination_path : String) -> Error:
var dir := DirAccess.open("res://")
var error := dir.copy(file_path, destination_path)
return error
func _copy_file_path(file_path : String, destination_path : String, target_path : String) -> Error:
var error : Error
if file_path.get_extension() in OMIT_COPY_EXTENSIONS:
return error
if file_path.get_extension() in RAW_COPY_EXTENSIONS:
error = _raw_copy_file_path(file_path, destination_path)
else:
error = _save_resource(file_path, destination_path)
if error == ERR_FILE_UNRECOGNIZED:
error = _raw_copy_file_path(file_path, destination_path)
if not error:
_replace_file_contents(destination_path, target_path)
return error
func _copy_directory_path(dir_path : String, target_path : String) -> void:
if not dir_path.ends_with("/"):
dir_path += "/"
var dir = DirAccess.open(dir_path)
if dir:
dir.list_dir_begin()
var file_name = dir.get_next()
var error : Error
while file_name != "" and error == 0:
var file_relative_path = dir_path.trim_prefix(relative_path)
var destination_path = target_path + file_relative_path + file_name
var full_file_path = dir_path + file_name
if dir.current_is_dir():
if not dir.dir_exists(destination_path):
error = dir.make_dir(destination_path)
_copy_directory_path(full_file_path, target_path)
else:
error = _copy_file_path(full_file_path, destination_path, target_path)
file_name = dir.get_next()
if error:
push_error("plugin error - copying path: %s" % error)
else:
push_error("plugin error - accessing path: %s" % dir_path)
func _complete(target_path : String) -> void:
completed.emit(target_path)
close()
func _wait_for_scan_and_complete(target_path : String) -> void:
var timer: Timer = Timer.new()
var callable := func():
if EditorInterface.get_resource_filesystem().is_scanning(): return
timer.stop()
_complete(target_path)
timer.queue_free()
timer.timeout.connect(callable)
add_child(timer)
timer.start(RUNNING_CHECK_DELAY)
func _delayed_saving_and_next_prompt(target_path : String) -> void:
var timer: Timer = Timer.new()
var callable := func():
timer.stop()
EditorInterface.save_all_scenes()
EditorInterface.get_resource_filesystem().scan()
_wait_for_scan_and_complete(target_path)
timer.queue_free()
timer.timeout.connect(callable)
add_child(timer)
timer.start(RESAVING_DELAY)
func _copy_to_directory(target_path : String) -> void:
if not target_path.ends_with("/"):
target_path += "/"
_copy_directory_path(relative_path, target_path)
_delayed_saving_and_next_prompt(target_path)
func _on_destination_dialog_dir_selected(dir):
_copy_to_directory(dir)
func _on_destination_dialog_canceled():
canceled.emit()
close()

View File

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

View File

@@ -0,0 +1,16 @@
[gd_scene load_steps=3 format=3 uid="uid://6dpw3bj32ov"]
[ext_resource type="Script" uid="uid://terojwgplakq" path="res://addons/maaacks_game_template/installer/copy_and_edit_files.gd" id="1_oyky1"]
[ext_resource type="PackedScene" uid="uid://doa4t485iflon" path="res://addons/maaacks_game_template/installer/destination_dialog.tscn" id="2_g35hh"]
[node name="CopyAndEditFiles" type="Node"]
script = ExtResource("1_oyky1")
relative_path = "res://addons/maaacks_game_template/examples/"
replace_strings_map = {
"StateExample": "State"
}
[node name="DestinationDialog" parent="." instance=ExtResource("2_g35hh")]
[connection signal="canceled" from="DestinationDialog" to="." method="_on_destination_dialog_canceled"]
[connection signal="dir_selected" from="DestinationDialog" to="." method="_on_destination_dialog_dir_selected"]

View File

@@ -0,0 +1,15 @@
[gd_scene format=3 uid="uid://byupuycr28u17"]
[node name="CopyConfirmationDialog" type="ConfirmationDialog"]
oversampling_override = 1.0
title = "Copy Examples"
initial_position = 2
size = Vector2i(1024, 148)
visible = true
exclusive = false
ok_button_text = "Yes"
dialog_text = "Plugin enabled. It is recommended to copy the example scenes to a destination outside of the addons/ folder before editing them.
Would you like to copy the examples now?"
dialog_autowrap = true
cancel_button_text = "No"

View File

@@ -0,0 +1,16 @@
[gd_scene format=3 uid="uid://n2pm5se13638"]
[node name="DeleteExamplesConfirmationDialog" type="ConfirmationDialog"]
oversampling_override = 1.0
title = "Delete Source Examples"
initial_position = 2
size = Vector2i(1024, 256)
visible = true
ok_button_text = "Yes"
dialog_text = "If the copied scenes work as expected, you may delete the source examples folder. This avoids confusing both developers and the Godot editor.
This will also remove the option to copy the examples again. However, one copy is enough for most use cases.
Would you like to delete the source examples folder now?"
dialog_autowrap = true
cancel_button_text = "No"

View File

@@ -0,0 +1,17 @@
[gd_scene format=3 uid="uid://2ubjcuhlam2o"]
[node name="DeleteExamplesShortConfirmationDialog" type="ConfirmationDialog"]
oversampling_override = 1.0
title = "Delete Source Examples"
initial_position = 2
size = Vector2i(1024, 300)
visible = true
ok_button_text = "Yes"
dialog_text = "This will delete the original examples folder from the plugin (inside of addons/).
Copies of the examples (outside of addons/) will not be affected.
Are you sure you would like to delete the examples folder?
"
dialog_autowrap = true
cancel_button_text = "No"

View File

@@ -0,0 +1,12 @@
[gd_scene format=3 uid="uid://doa4t485iflon"]
[node name="DestinationDialog" type="FileDialog"]
oversampling_override = 1.0
title = "Select a Destination"
initial_position = 2
size = Vector2i(1024, 640)
visible = true
exclusive = false
ok_button_text = "Select Current Folder"
mode_overrides_title = false
file_mode = 2

View File

@@ -0,0 +1,12 @@
@tool
extends ConfirmationDialog
const SHORT_DESCRIPTION : String = "Choose a style for icons in the input remapping menu. This style can be changed later."
signal configuration_selected(index : int)
func _on_item_list_item_selected(index) -> void:
configuration_selected.emit(index)
func set_short_description() -> void:
%Label.text = SHORT_DESCRIPTION

View File

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

View File

@@ -0,0 +1,84 @@
[gd_scene load_steps=14 format=3 uid="uid://vr4wkvk734wg"]
[ext_resource type="Script" uid="uid://c8bnydo2t6vsu" path="res://addons/maaacks_game_template/installer/kenney_input_prompts_dialog.gd" id="1_nf1bc"]
[ext_resource type="Texture2D" uid="uid://dxvbsbs428nj7" path="res://addons/maaacks_game_template/assets/input-icons/icons-filled-colored.png" id="2_0nqam"]
[ext_resource type="Texture2D" uid="uid://5gjytq6nlww3" path="res://addons/maaacks_game_template/assets/input-icons/icons-filled-white.png" id="3_ynuxh"]
[ext_resource type="Texture2D" uid="uid://cj4f8dma2647n" path="res://addons/maaacks_game_template/assets/input-icons/icons-outlined-colored.png" id="4_dqbfh"]
[ext_resource type="Texture2D" uid="uid://cj524k2rdjvjo" path="res://addons/maaacks_game_template/assets/input-icons/icons-outlined-white.png" id="5_1tkva"]
[ext_resource type="Texture2D" uid="uid://nqdhl41h0tpv" path="res://addons/maaacks_game_template/assets/input-icons/icons-filled-colored-2x.png" id="6_r3yyh"]
[ext_resource type="Texture2D" uid="uid://df0qisbxrhsqa" path="res://addons/maaacks_game_template/assets/input-icons/icons-filled-white-2x.png" id="7_xgp8o"]
[ext_resource type="Texture2D" uid="uid://1gr33hl6p8tu" path="res://addons/maaacks_game_template/assets/input-icons/icons-outlined-colored-2x.png" id="8_ag5dy"]
[ext_resource type="Texture2D" uid="uid://betu06qbgx5gf" path="res://addons/maaacks_game_template/assets/input-icons/icons-outlined-white-2x.png" id="9_3b8mx"]
[ext_resource type="Texture2D" uid="uid://cp7xvu3oujlnj" path="res://addons/maaacks_game_template/assets/input-icons/icons-filled-colored-vector.svg" id="10_ag5dy"]
[ext_resource type="Texture2D" uid="uid://dl5p5qw3hp8y" path="res://addons/maaacks_game_template/assets/input-icons/icons-filled-white-vector.svg" id="11_3b8mx"]
[ext_resource type="Texture2D" uid="uid://2xcu58kisr45" path="res://addons/maaacks_game_template/assets/input-icons/icons-outlined-colored-vector.svg" id="12_rrkvx"]
[ext_resource type="Texture2D" uid="uid://cf4s4f7tvltpp" path="res://addons/maaacks_game_template/assets/input-icons/icons-outlined-white-vector.svg" id="13_bkfjd"]
[node name="KenneyInputPromptsDialog" type="ConfirmationDialog"]
oversampling_override = 1.0
title = "Add Kenney Input Prompts Pack"
initial_position = 2
size = Vector2i(1024, 640)
visible = true
ok_button_text = "Yes"
dialog_autowrap = true
cancel_button_text = "No"
script = ExtResource("1_nf1bc")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
custom_minimum_size = Vector2(560, 443)
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 8.0
offset_top = 8.0
offset_right = -8.0
offset_bottom = -49.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 16
[node name="Label" type="Label" parent="VBoxContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(384, 0)
layout_mode = 2
text = "Would you like to install Kenney's Input Prompts?
This adds icons for a majority of input keys and devices in the input remapping menu. They are Creative Commons Zero (CC0) licensed, about 3.9 MB in size (7.6 MB with *.import files), and get installed into the assets folder.
Choose a style for icons in the input remapping menu. The style can be changed later."
autowrap_mode = 3
[node name="ItemList" type="ItemList" parent="VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
item_count = 12
same_column_width = true
item_0/text = "Filled and Colored Vector"
item_0/icon = ExtResource("10_ag5dy")
item_1/text = "Filled and White Vector"
item_1/icon = ExtResource("11_3b8mx")
item_2/text = "Outlined and Colored Vector"
item_2/icon = ExtResource("12_rrkvx")
item_3/text = "Outlined and White Vector"
item_3/icon = ExtResource("13_bkfjd")
item_4/text = "Filled and Colored 64x64"
item_4/icon = ExtResource("2_0nqam")
item_5/text = "Filled and White 64x64"
item_5/icon = ExtResource("3_ynuxh")
item_6/text = "Outlined and Colored 64x64"
item_6/icon = ExtResource("4_dqbfh")
item_7/text = "Outlined and White 64x64"
item_7/icon = ExtResource("5_1tkva")
item_8/text = "Filled and Colored 128x128"
item_8/icon = ExtResource("6_r3yyh")
item_9/text = "Filled and White 128x128"
item_9/icon = ExtResource("7_xgp8o")
item_10/text = "Outlined and Colored 128x128"
item_10/icon = ExtResource("8_ag5dy")
item_11/text = "Outlined and White 128x128"
item_11/icon = ExtResource("9_3b8mx")
[connection signal="item_selected" from="VBoxContainer/ItemList" to="." method="_on_item_list_item_selected"]

View File

@@ -0,0 +1,336 @@
@tool
## Tool for installing icons and setting up the configuration of the input icon mapper.
extends Node
## Sent when the user selects to cancel the installation process.
signal canceled
## Sent when the installation process has completed.
signal completed
const DownloadAndExtract = MaaacksGameTemplatePlugin.DownloadAndExtract
const RELATIVE_PATH_TO_CONFIGURE_SCENE = "scenes/menus/options_menu/input/input_icon_mapper.tscn"
const REIMPORT_CHECK_DELAY : float = 0.5
const OPEN_SCENE_DELAY : float = 0.5
const MATCH_REGEX = """(\\[node name="InputIconMapper" instance=ExtResource\\("[0-9a-z_]+"\\)\\])[\\s\\S]*"""
const FILLED_WHITE_CONFIGURATION = """
replace_strings = {
"Capslock": "Caps Lock",
"Generic Stick": "Generic Left Stick",
"Guide": "Home",
"Slash Back": "Back Slash",
"Slash Forward": "Slash",
"Stick L": "Left Stick",
"Stick R": "Right Stick",
"Trigger L 1": "Left Shoulder",
"Trigger L 2": "Left Trigger",
"Trigger R 1": "Right Shoulder",
"Trigger R 2": "Right Trigger"
}
filtered_strings = Array[String](["keyboard", "color", "button", "arrow"])
directories = Array[String](["res://assets/kenney_input-prompts/Keyboard & Mouse/Default", "res://assets/kenney_input-prompts/Generic/Default", "res://assets/kenney_input-prompts/Xbox Series/Default", "res://assets/kenney_input-prompts/PlayStation Series/Default", "res://assets/kenney_input-prompts/Nintendo Switch/Default", "res://assets/kenney_input-prompts/Steam Deck/Default"])
filter = "color"
ends_with = ".png"
not_ends_with = "outline.png"
"""
const FILLED_COLOR_CONFIGURATION = """
prioritized_strings = Array[String](["color"])
replace_strings = {
"Capslock": "Caps Lock",
"Generic Stick": "Generic Left Stick",
"Guide": "Home",
"Slash Back": "Back Slash",
"Slash Forward": "Slash",
"Stick L": "Left Stick",
"Stick R": "Right Stick",
"Trigger L 1": "Left Shoulder",
"Trigger L 2": "Left Trigger",
"Trigger R 1": "Right Shoulder",
"Trigger R 2": "Right Trigger"
}
filtered_strings = Array[String](["keyboard", "color", "button", "arrow"])
directories = Array[String](["res://assets/kenney_input-prompts/Keyboard & Mouse/Default", "res://assets/kenney_input-prompts/Generic/Default", "res://assets/kenney_input-prompts/Xbox Series/Default", "res://assets/kenney_input-prompts/PlayStation Series/Default", "res://assets/kenney_input-prompts/Nintendo Switch/Default", "res://assets/kenney_input-prompts/Steam Deck/Default"])
ends_with = ".png"
not_ends_with = "outline.png"
"""
const OUTLINED_WHITE_CONFIGURATION = """
prioritized_strings = Array[String](["outline"])
replace_strings = {
"Capslock": "Caps Lock",
"Generic Stick": "Generic Left Stick",
"Guide": "Home",
"Slash Back": "Back Slash",
"Slash Forward": "Slash",
"Stick L": "Left Stick",
"Stick R": "Right Stick",
"Trigger L 1": "Left Shoulder",
"Trigger L 2": "Left Trigger",
"Trigger R 1": "Right Shoulder",
"Trigger R 2": "Right Trigger"
}
filtered_strings = Array[String](["keyboard", "color", "button", "arrow", "outline"])
directories = Array[String](["res://assets/kenney_input-prompts/Keyboard & Mouse/Default", "res://assets/kenney_input-prompts/Generic/Default", "res://assets/kenney_input-prompts/Xbox Series/Default", "res://assets/kenney_input-prompts/PlayStation Series/Default", "res://assets/kenney_input-prompts/Nintendo Switch/Default", "res://assets/kenney_input-prompts/Steam Deck/Default"])
filter = "color"
ends_with = ".png"
"""
const OUTLINED_COLOR_CONFIGURATION = """
prioritized_strings = Array[String](["outline", "color"])
replace_strings = {
"Capslock": "Caps Lock",
"Generic Stick": "Generic Left Stick",
"Guide": "Home",
"Slash Back": "Back Slash",
"Slash Forward": "Slash",
"Stick L": "Left Stick",
"Stick R": "Right Stick",
"Trigger L 1": "Left Shoulder",
"Trigger L 2": "Left Trigger",
"Trigger R 1": "Right Shoulder",
"Trigger R 2": "Right Trigger"
}
filtered_strings = Array[String](["keyboard", "color", "button", "arrow", "outline"])
directories = Array[String](["res://assets/kenney_input-prompts/Keyboard & Mouse/Default", "res://assets/kenney_input-prompts/Generic/Default", "res://assets/kenney_input-prompts/Xbox Series/Default", "res://assets/kenney_input-prompts/PlayStation Series/Default", "res://assets/kenney_input-prompts/Nintendo Switch/Default", "res://assets/kenney_input-prompts/Steam Deck/Default"])
ends_with = ".png"
"""
const PACKAGE_EXTRA_DIRECTORIES := [
"Flairs",
"Nintendo Gamecube",
"Nintendo Switch 2",
"Nintendo Wii",
"Nintendo WiiU",
"Playdate",
"Steam Controller",
"Touch",
]
const PACKAGE_EXTRA_FILES := [
"Preview",
]
## Path start where the project examples have been copied.
@export_dir var copy_dir_path : String
## Path end where the zipped files are to be extracted.
@export var extract_extension : String
@onready var _download_and_extract_node : DownloadAndExtract = $DownloadAndExtract
@onready var _skip_installation_dialog : ConfirmationDialog = $SkipInstallationDialog
@onready var _kenney_input_prompts_dialog : ConfirmationDialog = $KenneyInputPromptsDialog
@onready var _installing_dialog : AcceptDialog = $InstallingDialog
@onready var _clean_up_dialog : ConfirmationDialog = $CleanUpDialog
@onready var _error_dialog : AcceptDialog = $ErrorDialog
@onready var _stage_label : Label = %StageLabel
@onready var _progress_bar : ProgressBar = %ProgressBar
var _configuration_index : int = -1
## State flag of whether the tool is waiting for the filesystem to finish scanning.
var scanning : bool = false
## State flag for whether the tool is waiting for the filesystem to finish reimporting.
var reimporting : bool = false
## Flag for whether the tool will force a download and extraction, even if the contents exist.
var force : bool = false
func _download_and_extract() -> void:
_installing_dialog.show()
_download_and_extract_node.run.call_deferred()
func _run_complete() -> void:
completed.emit()
queue_free()
func _clean_up_or_complete() -> void:
if _has_extras():
_clean_up_dialog.show()
else:
_run_complete()
func _process(_delta : float) -> void:
if _installing_dialog.visible:
_progress_bar.value = _download_and_extract_node.get_progress()
match _download_and_extract_node.stage:
DownloadAndExtract.DownloadAndExtractStage.DOWNLOAD:
_stage_label.text = "Downloading..."
DownloadAndExtract.DownloadAndExtractStage.SAVE:
_stage_label.text = "Saving..."
DownloadAndExtract.DownloadAndExtractStage.EXTRACT:
_stage_label.text = "Extracting..."
DownloadAndExtract.DownloadAndExtractStage.DELETE:
_stage_label.text = "Cleaning up..."
DownloadAndExtract.DownloadAndExtractStage.NONE:
_installing_dialog.hide()
elif scanning:
var file_system := EditorInterface.get_resource_filesystem()
if not file_system.is_scanning():
scanning = false
await get_tree().create_timer(REIMPORT_CHECK_DELAY).timeout
if reimporting:
await file_system.resources_reimported
reimporting = false
_configure_and_complete()
func _delete_recursive(path : String) -> void:
if not path.ends_with("/"):
path += "/"
var dir_access := DirAccess.open(path)
if dir_access == null:
return
var directories := dir_access.get_directories()
for directory in directories:
_delete_recursive(path + directory)
DirAccess.remove_absolute(path + directory)
var files := dir_access.get_files()
for file in files:
DirAccess.remove_absolute(path + file)
func get_full_path() -> String:
var full_path := copy_dir_path
if not full_path.ends_with("/"):
full_path += "/"
full_path += extract_extension
if not full_path.ends_with("/"):
full_path += "/"
return full_path
func _has_extras() -> bool:
var full_path := get_full_path()
var directories := DirAccess.get_directories_at(full_path)
for directory in directories:
for key in PACKAGE_EXTRA_DIRECTORIES:
if directory.contains(key):
return true
var files := DirAccess.get_files_at(full_path)
for file in files:
for key in PACKAGE_EXTRA_FILES:
if file.contains(key):
return true
return false
func _delete_extras() -> void:
var full_path := get_full_path()
var directories := DirAccess.get_directories_at(full_path)
for directory in directories:
for key in PACKAGE_EXTRA_DIRECTORIES:
if directory.contains(key):
_delete_recursive(full_path + directory)
DirAccess.remove_absolute(full_path + directory)
continue
var files := DirAccess.get_files_at(full_path)
for file in files:
for key in PACKAGE_EXTRA_FILES:
if file.contains(key):
DirAccess.remove_absolute(full_path + file)
continue
EditorInterface.get_resource_filesystem().scan()
func _configure_icons() -> void:
var input_mapper_path := copy_dir_path + RELATIVE_PATH_TO_CONFIGURE_SCENE
var icon_mapper_string := FileAccess.get_file_as_string(input_mapper_path)
var replacing_string := "$1\n"
match(_configuration_index % 4):
0:
replacing_string += FILLED_COLOR_CONFIGURATION
1:
replacing_string += FILLED_WHITE_CONFIGURATION
2:
replacing_string += OUTLINED_COLOR_CONFIGURATION
3:
replacing_string += OUTLINED_WHITE_CONFIGURATION
match(_configuration_index / 4):
0:
replacing_string = replacing_string.replace("Default", "Vector").replace(".png", ".svg")
1:
pass
2:
replacing_string = replacing_string.replace("Default", "Double")
var regex = RegEx.new()
regex.compile(MATCH_REGEX)
icon_mapper_string = regex.sub(icon_mapper_string, replacing_string)
var file_rewrite := FileAccess.open(input_mapper_path, FileAccess.WRITE)
file_rewrite.store_string(icon_mapper_string)
file_rewrite.close()
if input_mapper_path in EditorInterface.get_open_scenes():
EditorInterface.reload_scene_from_path(input_mapper_path)
else:
EditorInterface.open_scene_from_path(input_mapper_path)
await get_tree().create_timer(OPEN_SCENE_DELAY).timeout
EditorInterface.save_scene()
await get_tree().create_timer(REIMPORT_CHECK_DELAY).timeout
_clean_up_or_complete()
func _configure_and_complete() -> void:
if _configuration_index >= 0:
_configure_icons()
return
_clean_up_or_complete()
func _scan_filesystem_and_reimport() -> void:
var file_system := EditorInterface.get_resource_filesystem()
file_system.scan()
scanning = true
await file_system.resources_reimporting
reimporting = true
func _enable_forced_install() -> void:
force = true
_download_and_extract_node.force = true
_kenney_input_prompts_dialog.show.call_deferred()
func _enable_skipped_install() -> void:
_kenney_input_prompts_dialog.set_short_description()
_kenney_input_prompts_dialog.show.call_deferred()
func _show_error_dialog(error : String) -> void:
_installing_dialog.hide()
_error_dialog.show()
_error_dialog.dialog_text = "%s!" % error
func _ready() -> void:
_skip_installation_dialog.hide()
_kenney_input_prompts_dialog.hide()
_installing_dialog.hide()
_installing_dialog.get_ok_button().hide()
_clean_up_dialog.hide()
_error_dialog.hide()
_download_and_extract_node.extract_path = get_full_path()
if _download_and_extract_node.extract_path_exists():
_skip_installation_dialog.show()
else:
_kenney_input_prompts_dialog.show()
func _on_kenney_input_prompts_dialog_canceled() -> void:
canceled.emit()
queue_free()
func _on_kenney_input_prompts_dialog_configuration_selected(index: int) -> void:
_configuration_index = index
func _on_kenney_input_prompts_dialog_confirmed() -> void:
if _download_and_extract_node.extract_path_exists() and not force:
_configure_and_complete()
return
_download_and_extract()
func _on_skip_installation_dialog_canceled() -> void:
_enable_forced_install()
func _on_skip_installation_dialog_confirmed() -> void:
_enable_skipped_install()
func _on_error_dialog_confirmed() -> void:
queue_free()
func _on_error_dialog_canceled() -> void:
queue_free()
func _on_download_and_extract_run_completed() -> void:
_scan_filesystem_and_reimport()
func _on_download_and_extract_run_failed(error : String) -> void:
_show_error_dialog(error)
func _on_clean_up_dialog_confirmed() -> void:
_delete_extras()
_run_complete()
func _on_clean_up_dialog_canceled() -> void:
_run_complete()

View File

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

View File

@@ -0,0 +1,92 @@
[gd_scene load_steps=4 format=3 uid="uid://c56u7kqt1l8yp"]
[ext_resource type="Script" uid="uid://cl2gk653d5d6o" path="res://addons/maaacks_game_template/installer/kenney_input_prompts_installer.gd" id="1_ebstj"]
[ext_resource type="PackedScene" uid="uid://vr4wkvk734wg" path="res://addons/maaacks_game_template/installer/kenney_input_prompts_dialog.tscn" id="1_pslk0"]
[ext_resource type="PackedScene" uid="uid://5hhnbgqjwnic" path="res://addons/maaacks_game_template/utilities/download_and_extract.tscn" id="3_ebstj"]
[node name="KenneyInputPromptsInstaller" type="Node"]
script = ExtResource("1_ebstj")
copy_dir_path = "res://"
extract_extension = "assets/kenney_input-prompts"
[node name="DownloadAndExtract" parent="." instance=ExtResource("3_ebstj")]
zip_url = "https://github.com/Maaack/Kenney-Input-Prompts/archive/refs/tags/1.3.zip"
extract_path = "res://assets/kenney_input-prompts/"
skip_base_zip_dir = true
zip_file_path = "res://kenney_input-prompts.zip"
metadata/_custom_type_script = "uid://bkno1by7i3hrb"
[node name="SkipInstallationDialog" type="ConfirmationDialog" parent="."]
title = "Skip Installation?"
initial_position = 2
size = Vector2i(682, 160)
ok_button_text = "Skip"
dialog_text = "The input prompts pack appears to already be installed.
Do you want to force a reinstall of the pack, or skip to picking a style?"
cancel_button_text = "Reinstall"
[node name="KenneyInputPromptsDialog" parent="." instance=ExtResource("1_pslk0")]
visible = false
[node name="InstallingDialog" type="AcceptDialog" parent="."]
title = "Installing..."
initial_position = 2
size = Vector2i(400, 100)
[node name="MarginContainer" type="MarginContainer" parent="InstallingDialog"]
offset_left = 4.0
offset_top = 4.0
offset_right = 396.0
offset_bottom = 96.0
theme_override_constants/margin_left = 16
theme_override_constants/margin_top = 16
theme_override_constants/margin_right = 16
theme_override_constants/margin_bottom = 16
[node name="VBoxContainer" type="VBoxContainer" parent="InstallingDialog/MarginContainer"]
layout_mode = 2
alignment = 1
[node name="StageLabel" type="Label" parent="InstallingDialog/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
horizontal_alignment = 1
vertical_alignment = 1
[node name="ProgressBar" type="ProgressBar" parent="InstallingDialog/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
max_value = 1.0
step = 0.001
[node name="CleanUpDialog" type="ConfirmationDialog" parent="."]
auto_translate_mode = 1
title = "Clean Up Extra Content?"
initial_position = 2
size = Vector2i(1024, 210)
ok_button_text = "Yes"
dialog_text = "Kenney's Input Prompts contains extra content not used by the input remapping menu.
This includes icons for devices not currently detected and preview images. Removing the extras cuts the total size of extracted assets by almost 50%. The option to change input icon styles will remain available after the clean up, too.
Would you like to have the extra content removed?"
dialog_autowrap = true
cancel_button_text = "No"
[node name="ErrorDialog" type="AcceptDialog" parent="."]
title = "Error!"
initial_position = 2
size = Vector2i(400, 128)
[connection signal="run_completed" from="DownloadAndExtract" to="." method="_on_download_and_extract_run_completed"]
[connection signal="run_failed" from="DownloadAndExtract" to="." method="_on_download_and_extract_run_failed"]
[connection signal="canceled" from="SkipInstallationDialog" to="." method="_on_skip_installation_dialog_canceled"]
[connection signal="confirmed" from="SkipInstallationDialog" to="." method="_on_skip_installation_dialog_confirmed"]
[connection signal="canceled" from="KenneyInputPromptsDialog" to="." method="_on_kenney_input_prompts_dialog_canceled"]
[connection signal="configuration_selected" from="KenneyInputPromptsDialog" to="." method="_on_kenney_input_prompts_dialog_configuration_selected"]
[connection signal="confirmed" from="KenneyInputPromptsDialog" to="." method="_on_kenney_input_prompts_dialog_confirmed"]
[connection signal="canceled" from="CleanUpDialog" to="." method="_on_clean_up_dialog_canceled"]
[connection signal="confirmed" from="CleanUpDialog" to="." method="_on_clean_up_dialog_confirmed"]
[connection signal="canceled" from="ErrorDialog" to="." method="_on_error_dialog_canceled"]
[connection signal="confirmed" from="ErrorDialog" to="." method="_on_error_dialog_confirmed"]

View File

@@ -0,0 +1,8 @@
@tool
extends ConfirmationDialog
const MAIN_SCENE_UPDATE_TEXT = "Would you like to update the project's main scene?\n\nCurrent:\n%s\n\nNew:\n%s\n"
func set_main_scene_text(new_scene_path):
var current_scene_path : String = ProjectSettings.get_setting("application/run/main_scene", "")
dialog_text = MAIN_SCENE_UPDATE_TEXT % [current_scene_path, new_scene_path]

View File

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

View File

@@ -0,0 +1,18 @@
[gd_scene load_steps=2 format=3 uid="uid://gblkpm37lq1j"]
[ext_resource type="Script" uid="uid://exfk51fr7yx0" path="res://addons/maaacks_game_template/installer/main_scene_confirmation_dialog.gd" id="1_1ydgd"]
[node name="MainSceneConfirmationDialog" type="ConfirmationDialog"]
oversampling_override = 1.0
title = "Update Main Scene"
initial_position = 2
size = Vector2i(1024, 192)
visible = true
exclusive = false
ok_button_text = "Yes"
dialog_text = "Would you like to update the project's main scene?
"
dialog_autowrap = true
cancel_button_text = "No"
script = ExtResource("1_1ydgd")

View File

@@ -0,0 +1,36 @@
; Project settings override file.
; Adds gamepad inputs to built-in actions.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
[input]
ui_accept={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194309,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194310,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":32,"physical_keycode":0,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":true,"script":null)
]
}
ui_cancel={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194305,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":6,"pressure":0.0,"pressed":true,"script":null)
]
}
ui_page_up={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194323,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":9,"pressure":0.0,"pressed":true,"script":null)
]
}
ui_page_down={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194324,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":10,"pressure":0.0,"pressed":true,"script":null)
]
}

View File

@@ -0,0 +1,14 @@
[gd_scene format=3 uid="uid://cukfpfjy3827p"]
[node name="PlayOpeningConfirmationDialog" type="ConfirmationDialog"]
oversampling_override = 1.0
title = "Run & Test"
initial_position = 2
size = Vector2i(1024, 148)
visible = true
ok_button_text = "Yes"
dialog_text = "It is recommended to run the opening scene of the plugin and test if any issues occurred during the copying process.
Would you like to run and test the scenes now?"
dialog_autowrap = true
cancel_button_text = "No"

View File

@@ -0,0 +1,79 @@
@tool
extends RichTextLabel
const HEADING_STRING_REPLACEMENT = "$1[font_size=%d]$2[/font_size]"
const BOLD_HEADING_STRING_REPLACEMENT = "$1[b][font_size=%d]$2[/font_size][/b]"
@export_group("Font Sizes")
@export var h1_font_size: int
@export var h2_font_size: int
@export var h3_font_size: int
@export var h4_font_size: int
@export var h5_font_size: int
@export var h6_font_size: int
@export var bold_headings : bool
@export_group("Extra Options")
@export var disable_images : bool = false
@export var disable_urls : bool = false
@export var disable_bolds : bool = false
func regex_replace_imgs(markdown_text:String) -> String:
var regex = RegEx.new()
var match_string := "<img .* src=\"(.*)\" \\/>"
var replace_string := ""
if not disable_images:
replace_string = "$1"
regex.compile(match_string)
return regex.sub(markdown_text, replace_string, true)
func regex_replace_urls(markdown_text:String) -> String:
var regex = RegEx.new()
var match_string := "(https:\\/\\/.*)($|\\s)"
var replace_string := "$1"
if not disable_urls:
replace_string = "[url=$1]$1[/url]"
regex.compile(match_string)
return regex.sub(markdown_text, replace_string, true)
func regex_replace_bolds(markdown_text:String) -> String:
var regex = RegEx.new()
var match_string := "\\*\\*(.*)\\*\\*"
var replace_string := "$1"
if not disable_bolds:
replace_string = "[b]$1[/b]"
regex.compile(match_string)
return regex.sub(markdown_text, replace_string, true)
func regex_replace_titles(markdown_text:String) -> String:
var iter = 0
var heading_font_sizes : Array[int] = [
h1_font_size,
h2_font_size,
h3_font_size,
h4_font_size,
h5_font_size,
h6_font_size]
for heading_font_size in heading_font_sizes:
iter += 1
var regex = RegEx.new()
var match_string : String = "([^#]|^)#{%d}\\s([^\n]*)" % iter
var replace_string := HEADING_STRING_REPLACEMENT % [heading_font_size]
if bold_headings:
replace_string = BOLD_HEADING_STRING_REPLACEMENT % [heading_font_size]
regex.compile(match_string)
markdown_text = regex.sub(markdown_text, replace_string, true)
return markdown_text
func from_release_notes(markdown_text : String) -> void:
markdown_text = regex_replace_imgs(markdown_text)
markdown_text = regex_replace_urls(markdown_text)
markdown_text = regex_replace_bolds(markdown_text)
markdown_text = regex_replace_titles(markdown_text)
text = markdown_text
func _on_meta_clicked(meta: String) -> void:
if meta.begins_with("https://"):
var _err = OS.shell_open(meta)
func _ready() -> void:
meta_clicked.connect(_on_meta_clicked)

View File

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

View File

@@ -0,0 +1,14 @@
[gd_scene format=3 uid="uid://bie1wby7a56or"]
[node name="SetupCompleteDialog" type="AcceptDialog"]
oversampling_override = 1.0
title = "Setup Complete!"
initial_position = 2
size = Vector2i(1032, 360)
visible = true
dialog_text = "Thanks for installing Maaack's Minimal Game Template!
Go to `Project > Tools > Run Maaack's Minimal Game Template Setup...` for extra features.
Each step of the setup can also be revisited.
See the included README for next steps and additional documentation."

View File

@@ -0,0 +1,125 @@
@tool
extends AcceptDialog
@export_file("*.tscn") var check_version_scene_path : String
@export_dir var input_prompts_directory_path : String
@onready var plugin_label : Label = %PluginLabel
@onready var update_label : Label = %UpdateLabel
@onready var update_check_box : CheckBox = %UpdateCheckBox
@onready var update_button : Button = %UpdateButton
@onready var copy_check_box : CheckBox = %CopyCheckBox
@onready var copy_button : Button = %CopyButton
@onready var delete_check_box : CheckBox = %DeleteCheckBox
@onready var delete_button : Button = %DeleteButton
@onready var set_main_scene_check_box : CheckBox = %SetMainSceneCheckBox
@onready var set_main_scene_button : Button = %SetMainSceneButton
@onready var set_default_theme_check_box : CheckBox = %SetDefaultThemeCheckBox
@onready var set_default_theme_button : Button = %SetDefaultThemeButton
@onready var add_input_icons_check_box : CheckBox = %AddInputIconsCheckBox
@onready var add_input_icons_button : Button = %AddInputIconsButton
func _refresh_plugin_details() -> void:
for enabled_plugin in ProjectSettings.get_setting("editor_plugins/enabled"):
if enabled_plugin.contains(MaaacksGameTemplatePlugin.get_settings_path()):
var config := ConfigFile.new()
var error = config.load(enabled_plugin)
if error != OK:
return
var current_plugin_version : String = config.get_value("plugin", "version", "0.0.0")
var plugin_name : String = config.get_value("plugin", "name", "Plugin")
plugin_label.text = "%s v%s" % [plugin_name, current_plugin_version]
func _show_plugin_versions_match() -> void:
update_label.text = "Using Latest Version"
update_check_box.button_pressed = true
update_button.disabled = true
func _enable_update_plugin_tool_option(tag_name : String) -> void:
update_label.text = "Update to Latest Version v%s" % tag_name
update_button.disabled = false
func _open_check_plugin_version() -> void:
if check_version_scene_path.is_empty():
push_warning("Variable \"check_version_scene_path\" is not set")
return
if ProjectSettings.get_setting(MaaacksGameTemplatePlugin.get_settings_path() + "disable_update_check", false):
update_label.text = "Check for Latest Version"
update_button.disabled = false
return
var check_version_scene : PackedScene = load(check_version_scene_path)
var check_version_instance : Node = check_version_scene.instantiate()
check_version_instance.auto_start = true
check_version_instance.new_version_detected.connect(_enable_update_plugin_tool_option)
check_version_instance.versions_matched.connect(_show_plugin_versions_match)
add_child(check_version_instance)
func _refresh_copy_and_delete_examples() -> void:
var examples_path = MaaacksGameTemplatePlugin.instance.get_plugin_examples_path()
if MaaacksGameTemplatePlugin.get_copy_path() != examples_path:
copy_check_box.button_pressed = true
var dir := DirAccess.open("res://")
if dir.dir_exists(examples_path):
copy_button.disabled = false
delete_button.disabled = false
else:
delete_check_box.button_pressed = true
func _refresh_main_scene() -> void:
if MaaacksGameTemplatePlugin.instance.is_main_scene_set():
set_main_scene_check_box.button_pressed = true
else:
set_main_scene_button.disabled = false
func _refresh_default_theme() -> void:
set_default_theme_button.disabled = false
if ProjectSettings.get_setting("gui/theme/custom", "") != "":
set_default_theme_check_box.button_pressed = true
func _refresh_input_prompts() -> void:
if input_prompts_directory_path.is_empty():
push_warning("Variable \"input_prompts_directory_path\" is not set")
return
if DirAccess.dir_exists_absolute(input_prompts_directory_path):
add_input_icons_check_box.button_pressed = true
add_input_icons_button.disabled = false
func _refresh_options():
_refresh_plugin_details()
_open_check_plugin_version()
_refresh_copy_and_delete_examples()
_refresh_main_scene()
_refresh_default_theme()
_refresh_input_prompts()
func _ready():
_refresh_options()
func _on_update_button_pressed():
if ProjectSettings.get_setting(MaaacksGameTemplatePlugin.get_settings_path() + "disable_update_check", false):
ProjectSettings.set_setting(MaaacksGameTemplatePlugin.get_settings_path() + "disable_update_check", false)
_open_check_plugin_version()
return
else:
tree_exited.connect(func(): MaaacksGameTemplatePlugin.instance.open_update_plugin())
queue_free()
func _on_copy_button_pressed():
tree_exited.connect(func(): MaaacksGameTemplatePlugin.instance.open_copy_and_edit_dialog())
queue_free()
func _on_delete_button_pressed():
tree_exited.connect(func(): MaaacksGameTemplatePlugin.instance.open_delete_examples_short_confirmation_dialog())
queue_free()
func _on_set_main_scene_button_pressed():
tree_exited.connect(func(): MaaacksGameTemplatePlugin.instance.open_main_scene_confirmation_dialog(MaaacksGameTemplatePlugin.get_copy_path()))
queue_free()
func _on_set_default_theme_button_pressed():
tree_exited.connect(func(): MaaacksGameTemplatePlugin.instance.open_theme_selection_dialog(MaaacksGameTemplatePlugin.get_copy_path()))
queue_free()
func _on_add_input_icons_button_pressed():
tree_exited.connect(func(): MaaacksGameTemplatePlugin.instance.open_input_icons_dialog())
queue_free()

View File

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

View File

@@ -0,0 +1,153 @@
[gd_scene load_steps=2 format=3 uid="uid://ffy0rjxas5op"]
[ext_resource type="Script" uid="uid://dres77mrg8fxf" path="res://addons/maaacks_game_template/installer/setup_wizard.gd" id="1_jck5m"]
[node name="SetupWizardDialog" type="AcceptDialog"]
oversampling_override = 1.0
title = "Setup Wizard"
initial_position = 2
size = Vector2i(576, 540)
visible = true
ok_button_text = "Done"
dialog_autowrap = true
script = ExtResource("1_jck5m")
check_version_scene_path = "res://addons/maaacks_game_template/installer/check_plugin_version.tscn"
input_prompts_directory_path = "res://assets/kenney_input-prompts/"
[node name="VBoxContainer" type="VBoxContainer" parent="."]
custom_minimum_size = Vector2(560, 483)
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 8.0
offset_top = 8.0
offset_right = -8.0
offset_bottom = -49.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 16
[node name="PluginLabel" type="Label" parent="VBoxContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(384, 0)
layout_mode = 2
autowrap_mode = 3
[node name="HSeparator" type="HSeparator" parent="VBoxContainer"]
layout_mode = 2
[node name="StepsContainer" type="GridContainer" parent="VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
theme_override_constants/h_separation = 12
columns = 3
[node name="UpdateLabel" type="Label" parent="VBoxContainer/StepsContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "Update to Latest Version"
[node name="UpdateCheckBox" type="CheckBox" parent="VBoxContainer/StepsContainer"]
unique_name_in_owner = true
layout_mode = 2
disabled = true
[node name="UpdateButton" type="Button" parent="VBoxContainer/StepsContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
disabled = true
text = "Run"
[node name="CopyLabel" type="Label" parent="VBoxContainer/StepsContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Copy Example Files"
[node name="CopyCheckBox" type="CheckBox" parent="VBoxContainer/StepsContainer"]
unique_name_in_owner = true
layout_mode = 2
disabled = true
[node name="CopyButton" type="Button" parent="VBoxContainer/StepsContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
disabled = true
text = "Run"
[node name="DeleteLabel" type="Label" parent="VBoxContainer/StepsContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Delete Example Files"
[node name="DeleteCheckBox" type="CheckBox" parent="VBoxContainer/StepsContainer"]
unique_name_in_owner = true
layout_mode = 2
disabled = true
[node name="DeleteButton" type="Button" parent="VBoxContainer/StepsContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
disabled = true
text = "Run"
[node name="SetMainSceneLabel" type="Label" parent="VBoxContainer/StepsContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Set the Main Scene"
[node name="SetMainSceneCheckBox" type="CheckBox" parent="VBoxContainer/StepsContainer"]
unique_name_in_owner = true
layout_mode = 2
disabled = true
[node name="SetMainSceneButton" type="Button" parent="VBoxContainer/StepsContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
disabled = true
text = "Run"
[node name="SetDefaultThemeLabel" type="Label" parent="VBoxContainer/StepsContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Set the Default Theme"
[node name="SetDefaultThemeCheckBox" type="CheckBox" parent="VBoxContainer/StepsContainer"]
unique_name_in_owner = true
layout_mode = 2
disabled = true
[node name="SetDefaultThemeButton" type="Button" parent="VBoxContainer/StepsContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
disabled = true
text = "Run"
[node name="AddInputIconsLabel" type="Label" parent="VBoxContainer/StepsContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Add Input Prompt Icons"
[node name="AddInputIconsCheckBox" type="CheckBox" parent="VBoxContainer/StepsContainer"]
unique_name_in_owner = true
layout_mode = 2
disabled = true
[node name="AddInputIconsButton" type="Button" parent="VBoxContainer/StepsContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
disabled = true
text = "Run"
[connection signal="pressed" from="VBoxContainer/StepsContainer/UpdateButton" to="." method="_on_update_button_pressed"]
[connection signal="pressed" from="VBoxContainer/StepsContainer/CopyButton" to="." method="_on_copy_button_pressed"]
[connection signal="pressed" from="VBoxContainer/StepsContainer/DeleteButton" to="." method="_on_delete_button_pressed"]
[connection signal="pressed" from="VBoxContainer/StepsContainer/SetMainSceneButton" to="." method="_on_set_main_scene_button_pressed"]
[connection signal="pressed" from="VBoxContainer/StepsContainer/SetDefaultThemeButton" to="." method="_on_set_default_theme_button_pressed"]
[connection signal="pressed" from="VBoxContainer/StepsContainer/AddInputIconsButton" to="." method="_on_add_input_icons_button_pressed"]

View File

@@ -0,0 +1,33 @@
@tool
extends ConfirmationDialog
signal theme_selected(theme_file: String)
@export_dir var theme_directories : Array[String] :
set(value):
theme_directories = value
if is_inside_tree():
%FileLister.directories = theme_directories
_fill_with_themes()
func _fill_with_themes() -> void:
%ItemList.clear()
for file in %FileLister.files:
if file is String:
var readable_name = file.get_file().get_basename().capitalize()
%ItemList.add_item(readable_name)
func _ready() -> void:
get_ok_button().disabled = true
func _preview_theme(theme_file: String) -> void:
var theme_resource : Theme = load(theme_file)
if theme_resource == null: return
%ThemePreviewContainer.theme = theme_resource
func _on_item_list_item_selected(index) -> void:
get_ok_button().disabled = false
if index < %FileLister.files.size():
var file = %FileLister.files[index]
_preview_theme(file)
theme_selected.emit(file)

View File

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

View File

@@ -0,0 +1,179 @@
[gd_scene load_steps=3 format=3 uid="uid://cmqcqq54rj454"]
[ext_resource type="Script" uid="uid://cqlg20n7vu4jx" path="res://addons/maaacks_game_template/installer/theme_selection_dialog.gd" id="1_5u0gx"]
[ext_resource type="Script" uid="uid://bij7wsh8d44gv" path="res://addons/maaacks_game_template/base/nodes/utilities/file_lister.gd" id="2_luhgx"]
[node name="ThemeSelectionDialog" type="ConfirmationDialog"]
oversampling_override = 1.0
title = "Use a Starter Theme"
initial_position = 2
size = Vector2i(1024, 704)
visible = true
ok_button_text = "Yes"
dialog_autowrap = true
cancel_button_text = "No"
script = ExtResource("1_5u0gx")
[node name="FileLister" type="Node" parent="."]
unique_name_in_owner = true
script = ExtResource("2_luhgx")
ends_with = ".tres"
[node name="VBoxContainer" type="VBoxContainer" parent="."]
custom_minimum_size = Vector2(560, 443)
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 8.0
offset_top = 8.0
offset_right = -8.0
offset_bottom = -49.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 16
[node name="Label" type="Label" parent="VBoxContainer"]
custom_minimum_size = Vector2(384, 0)
layout_mode = 2
text = "Set the projects default theme to a start theme provided below. These can be customized as needed.
Requires restarting the editor to take full effect."
autowrap_mode = 3
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="ItemList" type="ItemList" parent="VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="VSeparator" type="VSeparator" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/margin_left = 16
theme_override_constants/margin_right = 16
[node name="ThemePreviewContainer" type="TabContainer" parent="VBoxContainer/HBoxContainer/MarginContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
current_tab = 0
[node name="Tab1" type="Control" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer"]
layout_mode = 2
metadata/_tab_index = 0
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab1"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 8
theme_override_constants/margin_top = 8
theme_override_constants/margin_right = 8
theme_override_constants/margin_bottom = 8
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab1/MarginContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab1/MarginContainer/VBoxContainer"]
layout_mode = 2
text = "Label"
horizontal_alignment = 1
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab1/MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="Button" type="Button" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab1/MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Button"
[node name="Button2" type="Button" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab1/MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
toggle_mode = true
button_pressed = true
text = "Button"
[node name="Button3" type="Button" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab1/MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
disabled = true
text = "Button"
[node name="CheckButton" type="CheckButton" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab1/MarginContainer/VBoxContainer"]
layout_mode = 2
text = "CheckButton"
[node name="CheckBox" type="CheckBox" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab1/MarginContainer/VBoxContainer"]
layout_mode = 2
text = "CheckBox"
[node name="MenuButton" type="MenuButton" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab1/MarginContainer/VBoxContainer"]
layout_mode = 2
text = "MenuButton"
[node name="OptionButton" type="OptionButton" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab1/MarginContainer/VBoxContainer"]
layout_mode = 2
selected = 0
item_count = 2
popup/item_0/text = "OptionButton"
popup/item_0/id = 0
popup/item_1/text = "OptionButton2"
popup/item_1/id = 1
[node name="Tab2" type="Control" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer"]
visible = false
layout_mode = 2
metadata/_tab_index = 1
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab2"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 8
theme_override_constants/margin_top = 8
theme_override_constants/margin_right = 8
theme_override_constants/margin_bottom = 8
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab2/MarginContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab2/MarginContainer/VBoxContainer"]
layout_mode = 2
text = "Another label"
horizontal_alignment = 1
[node name="LineEdit" type="LineEdit" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab2/MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="TextEdit" type="TextEdit" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab2/MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="HSlider" type="HSlider" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab2/MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="HScrollBar" type="HScrollBar" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab2/MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="HSeparator" type="HSeparator" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab2/MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="ProgressBar" type="ProgressBar" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab2/MarginContainer/VBoxContainer"]
layout_mode = 2
value = 50.0
[connection signal="item_selected" from="VBoxContainer/HBoxContainer/ItemList" to="." method="_on_item_list_item_selected"]

View File

@@ -0,0 +1,174 @@
@tool
extends Node
## Script for updating the version of a plugin to the latest release on GitHub.
signal update_completed
const DownloadAndExtract = MaaacksGameTemplatePlugin.DownloadAndExtract
const APIClient = MaaacksGameTemplatePlugin.APIClient
const ReleaseNotesLabel = preload("./release_notes_label.gd")
const API_RELEASES_URL := "https://api.github.com/repos/%s/%s/releases"
const UPDATE_CONFIRMATION_MESSAGE := "This will update the contents of the plugin folder (addons/%s/).\nFiles outside of the plugin folder will not be affected.\n\nUpdate %s to v%s?"
const PLUGIN_EXTRACT_PATH := "res://addons/%s/"
const PLUGIN_TEMP_ZIP_PATH := "res://%s_%s_update.zip"
## The directory of the plugin to update. Typically in res://addons/.
@export var plugin_directory : String
## The URL of the GitHub repo to pull new releases.
@export var plugin_github_url : String :
set(value):
plugin_github_url = value
_update_urls()
@export_group("Advanced")
## If true, automatically download the new version when ready.
@export var auto_start : bool = false
## Text to remove from the tag before showing to the user.
@export var replace_tag_name : String = "v"
## The default lowest version to display.
@export var default_version : String = "0.0.0"
## If true, test getting the new version.
## Replace with @export_tool_button for Godot 4.4+
@export var _test_action : bool = false :
set(value):
if value and Engine.is_editor_hint():
get_newest_version()
@onready var _api_client : APIClient = $APIClient
@onready var _download_and_extract_node : DownloadAndExtract = $DownloadAndExtract
@onready var _update_confirmation_dialog : ConfirmationDialog = $UpdateConfirmationDialog
@onready var _installing_dialog : AcceptDialog = $InstallingDialog
@onready var _error_dialog : AcceptDialog = $ErrorDialog
@onready var _success_dialog : AcceptDialog = $SuccessDialog
@onready var _release_notes_label : ReleaseNotesLabel = %ReleaseNotesLabel
@onready var _update_label : Label = %UpdateLabel
@onready var _warning_message_button : LinkButton = %WarningMessageButton
@onready var _warning_message_label : Label = %WarningMessageLabel
@onready var _release_notes_button : LinkButton = %ReleaseNotesButton
@onready var _release_notes_panel : Panel = %ReleaseNotesPanel
@onready var _stage_label : Label = %StageLabel
@onready var _progress_bar : ProgressBar = %ProgressBar
var _zipball_url : String
var _newest_version : String
var _plugin_name : String
var _current_plugin_version : String
func _load_plugin_details() -> void:
if plugin_directory.is_empty(): return
for enabled_plugin in ProjectSettings.get_setting("editor_plugins/enabled"):
if enabled_plugin.contains(plugin_directory):
var config := ConfigFile.new()
var error = config.load(enabled_plugin)
if error != OK:
return
_current_plugin_version = config.get_value("plugin", "version", default_version)
_plugin_name = config.get_value("plugin", "name", "Plugin")
func _update_urls() -> void:
if plugin_github_url.is_empty(): return
if _api_client == null: return
var regex := RegEx.create_from_string("https:\\/\\/github\\.com\\/([\\w-]+)\\/([\\w-]+)\\/*")
var regex_match := regex.search(plugin_github_url)
if regex_match == null: return
var username := regex_match.get_string(1)
var repository := regex_match.get_string(2)
_api_client.api_url = API_RELEASES_URL % [username, repository]
func _show_error_dialog(error : String) -> void:
_error_dialog.show()
_error_dialog.dialog_text = "%s!" % error
func _show_success_dialog() -> void:
_success_dialog.show()
_success_dialog.dialog_text = "%s updated to v%s." % [_plugin_name, _newest_version]
func _on_api_client_request_failed(error : String) -> void:
_show_error_dialog(error)
func _on_api_client_response_received(response_body : Variant) -> void:
if response_body is not Array:
push_error("Response was not an array")
return
if response_body.is_empty():
push_error("Response was an empty array")
return
var latest_release : Dictionary = response_body.front()
_newest_version = default_version
if latest_release.has("tag_name"):
var tag_name = latest_release["tag_name"]
if replace_tag_name:
tag_name = tag_name.replacen(replace_tag_name, "")
_newest_version = tag_name
if latest_release.has("zipball_url"):
_zipball_url = latest_release["zipball_url"]
_download_and_extract_node.zip_url = _zipball_url
_download_and_extract_node.zip_file_path = PLUGIN_TEMP_ZIP_PATH % [plugin_directory, _newest_version]
_update_label.text = UPDATE_CONFIRMATION_MESSAGE % [plugin_directory, _plugin_name, _newest_version]
if latest_release.has("body"):
_release_notes_label.from_release_notes(latest_release["body"])
_update_confirmation_dialog.show()
func _on_download_and_extract_zip_saved() -> void:
OS.move_to_trash(ProjectSettings.globalize_path(PLUGIN_EXTRACT_PATH % plugin_directory))
func _on_download_and_extract_run_failed(error : String) -> void:
_show_error_dialog(error)
func _on_download_and_extract_run_completed() -> void:
update_completed.emit()
_show_success_dialog()
func _on_error_dialog_canceled() -> void:
queue_free()
func _on_error_dialog_confirmed() -> void:
queue_free()
func _on_success_dialog_canceled() -> void:
queue_free()
func _on_success_dialog_confirmed() -> void:
queue_free()
func _on_update_confirmation_dialog_canceled() -> void:
queue_free()
func _on_update_confirmation_dialog_confirmed() -> void:
_download_and_extract_node.run()
_installing_dialog.show()
func _on_warning_message_button_pressed():
_warning_message_label.show()
_warning_message_button.hide()
func _on_release_notes_button_pressed() -> void:
_release_notes_panel.show()
_release_notes_button.hide()
func get_newest_version() -> void:
_api_client.request()
func _ready() -> void:
_load_plugin_details()
_update_confirmation_dialog.hide()
_installing_dialog.hide()
_error_dialog.hide()
_success_dialog.hide()
if auto_start:
get_newest_version()
func _process(_delta : float) -> void:
if _installing_dialog.visible:
_progress_bar.value = _download_and_extract_node.get_progress()
match _download_and_extract_node.stage:
DownloadAndExtract.DownloadAndExtractStage.DOWNLOAD:
_stage_label.text = "Downloading..."
DownloadAndExtract.DownloadAndExtractStage.SAVE:
_stage_label.text = "Saving..."
DownloadAndExtract.DownloadAndExtractStage.EXTRACT:
_stage_label.text = "Extracting..."
DownloadAndExtract.DownloadAndExtractStage.DELETE:
_stage_label.text = "Cleaning up..."
DownloadAndExtract.DownloadAndExtractStage.NONE:
_installing_dialog.hide()

View File

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

View File

@@ -0,0 +1,158 @@
[gd_scene load_steps=5 format=3 uid="uid://d28bi7wdv8hbp"]
[ext_resource type="Script" uid="uid://txkhnw0u8jy4" path="res://addons/maaacks_game_template/installer/update_plugin.gd" id="1_s6qpc"]
[ext_resource type="PackedScene" uid="uid://cf0hkngq1mgfy" path="res://addons/maaacks_game_template/utilities/api_client.tscn" id="2_s6pdq"]
[ext_resource type="PackedScene" uid="uid://5hhnbgqjwnic" path="res://addons/maaacks_game_template/utilities/download_and_extract.tscn" id="3_s6pdq"]
[ext_resource type="Script" uid="uid://cgv1xnmh7vr66" path="res://addons/maaacks_game_template/installer/release_notes_label.gd" id="4_1amwf"]
[node name="UpdatePlugin" type="Node"]
script = ExtResource("1_s6qpc")
plugin_directory = "maaacks_game_template"
plugin_github_url = "https://github.com/Maaack/Godot-Minimal-Game-Template"
[node name="APIClient" parent="." instance=ExtResource("2_s6pdq")]
api_url = "https://api.github.com/repos/Maaack/Godot-Minimal-Game-Template/releases"
request_method = 0
[node name="DownloadAndExtract" parent="." instance=ExtResource("3_s6pdq")]
extract_path = "res://"
path_match_string = "addons/"
skip_base_zip_dir = true
force = true
[node name="UpdateConfirmationDialog" type="ConfirmationDialog" parent="."]
auto_translate_mode = 1
title = "Update Plugin?"
initial_position = 2
size = Vector2i(640, 360)
dialog_autowrap = true
[node name="MarginContainer" type="MarginContainer" parent="UpdateConfirmationDialog"]
offset_left = 8.0
offset_top = 8.0
offset_right = 632.0
offset_bottom = 311.0
theme_override_constants/margin_bottom = 16
[node name="VBoxContainer" type="VBoxContainer" parent="UpdateConfirmationDialog/MarginContainer"]
layout_mode = 2
[node name="UpdateLabel" type="Label" parent="UpdateConfirmationDialog/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "This will update the contents of the plugin folder (addons/plugin_directory/).
Files outside of the plugin folder will not be affected.
Update to v0.0.0?"
[node name="HSeparator" type="HSeparator" parent="UpdateConfirmationDialog/MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="WarningMessageButton" type="LinkButton" parent="UpdateConfirmationDialog/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Warning About Updating"
[node name="WarningMessageLabel" type="Label" parent="UpdateConfirmationDialog/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
visible = false
custom_minimum_size = Vector2(624, 205)
layout_mode = 2
text = "Not all updates are backwards compatible!
Updating can cause issues with previously copied examples. It is recommended to delete any copied example files before starting an update, if it is an option. Otherwise, pay attention to the Release Notes for an idea for how changes may impact your project.
Lastly, save your work and proceed with caution."
autowrap_mode = 3
[node name="ReleaseNotesButton" type="LinkButton" parent="UpdateConfirmationDialog/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Show Release Notes"
[node name="ReleaseNotesPanel" type="Panel" parent="UpdateConfirmationDialog/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
visible = false
custom_minimum_size = Vector2(0, 420)
layout_mode = 2
size_flags_vertical = 3
[node name="ReleaseNotesLabel" type="RichTextLabel" parent="UpdateConfirmationDialog/MarginContainer/VBoxContainer/ReleaseNotesPanel"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_vertical = 3
focus_mode = 2
bbcode_enabled = true
selection_enabled = true
script = ExtResource("4_1amwf")
h1_font_size = 64
h2_font_size = 48
h3_font_size = 32
h4_font_size = 24
h5_font_size = 16
h6_font_size = 12
bold_headings = true
[node name="InstallingDialog" type="AcceptDialog" parent="."]
auto_translate_mode = 1
title = "Installing..."
initial_position = 2
size = Vector2i(400, 111)
[node name="MarginContainer" type="MarginContainer" parent="InstallingDialog"]
offset_left = 4.0
offset_top = 4.0
offset_right = 396.0
offset_bottom = 96.0
theme_override_constants/margin_left = 16
theme_override_constants/margin_top = 16
theme_override_constants/margin_right = 16
theme_override_constants/margin_bottom = 16
[node name="VBoxContainer" type="VBoxContainer" parent="InstallingDialog/MarginContainer"]
layout_mode = 2
alignment = 1
[node name="StageLabel" type="Label" parent="InstallingDialog/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
horizontal_alignment = 1
vertical_alignment = 1
[node name="ProgressBar" type="ProgressBar" parent="InstallingDialog/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
max_value = 1.0
step = 0.001
[node name="ErrorDialog" type="AcceptDialog" parent="."]
auto_translate_mode = 1
title = "Error!"
initial_position = 2
size = Vector2i(400, 128)
[node name="SuccessDialog" type="AcceptDialog" parent="."]
auto_translate_mode = 1
title = "Update Complete"
initial_position = 2
size = Vector2i(400, 128)
dialog_text = "%s updated to v%s."
[connection signal="request_failed" from="APIClient" to="." method="_on_api_client_request_failed"]
[connection signal="response_received" from="APIClient" to="." method="_on_api_client_response_received"]
[connection signal="run_completed" from="DownloadAndExtract" to="." method="_on_download_and_extract_run_completed"]
[connection signal="run_failed" from="DownloadAndExtract" to="." method="_on_download_and_extract_run_failed"]
[connection signal="zip_saved" from="DownloadAndExtract" to="." method="_on_download_and_extract_zip_saved"]
[connection signal="canceled" from="UpdateConfirmationDialog" to="." method="_on_update_confirmation_dialog_canceled"]
[connection signal="confirmed" from="UpdateConfirmationDialog" to="." method="_on_update_confirmation_dialog_confirmed"]
[connection signal="pressed" from="UpdateConfirmationDialog/MarginContainer/VBoxContainer/WarningMessageButton" to="." method="_on_warning_message_button_pressed"]
[connection signal="pressed" from="UpdateConfirmationDialog/MarginContainer/VBoxContainer/ReleaseNotesButton" to="." method="_on_release_notes_button_pressed"]
[connection signal="canceled" from="ErrorDialog" to="." method="_on_error_dialog_canceled"]
[connection signal="confirmed" from="ErrorDialog" to="." method="_on_error_dialog_confirmed"]
[connection signal="canceled" from="SuccessDialog" to="." method="_on_success_dialog_canceled"]
[connection signal="confirmed" from="SuccessDialog" to="." method="_on_success_dialog_confirmed"]