First commit 🎉

This commit is contained in:
Tony Bark 2025-07-17 01:49:18 -04:00
commit 43ea213f9b
728 changed files with 37080 additions and 0 deletions

View file

@ -0,0 +1,208 @@
@tool
class_name PopochiuMigrationHelper
extends Node
## Helper class to assist migrating Popochiu Projects to newer versions
const MIGRATIONS_PATH = "res://addons/popochiu/migration/migrations/"
const MIGRATION_SECTION = "last_migration"
const MIGRATION_KEY = "version"
const POPOCHIU_PATH = "res://popochiu/"
static var old_settings_file := PopochiuResources.GAME_PATH.path_join("popochiu_settings.tres")
#region Public #####################################################################################
static func get_migrations_count() -> int:
return DirAccess.get_files_at(MIGRATIONS_PATH).size()
## Returns the game folder path. If this returns [member POPOCHIU_PATH], then the project is from
## Popochiu 1.x or Popochiu 2.0.0-AlphaX.
static func get_game_path() -> String:
if (
DirAccess.dir_exists_absolute(PopochiuResources.GAME_PATH)
and DirAccess.dir_exists_absolute(POPOCHIU_PATH)
):
return POPOCHIU_PATH
elif DirAccess.dir_exists_absolute(PopochiuResources.GAME_PATH):
return PopochiuResources.GAME_PATH
else: # Error cannot access the game folders
return ""
## Returns the user project migration version from the "res://game/popochiu_data.cfg" file.
## If the Popochiu Migration Version is greater than the user project migration version
## then a migration needs to be done.
## If -1 gets returned then an error has occurred.
static func get_user_migration_version() -> int:
# popochiu_data.cfg config file could not be loaded, return error
if PopochiuResources.get_data_cfg() == null:
PopochiuUtils.print_error("Can't load [code]popochiu_data.cfg[/code] file.")
return -1
if PopochiuResources.has_data_value(MIGRATION_SECTION, MIGRATION_KEY):
# Return the migration version in the popochiu_data.cfg file
return PopochiuResources.get_data_value(MIGRATION_SECTION, MIGRATION_KEY, 1)
else:
# Run Migration 1 and so on
return 0
## Returns [code]true[/code] if this is an empty project: no rooms, no characters, no inventory
## items, no dialogues, and no audio files.
static func is_empty_project() -> bool:
return (
get_game_path() == PopochiuResources.GAME_PATH
and PopochiuResources.get_section_keys("rooms").is_empty()
and PopochiuResources.get_section_keys("characters").is_empty()
and PopochiuResources.get_section_keys("inventory_items").is_empty()
and PopochiuResources.get_section_keys("dialogs").is_empty()
and PopochiuResources.get_section_keys("audio").is_empty()
)
## Returns [true] if the current Popochiu migration version is newer than the user's migration
## version, which means a migration is needed.
static func is_migration_needed() -> bool:
return get_migrations_count() > get_user_migration_version()
## Updates [code]res://game/popochiu_data.cfg[/code] migration version to [param version].
static func update_user_migration_version(new_version: int) -> void:
if PopochiuResources.set_data_value(MIGRATION_SECTION, MIGRATION_KEY, new_version) != OK:
PopochiuUtils.print_error(
"Couldn't update the Migration version from [b]%d[/b] to [b]%d[/b] in Data file." % [
get_migrations_count(), new_version
]
)
## Executes the [param steps] in [param migration] one by one and returns [code]true[/code] if
## all finished without failing. It stops execution if a step fails.
static func execute_migration_steps(migration: PopochiuMigration, steps: Array) -> bool:
if steps.is_empty():
PopochiuUtils.print_error(
"No steps to execute for Migration %d" % migration.get_version()
)
await PopochiuEditorHelper.frame_processed()
return false
var idx := 0
for step: Callable in steps:
# Update the migration step interface to show a loader
migration.start_step(idx)
# Run the actual step
var completion_type: PopochiuMigration.Completion = await step.call()
if completion_type in [
PopochiuMigration.Completion.DONE, PopochiuMigration.Completion.IGNORED
]:
# Update the interface, no more loader
await migration.step_finished(idx, completion_type)
else:
return false
idx += 1
return true
## Helper function to recursively scan the directory in [param path] and return an [Array] of
## absolute file paths with the specified extension.
static func get_absolute_file_paths_for_file_extensions(
path: String, file_extensions: Array[String], folders_to_ignore: Array[String] = []
) -> Array:
var file_paths := []
var dir: DirAccess = DirAccess.open(path)
if not dir.dir_exists(path):
return file_paths
dir.list_dir_begin()
var element_name = dir.get_next()
while not element_name.is_empty():
var file_path := path.path_join(element_name)
if dir.current_is_dir():
if element_name in folders_to_ignore:
element_name = dir.get_next()
continue
# Recurse into subdirectories
file_paths += get_absolute_file_paths_for_file_extensions(
file_path, file_extensions, folders_to_ignore
)
elif file_extensions.is_empty() or file_path.get_extension() in file_extensions:
# Add files with the specified extension to the [file_paths] array
file_paths.append(file_path)
element_name = dir.get_next()
dir.list_dir_end()
return file_paths
## Looks in the text of each file in [param file_paths] for coincidences of [param from], and
## replace them by [param to]. If any replacement was done, returns [code]true[/code].
static func replace_text_in_files(from: String, to: String, file_paths: Array) -> bool:
return PopochiuUtils.any_exhaustive(
file_paths,
func (file_path: String) -> bool:
if not FileAccess.file_exists(file_path):
return true
var file_read := FileAccess.open(file_path, FileAccess.READ)
var text := file_read.get_as_text()
file_read.close()
if not from in text:
return false
var file_write := FileAccess.open(file_path, FileAccess.WRITE)
text = text.replace(from, to)
file_write.store_string(text)
file_write.close()
return true
)
## Returns [true] if the game is checked as pixel-art based on the value in
## [code]popochiu/pixel/pixel_art_textures[/code] or in the old [code]popochiu_settings.tres[/code]
## file in versions prior to [i]2.0.0-beta3[/i].
static func is_pixel_art_game() -> bool:
var is_pixel_art := PopochiuConfig.is_pixel_art_textures()
if FileAccess.file_exists(old_settings_file):
var old_settings := load(old_settings_file)
if old_settings.get("is_pixel_art_game") != null:
is_pixel_art = old_settings.is_pixel_art_game
return is_pixel_art
## Checks if [param text] exists in the text of the file at [param file_path].
static func is_text_in_file(text: String, file_path: String) -> bool:
var file_read := FileAccess.open(file_path, FileAccess.READ)
var file_text := file_read.get_as_text()
file_read.close()
return text in file_text
## Replaces all text matches in all scripts of the game with the [param replacements].
## [param replacements] is an [Array] of [Dictionary] with the keys [code]from[/code] and
## [code]to[/code] to replace in the scripts. The [param folders_to_ignore] is an [Array] of
## folder names that should be ignored when searching for scripts.
static func replace_in_scripts(
replacements: Array[Dictionary], folders_to_ignore: Array[String] = []
) -> bool:
var scripts_paths := get_absolute_file_paths_for_file_extensions(
PopochiuResources.GAME_PATH, ["gd"], folders_to_ignore
)
var replaced_matches := 0
for dic: Dictionary in replacements:
replaced_matches += 1 if replace_text_in_files(dic.from, dic.to, scripts_paths) else 0
return replaced_matches > 0
#endregion

View file

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

View file

@ -0,0 +1,128 @@
@tool
class_name PopochiuMigration
extends Node
## This provides the core features needed to do a migration.
##
## Migration files in [code]res://addons/popochiu/migration/migrations/*.gd[/code] should
## extend this class.
enum Completion {
FAILED,
DONE,
IGNORED,
}
signal step_started(migration: PopochiuMigration, idx: int)
signal step_completed(migration: PopochiuMigration)
var _version := -1
var _reload_needed := false
## [Array] of completed steps
var completed := []
## [Array] of ignored steps
var ignored := []
#region Godot ######################################################################################
func _init() -> void:
set_migration_version(get("VERSION"))
#endregion
#region Virtual ####################################################################################
func _do_migration() -> bool:
return false
func _is_reload_required() -> bool:
return _reload_needed
#endregion
#region Public #####################################################################################
## Sets [param version] as the migration version for the migration script.
func set_migration_version(version: int) -> void:
_version = version
## Returns the [member _version] of this migration.
func get_version() -> int:
return _version
func get_migration_name() -> String:
return "Migration %d" % _version
## Returns [true] if the current Popochiu migration version is newer than the user's migration
## version, which means a migration is needed.
func is_migration_needed() -> bool:
return _version > PopochiuMigrationHelper.get_user_migration_version()
## A helper function to display an error message in the [b]Output[/b] if there is an error doing
## the migration, or a message if it is successful. This updates the [code]popochiu_data.cfg[/code]
## file to have a new migration version if successful. [param migration] is an instantiated
## [PopochiuMigration] from [code]res://addons/popochiu/migration/migrations/*.gd[/code].
## [param version] is an integer for the migration version being run. This is intended to be called
## [DoMigration].
static func run_migration(migration: PopochiuMigration, version: int) -> bool:
if not await migration.do_migration():
PopochiuUtils.print_error("Migration %d failed" % version)
return false
else:
PopochiuMigrationHelper.update_user_migration_version(version)
PopochiuUtils.print_normal("Migration %d completed" % version)
return true
## Attempts to do the migration. Returns [code]true[/code] if successful.
func do_migration() -> bool:
# Make sure the user migration version is less then this migration version
if not can_do_migration():
return false
PopochiuUtils.print_normal("Performing Migration %s: %s" % [
str(get("VERSION")), get("DESCRIPTION")
])
return await _do_migration()
## Makes sure that the user migration version is less than the current migration version and the
## [_version] variable has been set to a value. Returns [code]true[/code] if migration can be done.
func can_do_migration() -> bool:
# If the user migration version is equal to, or higher than [_version], ignore the migration.
# If [_version] is less than 0, then version has not been set so do not do the migration
return PopochiuMigrationHelper.get_user_migration_version() < _version and _version >= 0
## Emits [signal step_started] sending the [param idx], which is the index of the migration step
## that just started.
func start_step(idx: int) -> void:
step_started.emit(self, idx)
## Add the migration step ([param idx]) to its corresponding array depending on whether it was
## completed ([param type] == [constant Completion.DONE]) or ignored
## ([param type] == [constant Completion.IGNORED]). Then emits [signal step_completed] so the GUI
## provides feedback to the developer.
func step_finished(idx: int, type: Completion) -> void:
match type:
Completion.DONE:
completed.append(idx)
Completion.IGNORED:
ignored.append(idx)
step_completed.emit(self)
## Returns [code]true[/code] if this migration needs an Engine restart once applied.
func is_reload_required() -> bool:
return _is_reload_required()
#endregion

View file

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

View file

@ -0,0 +1,39 @@
@tool
class_name PopochiuMigrationX # Change X to be the correct version number for migration
extends PopochiuMigration
# Update constant values to be correct for your migration
const VERSION = -1
const DESCRIPTION = "short description of migration goes here"
const STEPS = [
# Include a short description of each step here
#"Step 1"
]
#region Virtual ####################################################################################
## This is code specific for this migration. This should return [code]true[/code] if the migration
## is successful. This is called from [method do_migration] which checks to make sure the migration
## should be done before calling this.
func _do_migration() -> bool:
return await PopochiuMigrationHelper.execute_migration_steps(
self,
[
# Include the function names for each step here
#_step1
]
)
func _is_reload_required() -> bool:
return false
#endregion
#region Private ####################################################################################
#func _step1() -> Completion:
#return Completion.DONE
#endregion

View file

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

View file

@ -0,0 +1,449 @@
@tool
class_name PopochiuMigration1
extends PopochiuMigration
## Migrates projects from Alpha to Beta.
const VERSION = 1
const DESCRIPTION = "Migrate project structure from alpha-x to beta"
const STEPS = [
"Delete [b]res://popochiu/autoloads/[/b].",
"Move folders in [b]res://popochiu/[/b] to [b]res://game/[/b]. (pre [i]beta 1[/i])",
"Rename folders and files to snake_case. (pre [i]alpha 2[/i])",
#"Select the default GUI template. (pre [i]beta 1[/i])",
"Update paths in [b]res://game/popochiu_data.cfg[/b]. (pre [i]beta 1[/i])",
"Rename [b]res://popochiu/[/b] references to [b]res://game/[/b]. (pre [i]beta 1[/i])",
"Rename [b]item_xxx[/b] to [b]inventory_item_xxx[/b] for inventory items. (pre [i]beta 1[/i])",
"Update [b]PopochiuCharacter[/b]s. (pre [i]beta 1[/i])",
"Replace deprecated method calls. (pre [i]beta 1[/i])",
]
const DEFAULT_GUI_TEMPLATE = "SimpleClick"
const PopochiuGuiTemplatesHelper = preload(
"res://addons/popochiu/editor/helpers/popochiu_gui_templates_helper.gd"
)
var _snake_renamed := []
#region Virtual ####################################################################################
## This is code specific for this migration. This should return [code]true[/code] if the migration
## is successful. This is called from [method do_migration] which checks to make sure the migration
## should be done before calling this.
func _do_migration() -> bool:
return await PopochiuMigrationHelper.execute_migration_steps(
self,
[
_delete_popochiu_folder_autoloads,
_move_game_data,
_rename_files_and_folders_to_snake_case,
#_select_gui_template,
_rebuild_popochiu_data_file,
_rename_game_folder_references,
_update_inventory_items,
_update_characters,
_replace_deprecated_method_calls,
]
)
#endregion
#region Public #####################################################################################
func is_migration_needed() -> bool:
return super() and !_ignore_popochiu_folder_step()
#endregion
#region Private ####################################################################################
## Checks if the folder where the game is stored is the one used since Beta 1. This means the
## [code]res://popochiu/[/code] folder doesn't exists in the project
func _ignore_popochiu_folder_step() -> bool:
return PopochiuMigrationHelper.get_game_path() == PopochiuResources.GAME_PATH
## Delete the [constant PopochiuMigrationHelper.POPOCHIU_PATH] autoloads directory if it exists.
func _delete_popochiu_folder_autoloads() -> Completion:
if _ignore_popochiu_folder_step():
return Completion.IGNORED
# No need to move the autoloads directory as Popochiu 2 creates them automatically. This will
# also fix the issue related with using [preload()] in old [A] autoload.
var all_done := false
var autoloads_path := PopochiuMigrationHelper.POPOCHIU_PATH.path_join("Autoloads")
if DirAccess.dir_exists_absolute(autoloads_path):
all_done = PopochiuEditorHelper.remove_recursive(autoloads_path)
elif DirAccess.dir_exists_absolute(autoloads_path.to_lower()):
all_done = PopochiuEditorHelper.remove_recursive(autoloads_path.to_lower())
_reload_needed = true
return Completion.DONE if all_done else Completion.FAILED
## Moves game data from [constant PopochiuMigrationHelper.POPOCHIU_PATH] to
## [constant PopochiuResources.GAME_PATH].
func _move_game_data() -> Completion:
if _ignore_popochiu_folder_step():
return Completion.IGNORED
var folders := DirAccess.get_directories_at(PopochiuMigrationHelper.POPOCHIU_PATH)
var files := DirAccess.get_files_at(PopochiuMigrationHelper.POPOCHIU_PATH)
# Move files from PopochiuMigrationHelper.POPOCHIU_PATH to PopochiuResources.GAME_PATH
for file in files:
var src := PopochiuMigrationHelper.POPOCHIU_PATH.path_join(file)
var dest := PopochiuResources.GAME_PATH.path_join(file.to_snake_case())
var err := DirAccess.rename_absolute(src, dest)
if err != OK:
PopochiuUtils.print_error("Couldn't move %s to %s: %d" % [src, dest, err])
return Completion.FAILED
# Move folders from PopochiuMigrationHelper.POPOCHIU_PATH to PopochiuResources.GAME_PATH
for folder in folders:
var src := PopochiuMigrationHelper.POPOCHIU_PATH.path_join(folder)
var dest := PopochiuResources.GAME_PATH.path_join(folder.to_snake_case())
DirAccess.remove_absolute(dest)
var err := DirAccess.rename_absolute(src, dest)
if err != OK:
PopochiuUtils.print_error("Couldn't move %s to %s: %d" % [src, dest, err])
return Completion.FAILED
# All files/folders moved to PopochiuResources.GAME_PATH so delete the
# PopochiuMigrationHelper.POPOCHIU_PATH directory
_reload_needed = true
return (
Completion.DONE if DirAccess.remove_absolute(PopochiuMigrationHelper.POPOCHIU_PATH) == OK
else Completion.FAILED
)
## Rename [constant PopochiuResources.GAME_PATH] files and folders to snake case.
func _rename_files_and_folders_to_snake_case() -> Completion:
var any_renamed := PopochiuUtils.any_exhaustive(
PopochiuEditorHelper.get_absolute_directory_paths_at(PopochiuResources.GAME_PATH),
func (folder: String) -> bool:
var any_file_renamed := _rename_files_to_snake_case(folder)
var any_folder_renamed := _rename_folders_to_snake_case(folder)
return any_file_renamed or any_folder_renamed
)
if any_renamed:
# Go over .gd, .tscn, .tres, and .cfg files to update their references to snake renamed
# files and folders
var files := PopochiuMigrationHelper.get_absolute_file_paths_for_file_extensions(
PopochiuResources.GAME_PATH, ["gd", "tscn", "tres", "cfg"], ["autoloads"]
)
for names_pair: Dictionary in _snake_renamed:
PopochiuMigrationHelper.replace_text_in_files(names_pair.old, names_pair.new, files)
_reload_needed = true
return Completion.DONE if any_renamed else Completion.IGNORED
## Renames all the folders and files in [param folder_path] to snake_case.
func _rename_files_to_snake_case(folder_path: String) -> bool:
return PopochiuUtils.any_exhaustive(
Array(DirAccess.get_files_at(folder_path)),
func (file: String) -> bool:
var src := folder_path.path_join(file)
var dest := folder_path.path_join(file.to_snake_case())
if src != dest:
_snake_renamed.append({
old = src.get_file(),
new = dest.get_file()
})
DirAccess.rename_absolute(src, dest)
return true
return false
)
## Rename [param path] folders and the content in the folders recursively to snake_case
func _rename_folders_to_snake_case(path: String) -> bool:
return PopochiuUtils.any_exhaustive(
PopochiuEditorHelper.get_absolute_directory_paths_at(path),
func (sub_folder: String) -> bool:
# recursively rename files/folders to snake_case
var any_subfolder_renamed = _rename_folders_to_snake_case(sub_folder)
var any_file_renamed := _rename_files_to_snake_case(sub_folder)
var snake_case_name := sub_folder.to_snake_case()
var folder_renamed := sub_folder != snake_case_name
if folder_renamed:
_snake_renamed.append({
old = sub_folder.replace(PopochiuResources.GAME_PATH, ""),
new = snake_case_name.replace(PopochiuResources.GAME_PATH, ""),
})
DirAccess.rename_absolute(sub_folder, snake_case_name)
folder_renamed = true
return any_subfolder_renamed or any_file_renamed or folder_renamed
)
## Copies the 2-click Context-sensitive GUI to [code]res://game/gui/[/code] if there
## is no GUI template selected.
func _select_gui_template() -> Completion:
if PopochiuResources.get_data_value("ui", "template", "").is_empty():
# Assume the project is from Popochiu 1.x or Popochiu 2 - Alpha X and assign the SimpleClick
# GUI template
await PopochiuGuiTemplatesHelper.copy_gui_template(
DEFAULT_GUI_TEMPLATE,
func (_progress: int, _msg: String) -> void: return,
func () -> void: return,
)
return Completion.DONE
else:
await PopochiuEditorHelper.frame_processed()
return Completion.IGNORED
## Updates the paths to rooms, characters, inventory items and dialogs so they point to
## [code]res://game/[/code] (for cases where the project still used [code]res://popochiu/[/code]).
func _rebuild_popochiu_data_file() -> bool:
if PopochiuMigrationHelper.is_text_in_file(
PopochiuMigrationHelper.POPOCHIU_PATH, PopochiuResources.DATA
):
_rebuild_popochiu_data_section(PopochiuResources.GAME_PATH, "rooms")
_rebuild_popochiu_data_section(PopochiuResources.GAME_PATH, "characters")
_rebuild_popochiu_data_section(PopochiuResources.GAME_PATH, "inventory_items")
_rebuild_popochiu_data_section(PopochiuResources.GAME_PATH, "dialogs")
return (
Completion.DONE if PopochiuResources.set_data_value("setup", "done", true) == OK
else Completion.FAILED
)
return Completion.IGNORED
## Updates the path to [param game_path] for each value in the [param data_section] in the
## [code]popochiu_data.cfg[/code] ([ConfigFile]) file.
func _rebuild_popochiu_data_section(game_path: String, data_section: String) -> void:
var data_path := game_path.path_join(data_section)
var section_name := data_section
# Make sure the section name does not have an "s" character at the end
if section_name.length() > 0 and section_name[-1] == "s":
section_name = section_name.rstrip("s")
# Add the keys and tres files for each directory in the data section
for folder: String in DirAccess.get_directories_at(data_path):
var key_name := folder.to_pascal_case()
var tres_file := "%s_%s.tres" % [section_name, folder]
var key_value := game_path.path_join("%s/%s/%s" % [data_section, folder, tres_file])
PopochiuResources.set_data_value(data_section, key_name, key_value)
## Renames uses of [b]res://popochiu/[/b] to [b]res://game/[/b] in [code].tscn[/code],
## [code].tres[/code], and [code].gd[/code] files.
func _rename_game_folder_references() -> Completion:
var changes_done := false
# Update the path to the main scene in Project Settings
var main_scene_path := ProjectSettings.get_setting(PopochiuResources.MAIN_SCENE, "")
if PopochiuMigrationHelper.POPOCHIU_PATH in main_scene_path:
changes_done = true
ProjectSettings.set_setting(PopochiuResources.MAIN_SCENE, main_scene_path.replace(
PopochiuMigrationHelper.POPOCHIU_PATH, PopochiuResources.GAME_PATH
))
ProjectSettings.save()
# Go over gd, tscn, and tres files to update their references to res://popochiu/ by res://game/
var files := PopochiuMigrationHelper.get_absolute_file_paths_for_file_extensions(
PopochiuResources.GAME_PATH, ["gd", "tscn", "tres", "cfg"], ["autoloads"]
)
if PopochiuMigrationHelper.replace_text_in_files(
PopochiuMigrationHelper.POPOCHIU_PATH, PopochiuResources.GAME_PATH, files
):
changes_done = true
_reload_needed = changes_done
return Completion.DONE if changes_done else Completion.IGNORED
## Updates all inventory items in the project so:[br]
## - Their files (.tscn, .gd, and .tres) match the naming defined since beta-1 (inventory_item_*.*).
## - All the paths inside those files point to the new file.
## - Fixes a naming issue from alpha-1 where the root node name was set wrong. And also applies the
## [constant CanvasItem.TEXTURE_FILTER_NEAREST] to each node in case the project is marked as
## Pixel-art game.
func _update_inventory_items() -> Completion:
var inventory_item_files := PopochiuMigrationHelper.get_absolute_file_paths_for_file_extensions(
PopochiuResources.INVENTORY_ITEMS_PATH,
["tscn", "gd", "tres"]
)
# Get all the inventory item file paths that were previously called item_*.*
var scene_files := []
var files_to_update := inventory_item_files.filter(
func (file_path: String) -> bool:
if file_path.get_extension() == "tscn":
scene_files.append(file_path)
return "/item_" in file_path
)
if files_to_update.is_empty():
return Completion.IGNORED
var update_done := scene_files.all(_update_root_name_and_texture_filter)
if update_done:
update_done = files_to_update.all(_rename_inventory_item_files_name)
if update_done and PopochiuMigrationHelper.is_text_in_file("/item_", PopochiuResources.I_SNGL):
update_done = PopochiuMigrationHelper.replace_text_in_files(
"/item_", "/inventory_item_", [PopochiuResources.I_SNGL]
)
_reload_needed = update_done
return Completion.DONE if update_done else Completion.FAILED
## Loads the [PopochiuInventoryItem] in [param scene_file_path] and updates its root node name
## and makes its [member CanvasItem.texture_filter] to [constant CanvasItem.TEXTURE_FILTER_NEAREST]
## if this is a Pixel-art game.
func _update_root_name_and_texture_filter(scene_file_path: String) -> bool:
# Update root node name to PascalCase
var scene: PopochiuInventoryItem = (load(scene_file_path) as PackedScene).instantiate()
scene.name = "Item%s" % scene.script_name.to_pascal_case()
# Update the texture_filter if needed
if PopochiuMigrationHelper.is_pixel_art_game():
scene.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
if PopochiuEditorHelper.pack_scene(scene, scene_file_path) != OK:
PopochiuUtils.print_error(
"Couldn't update root node name for inventory item: [b]%s[/b]" % scene.script_name
)
return false
return true
## For each [PopochiuInventoryItem] in [param files_paths], updates the root node name to PascalCase,
## renames the files to inventory_item_*.*, and updates the internal paths to match the new path.
func _rename_inventory_item_files_name(file_path: String) -> bool:
var old_file_name := file_path.get_file().get_basename()
var new_file_name := old_file_name.replace("item_", "inventory_item_")
PopochiuMigrationHelper.replace_text_in_files(old_file_name, new_file_name, [file_path])
DirAccess.rename_absolute(file_path, file_path.replace("/item_", "/inventory_item_"))
return true
## For each [PopochiuCharacter] updates the way its voices are set to the structure defined in
## alpha-3. It also adds new required nodes like an [AnimationPlayer] and a [CollisionPolygon2D] for
## the [code]ScalingPolygon[/code].
func _update_characters() -> Completion:
# Get the characters' .tscn files
var file_paths := PopochiuMigrationHelper.get_absolute_file_paths_for_file_extensions(
PopochiuResources.CHARACTERS_PATH,
["tscn"]
)
var any_character_updated := PopochiuUtils.any_exhaustive(file_paths, _update_character)
return Completion.DONE if any_character_updated else Completion.IGNORED
## Loads the [PopochiuCharacter] in [param scene_path] and:[br]
## - Updates its [member PopochiuCharacter.voices] so they match the structure defined in alpha-3.
## - Makes its [member CanvasItem.texture_filter] to [constant CanvasItem.TEXTURE_FILTER_NEAREST] if
## this is a Pixel-art game.
## - Adds [AnimationPlayer] and [CollisionPolygon2D] nodes if necessary.
func _update_character(scene_path: String) -> bool:
var popochiu_character: PopochiuCharacter = (load(scene_path) as PackedScene).instantiate()
var was_scene_updated := false
# ---- Check if updating the voices [Dictionary] is needed -------------------------------------
if not popochiu_character.voices.is_empty() and popochiu_character.voices[0].has("cue"):
was_scene_updated = true
var voices: Array = PopochiuResources.get_data_value("audio", "vo_cues", [])
popochiu_character.voices = popochiu_character.voices.map(_map_voices.bind(voices))
# ---- Update the texture_filter if needed -----------------------------------------------------
if PopochiuMigrationHelper.is_pixel_art_game():
was_scene_updated = true
popochiu_character.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
if not popochiu_character.has_node("AnimationPlayer"):
# ---- Add an [AnimationPlayer] node if needed ---------------------------------------------
was_scene_updated = true
var animation_player := AnimationPlayer.new()
animation_player.name = "AnimationPlayer"
popochiu_character.add_child(animation_player)
animation_player.owner = popochiu_character
else:
# ---- Or remove the texture track if it exists (prior Beta 1)------------------------------
var animation_player: AnimationPlayer = popochiu_character.get_node("AnimationPlayer")
for anim_name: String in animation_player.get_animation_list():
var animation: Animation = animation_player.get_animation(anim_name)
var texture_path: String = "%s:%s" % [
popochiu_character.get_path_to(popochiu_character.get_node("Sprite2D")),
"texture"
]
var texture_track: int = animation.find_track(texture_path, Animation.TYPE_VALUE)
if texture_track > -1:
animation.remove_track(texture_track)
was_scene_updated = true
if was_scene_updated and PopochiuEditorHelper.pack_scene(popochiu_character, scene_path) != OK:
PopochiuUtils.print_error("Couldn't update [b]%s[/b]." % popochiu_character.script_name)
return was_scene_updated
## Maps the data [param emotion_dic] to a new [Dictionary] with the new format defined for
## [member PopochiuCharacter.voices]. The [param voices] array is used to get the path to the
## [PopochiuAudioCue] file that should be used in each voice variation.
func _map_voices(emotion_dic: Dictionary, voices: Array) -> Dictionary:
var arr: Array[AudioCueSound] = []
var new_emotion_dic := {
emotion = emotion_dic.emotion,
variations = arr
}
for num: int in emotion_dic.variations:
var cue_name := "%s_%s" % [emotion_dic.cue, str(num + 1).pad_zeros(2)]
var cue_path: String = voices.filter(
func (cue_path: String) -> bool:
return cue_name in cue_path
)[0]
var popochiu_audio_cue: AudioCueSound = load(cue_path)
new_emotion_dic.variations.append(popochiu_audio_cue)
return new_emotion_dic
## Replace calls to old methods ignoring the [code]res://game/gui/[/code] folder:
## - [code]R.get_point[/code] by [code]R.get_marker[/code].
## - [code]G.display[/code] to [code]G.show_system_text[/code].
## - Methods with [code]_now[/code] suffix.
## - [code]super.on_click() | super.on_right_click() | super.on_item_used(item)[/code] by
## [code]E.command_fallback()[/code]
## - [code]super(item)[/code] to [code]E.command_fallback()[/code].
func _replace_deprecated_method_calls() -> Completion:
return Completion.DONE if PopochiuMigrationHelper.replace_in_scripts([
{from = "R.get_point", to = "R.get_marker"},
{from = "G.display", to = "G.show_system_text"},
{from = "disable_now()", to = "disable()"},
{from = "enable_now()", to = "enable()"},
{from = "change_frame_now(", to = "change_frame("},
{from = "super.on_click()", to = "E.command_fallback()"},
{from = "super.on_right_click()", to = "E.command_fallback()"},
{from = "super.on_item_used(item)", to = "E.command_fallback()"},
{from = "super(item)", to = "E.command_fallback()"},
# TODO: Include the following replacement. But for this one, the change should only be done
# in scripts which have the default method implementation.
#{from = "func _on_item_used(item", to = "func _on_item_used(_item"},
], ["gui"]) else Completion.IGNORED
#endregion

View file

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

View file

@ -0,0 +1,671 @@
@tool
class_name PopochiuMigration2
extends PopochiuMigration
## Migrates projects from Beta to Release.
const VERSION = 2
const DESCRIPTION = "Make changes from beta-x to 2.0.0 release"
const STEPS = [
"Update external scenes and assign scripts that didn't exist before. (pre [i]release[/i])",
"Add a [b]ScalingPolygon[/b] node to each [b]PopochiuCharacter[/b]. (pre [i]beta 1[/i])",
"Move popochiu_settings.tres to ProjectSettings. (pre [i]beta 3[/i])",
#"Update the DialogMenu GUI component. (pre [i]beta 3[/i])",
#"(Optional) Update SettingsBar in 2-click Context-sensitive GUI template. (pre [i]beta 3[/i])",
"Remove [b]BaselineHelper[/b] and [b]WalkToHelper[/b] nodes in [b]PopochiuClickable[/b]s." \
+ " Also remove [b]DialogPos[/b] node in [b]PopochiuCharacter[/b]s. (pre [i]release[/i])",
"Update uses of deprecated properties and methods. (pre [i]release[/i])",
"Update rooms sizes",
]
const RESET_CHILDREN_OWNER = "reset_children_owner"
const GAME_INVENTORY_BAR_PATH =\
"res://game/gui/components/inventory_bar/inventory_bar.tscn"
const GAME_SETTINGS_BAR_PATH =\
"res://game/gui/components/settings_bar/settings_bar.tscn"
const GAME_DIALOG_MENU_PATH = "res://game/gui/components/dialog_menu/dialog_menu.tscn"
const GAME_DIALOG_MENU_OPTION_PATH =\
"res://game/gui/components/dialog_menu/dialog_menu_option/"
const ADDON_DIALOG_MENU_PATH =\
"res://addons/popochiu/engine/objects/gui/components/dialog_menu/dialog_menu.tscn"
const TextSpeedOption = preload(
PopochiuResources.GUI_ADDON_FOLDER + "components/settings_bar/resources/text_speed_option.gd"
)
var _gui_templates_helper := preload(
"res://addons/popochiu/editor/helpers/popochiu_gui_templates_helper.gd"
)
#region Virtual ####################################################################################
## This is code specific for this migration. This should return [code]true[/code] if the migration
## is successful. This is called from [method do_migration] which checks to make sure the migration
## should be done before calling this.
func _do_migration() -> bool:
return await PopochiuMigrationHelper.execute_migration_steps(
self,
[
_update_objects_in_rooms,
_add_scaling_polygon_to_characters,
_move_settings_to_project_settings,
#_update_dialog_menu,
#_update_simple_click_settings_bar,
_remove_helper_nodes,
_replace_deprecated,
_update_rooms_sizes,
]
)
#endregion
#region Private ####################################################################################
## Update external prop scenes and assign missing scripts for each prop, hotspot, region, and
## walkable area that didn't exist prior [i]beta 1[/i].
func _update_objects_in_rooms() -> Completion:
var any_room_updated := PopochiuUtils.any_exhaustive(
PopochiuEditorHelper.get_rooms(), _update_room
)
_reload_needed = any_room_updated
return Completion.DONE if any_room_updated else Completion.IGNORED
## Update the children of the different groups in [param popochiu_room] so the use instances of the
## new objects: [PopochiuProp], [PopochiuHotspot], [PopochiuRegion], and [PopochiuWalkableArea].
func _update_room(popochiu_room: PopochiuRoom) -> bool:
var room_objects_to_add := []
var room_objects_to_check := []
PopochiuUtils.any_exhaustive([
PopochiuPropFactory.new(),
PopochiuHotspotFactory.new(),
PopochiuRegionFactory.new(),
PopochiuWalkableAreaFactory.new(),
PopochiuMarkerFactory.new(),
], _create_new_room_objects.bind(popochiu_room, room_objects_to_add, room_objects_to_check))
for group: Dictionary in room_objects_to_add:
group.objects.all(
func (new_obj) -> bool:
# Set the owner of the new object and do the same for its children (those that
# were marked as PopochiuRoomObjFactory.CHILD_VISIBLE_IN_ROOM_META)
new_obj.owner = popochiu_room
for child: Node in new_obj.get_meta(RESET_CHILDREN_OWNER):
child.owner = popochiu_room
new_obj.remove_meta(RESET_CHILDREN_OWNER)
return true
)
if PopochiuEditorHelper.pack_scene(popochiu_room) != OK:
PopochiuUtils.print_error(
"Migration 2: Couldn't update [b]%s[/b] after adding new nodes." %
popochiu_room.script_name
)
var room_object_updated := false
for obj: Node2D in room_objects_to_check:
# Check if the node's scene has all the expected nodes based on its base scene
var added_nodes := _add_lacking_nodes(obj)
if added_nodes and not room_object_updated:
room_object_updated = added_nodes
if room_object_updated and PopochiuEditorHelper.pack_scene(popochiu_room) != OK:
PopochiuUtils.print_error(
"Migration 2: Couldn't update [b]%s[/b] after adding lacking nodes." %
popochiu_room.script_name
)
return !room_objects_to_add.is_empty() or room_object_updated
## Create a new scene of the [param factory] type. The scene will be placed in its corresponding
## folder inside the [param popochiu_room] folder. Created [Node]s will be stored in
## [param room_objects_to_add] so they are added to the room later.
func _create_new_room_objects(
factory: PopochiuRoomObjFactory,
popochiu_room: PopochiuRoom,
room_objects_to_add := [],
room_objects_to_check := []
) -> bool:
var created_objects := []
for obj in _get_room_objects(
popochiu_room.get_node(factory.get_group()),
[],
factory.get_type_method()
):
# Copy the points of the polygons that were previously a node visible in the Room tree, but
# now are only properties
if (
(obj is PopochiuProp or obj is PopochiuHotspot or obj is PopochiuRegion)
and (obj.has_node("InteractionPolygon") or obj.has_node("InteractionPolygon2"))
):
var interaction_polygon: CollisionPolygon2D = obj.get_node("InteractionPolygon")
if obj.has_node("InteractionPolygon2"):
interaction_polygon = obj.get_node("InteractionPolygon2")
if interaction_polygon.owner == popochiu_room:
# Store the polygon vectors into the new @export variable
obj.interaction_polygon = interaction_polygon.polygon
obj.interaction_polygon_position = interaction_polygon.position
# Delete the CollisionPolygon2D node that in previous versions was attached to the
# room
interaction_polygon.owner = null
interaction_polygon.free()
elif (
obj is PopochiuWalkableArea
and (obj.has_node("Perimeter") or obj.has_node("Perimeter2"))
):
var perimeter: NavigationRegion2D = obj.get_node("Perimeter")
if obj.has_node("Perimeter2"):
perimeter = obj.get_node("Perimeter2")
if perimeter.owner == popochiu_room:
# Store the navigation polygon vectors into the new @export variable
obj.map_navigation_polygon(perimeter)
# Delete the NavigationRegion2D node that in previous versions was attached to the
# room
perimeter.owner = null
perimeter.free()
# If the object already has its own scene and a script that is not inside Popochiu's folder,
# then just check if there are lacking nodes inside its scene
if (
not obj.scene_file_path.is_empty()
and not "addons" in obj.scene_file_path
and not "addons" in obj.get_script().resource_path
):
room_objects_to_check.append(obj)
continue
# Create the new scene (and script if needed) of the [obj]
var obj_factory: PopochiuRoomObjFactory = factory.get_new_instance()
if obj_factory.create_from(obj, popochiu_room) != ResultCodes.SUCCESS:
continue
# Map the properties of the [obj] to its new instance
created_objects.append(_create_new_room_obj(obj_factory, obj, popochiu_room))
if created_objects.is_empty():
return false
room_objects_to_add.append({
factory = factory,
objects = created_objects
})
return true
## Recursively search for nodes of a specific type in the [param parent] and its children. The nodes
## found are added to the [param objects] array. The [param type_method] is used to determine if a
## node is the desired type.
func _get_room_objects(parent: Node, objects: Array, type_method: Callable) -> Array:
for child: Node in parent.get_children():
if type_method.call(child):
objects.append(child)
else:
# If the child is a Node containing other nodes, go deeper in the tree looking for room
# object nodes
_get_room_objects(child, objects, type_method)
return objects
## Maps the properties (and nodes if needed) of [param source] to a new instance of itself created
## from [param obj_factory]. This assures that objects coming from versions prior to [i]beta 1[/i]
## will have the corresponding structure of new Popochiu versions.
func _create_new_room_obj(
obj_factory: PopochiuRoomObjFactory, source: Node, room: PopochiuRoom
) -> Node:
var new_obj: Node = (ResourceLoader.load(
obj_factory.get_scene_path()
) as PackedScene).instantiate(PackedScene.GEN_EDIT_STATE_INSTANCE)
new_obj.set_meta(RESET_CHILDREN_OWNER, [])
source.name += "_"
source.get_parent().add_child(new_obj)
source.get_parent().move_child(new_obj, source.get_index())
# Check if the original object has a script attached (different from the default one)
if (
source.get_script()
and not "addons" in source.get_script().resource_path
and source.get_script().resource_path != new_obj.get_script().resource_path
):
# Change the default script by the one attached to the original object
new_obj.set_script(load(source.get_script().resource_path))
# Copy its extra properties (those declared as vars in the script) to the new instance
PopochiuResources.copy_popochiu_object_properties(
new_obj, source, PopochiuResources[
"%s_IGNORE" % obj_factory.get_group().to_snake_case().to_upper()
]
)
new_obj.position = source.position
new_obj.scale = source.scale
new_obj.z_index = source.z_index
if new_obj is PopochiuProp or new_obj is PopochiuHotspot:
new_obj.baseline = source.baseline
new_obj.walk_to_point = source.walk_to_point
if new_obj is PopochiuProp:
new_obj.texture = source.texture
new_obj.frames = source.frames
new_obj.v_frames = source.v_frames
new_obj.link_to_item = source.link_to_item
new_obj.interaction_polygon = source.interaction_polygon
new_obj.interaction_polygon_position = source.interaction_polygon_position
if obj_factory.get_snake_name() in ["bg", "background"]:
new_obj.z_index = -1
if new_obj is PopochiuRegion:
new_obj.interaction_polygon = source.interaction_polygon
new_obj.interaction_polygon_position = source.interaction_polygon_position
if new_obj is PopochiuWalkableArea:
new_obj.interaction_polygon = source.interaction_polygon
new_obj.interaction_polygon_position = source.interaction_polygon_position
# Remove the old [source] node from the room
source.free()
return new_obj
## Checks the [code].tscn[/code] file of [param source] for lacking nodes based on its type. If
## there are any, then it will add them so the structure of the scene matches the one of the object
## it inherits from.
func _add_lacking_nodes(source: Node) -> bool:
var obj_scene: Node2D = ResourceLoader.load(source.scene_file_path).instantiate()
var was_updated := false
if (
PopochiuEditorHelper.is_prop(obj_scene)
or PopochiuEditorHelper.is_hotspot(obj_scene)
or PopochiuEditorHelper.is_region(obj_scene)
) and not obj_scene.has_node("InteractionPolygon"):
var interaction_polygon := CollisionPolygon2D.new()
interaction_polygon.name = "InteractionPolygon"
interaction_polygon.polygon = PackedVector2Array([
Vector2(-10, -10), Vector2(10, -10), Vector2(10, 10), Vector2(-10, 10)
])
obj_scene.add_child(interaction_polygon)
obj_scene.move_child(interaction_polygon, 0)
interaction_polygon.owner = obj_scene
was_updated = true
elif PopochiuEditorHelper.is_walkable_area(obj_scene) and not obj_scene.has_node("Perimeter"):
var perimeter := NavigationRegion2D.new()
perimeter.name = "Perimeter"
var polygon := NavigationPolygon.new()
polygon.agent_radius = 0.0
perimeter.navigation_polygon = polygon
obj_scene.add_child(perimeter)
perimeter.owner = obj_scene
obj_scene.interaction_polygon = source.interaction_polygon
obj_scene.clear_and_bake(perimeter.navigation_polygon)
was_updated = true
if PopochiuEditorHelper.is_prop(obj_scene) and not obj_scene.has_node("AnimationPlayer"):
var animation_player := AnimationPlayer.new()
obj_scene.add_child(animation_player)
animation_player.owner = obj_scene
was_updated = true
if was_updated:
PopochiuEditorHelper.pack_scene(obj_scene)
return was_updated
## Add a [CollisionPolygon2D] node named "ScalingPolygon" to each [PopochiuCharacter] that doesn't
## have it. Returns [code]Completion.DONE[/code] if any character is updated,
## [code]Completion.IGNORED[/code] otherwise.
func _add_scaling_polygon_to_characters() -> Completion:
# Get the characters' .tscn files
var file_paths := PopochiuMigrationHelper.get_absolute_file_paths_for_file_extensions(
PopochiuResources.CHARACTERS_PATH,
["tscn"]
)
var any_character_updated := PopochiuUtils.any_exhaustive(file_paths, _add_scaling_polygon_to)
return Completion.DONE if any_character_updated else Completion.IGNORED
## Loads the [PopochiuCharacter] in [param scene_path] and add a [CollisionPolygon2D] node if it
## doesn't has a [code]ScalingPolygon[/code] child.
func _add_scaling_polygon_to(scene_path: String) -> bool:
var popochiu_character: PopochiuCharacter = (load(scene_path) as PackedScene).instantiate()
var was_scene_updated := false
# ---- Add the ScalingPolygon node if needed ---------------------------------------------------
if not popochiu_character.has_node("ScalingPolygon"):
was_scene_updated = true
var scaling_polygon := CollisionPolygon2D.new()
scaling_polygon.name = "ScalingPolygon"
scaling_polygon.polygon = PackedVector2Array([
Vector2(-5, -5), Vector2(5, -5), Vector2(5, 5), Vector2(-5, 5)
])
popochiu_character.add_child(scaling_polygon)
popochiu_character.move_child(scaling_polygon, 1)
scaling_polygon.owner = popochiu_character
if was_scene_updated and PopochiuEditorHelper.pack_scene(popochiu_character) != OK:
PopochiuUtils.print_error(
"Couldn't update [b]%s[/b] with new voices array." % popochiu_character.script_name
)
return was_scene_updated
## Move the values from the old [code]popochiu_settings.tres[/code] file to the new
## [code]Project Settings > Popochiu[/code] section. Returns [constant Completion.DONE] if the values
## are moved, [constant Completion.IGNORED] if the file doesn't exist, [constant Completion.FAILED]
## otherwise.
func _move_settings_to_project_settings() -> Completion:
var old_settings_file := PopochiuMigrationHelper.old_settings_file
if not FileAccess.file_exists(old_settings_file):
return Completion.IGNORED
# Move custom defined values in the old [popochiu_settings.tres] file to Project Settings
var old_settings: PopochiuSettings = load(old_settings_file)
var settings_map := {
# ---- GUI ---------------------------------------------------------------------------------
"SCALE_GUI": "",
"FADE_COLOR": "",
"SKIP_CUTSCENE_TIME": "",
# ---- Dialogs -----------------------------------------------------------------------------
"TEXT_SPEED": old_settings.text_speeds[old_settings.default_text_speed],
"AUTO_CONTINUE_TEXT": "",
"USE_TRANSLATIONS": "",
# ---- Inventory ---------------------------------------------------------------------------
"INVENTORY_LIMIT": "",
# ---- Pixel game --------------------------------------------------------------------------
"PIXEL_ART_TEXTURES": "is_pixel_art_game",
"PIXEL_PERFECT": "is_pixel_perfect",
}
for key: String in settings_map:
PopochiuConfig.set_project_setting(
key,
old_settings[key.to_lower()] if key.is_empty() else settings_map[key]
)
for item_name: StringName in old_settings.items_on_start:
var items_on_start := PopochiuConfig.get_inventory_items_on_start()
items_on_start.append(str(item_name))
PopochiuConfig.set_inventory_items_on_start(items_on_start)
# Move custom defined values in the old [popochiu_settings.tres] to their corresponding GUI
# components
if FileAccess.file_exists(GAME_INVENTORY_BAR_PATH):
var inventory_bar: Control = load(GAME_INVENTORY_BAR_PATH).instantiate()
inventory_bar.always_visible = old_settings.inventory_always_visible
PopochiuEditorHelper.pack_scene(inventory_bar)
if FileAccess.file_exists(GAME_SETTINGS_BAR_PATH):
var settings_bar: PanelContainer = load(GAME_SETTINGS_BAR_PATH).instantiate()
settings_bar.always_visible = old_settings.toolbar_always_visible
PopochiuEditorHelper.pack_scene(settings_bar)
# Remove the old [popochiu_settings.tres]
if DirAccess.remove_absolute(old_settings_file) != OK:
PopochiuUtils.print_error("Couldn't delete [code]%s[/code]." % old_settings_file)
return Completion.FAILED
return Completion.DONE
## Update the [code]DialogMenu[/code] GUI component to use the new [code]DialogMenuOption[/code].
## Returns [constant Completion.DONE] if the component is updated, [constant Completion.IGNORED] if
## the game's GUI is not using the [code]DialogMenu[/code] component or is already using the new
## version, [constant Completion.FAILED] otherwise.
func _update_dialog_menu() -> Completion:
if (
not FileAccess.file_exists(GAME_DIALOG_MENU_PATH)
or DirAccess.dir_exists_absolute(GAME_DIALOG_MENU_OPTION_PATH)
):
# The game's GUI is not using the DialogMenu GUI component or is already using its beta-3
# version
return Completion.IGNORED
# Copy the new [PopochiuDialogMenuOption] component to the game's GUI components folder
await _gui_templates_helper.copy_components(ADDON_DIALOG_MENU_PATH)
# Store the scene of the new [PopochiuDialogMenuOption] in the game's graphic interface folder
var game_dialog_menu_option: PackedScene = load(PopochiuResources.GUI_GAME_FOLDER.path_join(
"components/dialog_menu/dialog_menu_option/dialog_menu_option.tscn"
))
# Assign the new [PopochiuDialogMenuOption] to the game's GUI dialog menu component and delete
# any option inside its DialogOptionsContainer child
var game_dialog_menu: PopochiuDialogMenu = load(GAME_DIALOG_MENU_PATH).instantiate()
game_dialog_menu.option_scene = game_dialog_menu_option
for opt in game_dialog_menu.get_node("ScrollContainer/DialogOptionsContainer").get_children():
opt.owner = null
opt.free()
var done := PopochiuEditorHelper.pack_scene(game_dialog_menu)
if done != OK:
PopochiuUtils.print_error(
"Couldn't update PopochiuDialogMenuOption reference in PopochiuDialogMenu"
)
return Completion.FAILED
# Update the dependency to [PopochiuDialogMenuOption] in the game's graphic interface scene
var game_gui: PopochiuGraphicInterface = load(PopochiuResources.GUI_GAME_SCENE).instantiate()
game_gui.get_node("DialogMenu").option_scene = game_dialog_menu_option
done = PopochiuEditorHelper.pack_scene(game_gui)
if done != OK:
PopochiuUtils.print_error(
"Couldn't update PopochiuDialogMenuOption reference in PopochiuGraphicInterface"
)
return Completion.FAILED
# Delete the old [dialog_menu_option.tscn] file
done = DirAccess.remove_absolute(
"res://game/gui/components/dialog_menu/dialog_menu_option.tscn"
)
if done != OK:
PopochiuUtils.print_error(
"Couldn't update PopochiuDialogMenuOption reference in PopochiuDialogMenu"
)
return Completion.FAILED
return Completion.DONE
## Update the [code]SettingsBar[/code] GUI component in the 2-click Context-sensitive GUI template.
## Returns [constant Completion.DONE] if the component is updated, [constant Completion.IGNORED] if
## the game's GUI does not use the [code]SettingsBar[/code] GUI component or is already using the
## new version, [constant Completion.FAILED] otherwise.
func _update_simple_click_settings_bar() -> Completion:
if not FileAccess.file_exists(GAME_SETTINGS_BAR_PATH):
# The game's GUI does not use the SettingsBar GUI component
return Completion.IGNORED
var game_settings_bar: PanelContainer = load(GAME_SETTINGS_BAR_PATH).instantiate()
var dialog_speed_button: TextureButton = game_settings_bar.get_node("Box/BtnDialogSpeed")
if not dialog_speed_button.speed_options.is_empty():
# The component is up to date with the beta-3 version
return Completion.IGNORED
var addons_settings_bar: PanelContainer = load(PopochiuResources.GUI_TEMPLATES_FOLDER.path_join(
"simple_click/components/settings_bar/settings_bar.tscn"
)).instantiate()
# Store the speed options defined in the original component
var speed_options := []
for opt: TextSpeedOption in addons_settings_bar.get_node("Box/BtnDialogSpeed").speed_options:
var option := TextSpeedOption.new()
option.resource_name = opt.resource_name
option.speed = opt.speed
option.description = opt.description
option.icon = load(opt.icon.resource_path.replace(
PopochiuResources.GUI_TEMPLATES_FOLDER.path_join(
"simple_click/components/settings_bar/sprites/"
),
"res://game/gui/components/settings_bar/sprites/"
))
speed_options.append(option)
# Assign the options to the component in the game's graphic interface component and save the
# SettingsBat scene
dialog_speed_button.speed_options = speed_options
var scene_updated := PopochiuEditorHelper.pack_scene(game_settings_bar)
return Completion.DONE if scene_updated == OK else Completion.FAILED
## Remove visual helper nodes in all [PopochiuProp]s, [PopochiuHotspot]s, and [PopochiuCharacter]s.
func _remove_helper_nodes() -> Completion:
var characters_paths := PopochiuMigrationHelper.get_absolute_file_paths_for_file_extensions(
PopochiuResources.CHARACTERS_PATH,
["tscn"]
)
var props_and_hotspots_paths :=\
PopochiuMigrationHelper.get_absolute_file_paths_for_file_extensions(
PopochiuResources.ROOMS_PATH,
["tscn"],
["markers", "regions", "walkable_areas"]
).filter(
func (file_path: String) -> bool:
return not "room_" in file_path
)
var popochiu_clickables := characters_paths + props_and_hotspots_paths
var any_updated := PopochiuUtils.any_exhaustive(popochiu_clickables, _remove_helper_nodes_in)
return Completion.DONE if any_updated else Completion.IGNORED
## Remove the [code]BaselineHelper[/code] and [code]WalkToHelper[/code] nodes in [param scene_path].
## Also remove the [code]DialogPos[/code] node if it is a [PopochiuCharacter]. Returns
## [constant Completion.DONE] if any node is removed, [constant Completion.IGNORED] otherwise.
func _remove_helper_nodes_in(scene_path: String) -> bool:
# Load the scene ignoring cache so changes made in previous steps are taken into account
var popochiu_clickable: PopochiuClickable = (
ResourceLoader.load(scene_path, "", ResourceLoader.CACHE_MODE_IGNORE) as PackedScene
).instantiate(PackedScene.GEN_EDIT_STATE_MAIN)
var was_scene_updated := false
# ---- Remove the BaselineHelper and WalkToHelper nodes ----------------------------------------
if _remove_node(popochiu_clickable, "BaselineHelper"):
was_scene_updated = true
if _remove_node(popochiu_clickable, "WalkToHelper"):
was_scene_updated = true
# ---- Remove the DialogPos node ---------------------------------------------------------------
# TODO: Uncomment this once PR #241 is merged
if popochiu_clickable is PopochiuCharacter and popochiu_clickable.has_node("DialogPos"):
popochiu_clickable.dialog_pos = popochiu_clickable.get_node("DialogPos").position
_remove_node(popochiu_clickable, "DialogPos")
was_scene_updated = true
if was_scene_updated and PopochiuEditorHelper.pack_scene(popochiu_clickable, scene_path) != OK:
PopochiuUtils.print_error(
"Couldn't remove helper nodes in [b]%s[/b]." % popochiu_clickable.script_name
)
return was_scene_updated
## Remove the node in [param parent] with the path [param node_path]. Returns [code]true[/code] if
## the node is removed, [code]false[/code] otherwise.
func _remove_node(parent: Node, node_path: NodePath) -> bool:
if parent.has_node(node_path):
var child: Node = parent.get_node(node_path)
child.owner = null
child.free()
return true
return false
## Replace calls to deprecated properties and methods:
## - [code]E.current_room[/code] by [code]R.current[/code].
## - [code]E.goto_room()[/code] by [code]R.goto_room()[/code].
## - [code]E.queue_camera_offset()[/code] by [code]E.camera.queue_change_offset()[/code].
## - [code]E.camera_offset()[/code] by [code]E.camera.change_offset()[/code].
## - [code]E.queue_camera_shake()[/code] by [code]E.camera.queue_shake()[/code].
## - [code]E.camera_shake()[/code] by [code]E.camera.shake()[/code].
## - [code]E.queue_camera_shake_bg()[/code] by [code]E.camera.queue_shake_bg()[/code].
## - [code]E.camera_shake_bg()[/code] by [code]E.camera.shake_bg()[/code].
## - [code]E.queue_camera_zoom()[/code] by [code]E.camera.queue_change_zoom()[/code].
## - [code]E.camera_zoom()[/code] by [code]E.camera.change_zoom()[/code].
## - [code]E.stop_camera_shake()[/code] by [code]E.camera.stop_shake()[/code].
## - [code]return super.get_runtime_room()[/code] by [code]return get_runtime_room()[/code].
## - [code]return super.get_runtime_character()[/code] by [code]return get_runtime_character()[/code].
## - [code]return super.get_item_instance()[/code] by [code]return get_item_instance()[/code].
## - [code]return E.get_dialog()[/code] by [code]return get_instance()[/code].
## Returns [constant Completion.DONE] if any replacement is done, [constant Completion.IGNORED]
## otherwise.
func _replace_deprecated() -> Completion:
return Completion.DONE if PopochiuMigrationHelper.replace_in_scripts([
{from = "E.current_room", to = "R.current"},
{from = "E.goto_room(", to = "R.goto_room("},
{from = "E.queue_camera_offset(", to = "E.camera.queue_change_offset("},
{from = "E.camera_offset(", to = "E.camera.change_offset("},
{from = "E.queue_camera_shake(", to = "E.camera.queue_shake("},
{from = "E.camera_shake(", to = "E.camera.shake("},
{from = "E.queue_camera_shake_bg(", to = "E.camera.queue_shake_bg("},
{from = "E.camera_shake_bg(", to = "E.camera.shake_bg("},
{from = "E.queue_camera_zoom(", to = "E.camera.queue_change_zoom("},
{from = "E.camera_zoom(", to = "E.camera.change_zoom("},
{from = "E.stop_camera_shake()", to = "E.camera.stop_shake()"},
# autoloads
{from = "return super.get_runtime_room(", to = "return get_runtime_room("},
{from = "return super.get_runtime_character(", to = "return get_runtime_character("},
{from = "return super.get_item_instance(", to = "return get_item_instance("},
{from = "return E.get_dialog(", to = "return get_instance("},
]) else Completion.IGNORED
## Update camera limit calculations for each room to utilize the new [member PopochiuRoom.width]
## and [member PopochiuRoom.height] properties.
func _update_rooms_sizes() -> Completion:
var any_room_updated := PopochiuUtils.any_exhaustive(
PopochiuEditorHelper.get_rooms(), _update_room_size
)
_reload_needed = any_room_updated
return Completion.DONE if any_room_updated else Completion.IGNORED
## Updates the values of [member PopochiuRoom.width] and [member PopochiuRoom.height] in
## [param popochiu_room] based on the values of the deprecated camera limits properties.
func _update_room_size(popochiu_room: PopochiuRoom) -> bool:
var viewport_width := ProjectSettings.get_setting(PopochiuResources.DISPLAY_WIDTH)
var viewport_height := ProjectSettings.get_setting(PopochiuResources.DISPLAY_HEIGHT)
# Calculate the width based on the camera limits
var left := 0.0 if is_inf(popochiu_room.limit_left) else popochiu_room.limit_left
var right := 0.0 if is_inf(popochiu_room.limit_right) else popochiu_room.limit_right
var width: int = viewport_width - int(left)
width += int(right) - viewport_width
popochiu_room.width = maxi(width, viewport_width)
# Calculate the height based on the camera limits
var top := 0.0 if is_inf(popochiu_room.limit_top) else popochiu_room.limit_top
var bottom := 0.0 if is_inf(popochiu_room.limit_bottom) else popochiu_room.limit_bottom
var height: int = viewport_height - int(top)
height += int(bottom) - viewport_height
popochiu_room.height = maxi(height, viewport_height)
if PopochiuEditorHelper.pack_scene(popochiu_room) != OK:
PopochiuUtils.print_error(
"Migration 2: Couldn't update the [code]width[/code] and [code]height[/code] of" +\
" [b]%s[/b] room." % popochiu_room.script_name
)
return false
return true
#endregion

View file

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

View file

@ -0,0 +1,71 @@
@tool
class_name PopochiuMigration3
extends PopochiuMigration
const VERSION = 3
const DESCRIPTION = "Update clickables to set look_at_point property"
const STEPS = [
"Update all clickables in rooms",
]
const LOOK_AT_POINT_OFFSET = Vector2(-10, -10)
#region Virtual ####################################################################################
## This is code specific for this migration. This should return [code]true[/code] if the migration
## is successful. This is called from [method do_migration] which checks to make sure the migration
## should be done before calling this.
func _do_migration() -> bool:
return await PopochiuMigrationHelper.execute_migration_steps(
self,
[
_update_objects_in_rooms
]
)
#endregion
#region Private ####################################################################################
## Update all rooms clickables to set a default value for the look_at_point property.
func _update_objects_in_rooms() -> Completion:
var any_room_updated := PopochiuUtils.any_exhaustive(
PopochiuEditorHelper.get_rooms(), _update_room
)
_reload_needed = any_room_updated
return Completion.DONE if any_room_updated else Completion.IGNORED
func _update_popochiu_clickable(popochiu_room: PopochiuRoom, clickable_type: String) -> bool:
if not popochiu_room.has_node(clickable_type):
return false
var changed = false
for obj: Node in popochiu_room.find_child(clickable_type).get_children():
if PopochiuEditorHelper.is_popochiu_clickable(obj):
obj.look_at_point = obj.walk_to_point + LOOK_AT_POINT_OFFSET
changed = true
PopochiuUtils.print_normal(
"Migration %d: %s: updated %s look_at_point." %
[VERSION, clickable_type, obj.script_name]
)
return changed
func _update_room(popochiu_room: PopochiuRoom) -> bool:
var room_updated = _update_popochiu_clickable(popochiu_room, "Characters")
room_updated = _update_popochiu_clickable(popochiu_room, "Props") or room_updated
room_updated = _update_popochiu_clickable(popochiu_room, "Hotspots") or room_updated
if room_updated and PopochiuEditorHelper.pack_scene(popochiu_room) != OK:
PopochiuUtils.print_error(
"Migration %d: Couldn't update [b]%s[/b] after updating clickables." %
[VERSION, popochiu_room.script_name]
)
return room_updated
#endregion

View file

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

View file

@ -0,0 +1,99 @@
@tool
class_name MigrationsManager
extends Node
static var migrations_to_execute := []
static var migrations_panel: PopochiuEditorHelper.MigrationsPanel
static var migrations_popup: AcceptDialog
#region Public #####################################################################################
## If there are migrations available with a higher number then the last one
## applied to the project, apply them all in order.
static func do_migrations() -> void:
if PopochiuMigrationHelper.is_empty_project():
PopochiuMigrationHelper.update_user_migration_version(
PopochiuMigrationHelper.get_migrations_count()
)
await PopochiuEditorHelper.frame_processed()
return
if not PopochiuMigrationHelper.is_migration_needed():
await PopochiuEditorHelper.frame_processed()
return
migrations_panel = PopochiuEditorHelper.MIGRATIONS_PANEL_SCENE.instantiate()
migrations_popup = await PopochiuEditorHelper.show_migrations(migrations_panel)
migrations_popup.hide()
# Get the list of migrations to apply
for idx: int in PopochiuMigrationHelper.get_migrations_count():
# Migration classes are located at "res://addons/popochiu/migration/migrations/*.gd"
var migration: PopochiuMigration = load(
PopochiuMigrationHelper.MIGRATIONS_PATH.path_join("popochiu_migration_%d.gd" % (idx + 1))
).new()
if not migration.is_migration_needed():
continue
migrations_to_execute.append(migration)
await migrations_panel.add_migration(migration)
migration.step_started.connect(migrations_panel.start_step)
migration.step_completed.connect(migrations_panel.update_steps)
if migrations_to_execute.is_empty():
migrations_popup.free()
return
migrations_popup.get_ok_button().text = "Run migrations"
migrations_popup.confirmed.connect(_run_migrations)
migrations_popup.canceled.connect(
func () -> void:
PopochiuEditorHelper.signal_bus.migrations_done.emit()
migrations_popup.queue_free()
)
migrations_popup.show()
await PopochiuEditorHelper.signal_bus.migrations_done
#endregion
#region Private ####################################################################################
static func _run_migrations() -> void:
migrations_popup.confirmed.disconnect(_run_migrations)
migrations_popup.get_ok_button().text = "OK"
migrations_popup.get_ok_button().disabled = true
# Make the popup visible again so devs can see the progress on the migrations' steps
migrations_popup.popup()
PopochiuUtils.print_normal("Processing Popochiu Migrations")
var should_reload := false
for migration: PopochiuMigration in migrations_to_execute:
var user_migration_version := PopochiuMigrationHelper.get_user_migration_version()
# adding 1 to user migration version to match with the migration that needs to be done
var migration_version := user_migration_version + 1
if not await PopochiuMigration.run_migration(migration, migration_version):
PopochiuUtils.print_error(
"Something went wrong while executing Migration %d" % migration_version
)
break
should_reload = should_reload or migration.is_reload_required()
await migrations_popup.get_tree().create_timer(1.0).timeout
if should_reload:
migrations_panel.reload_label.show()
migrations_popup.get_ok_button().disabled = false
migrations_popup.confirmed.connect(
func () -> void:
if should_reload:
EditorInterface.restart_editor(false)
else:
PopochiuEditorHelper.signal_bus.migrations_done.emit()
migrations_popup.queue_free()
)
#endregion

View file

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