@ -9,7 +9,9 @@ extends Node
signal apply_complete
signal apply_save ## Apply the loaded data to the tree. Note: This should happen AFTER load_save()
signal create_auto_save
signal autosave_complete ## Autosave has completed
signal autosave_start ## Autosave has started
signal create_autosave
signal create_save ( save_name : String )
signal delete_save ( filename : String )
signal delete_error ( error_message : String )
@ -18,16 +20,19 @@ signal load_error(error_message: String)
signal load_save ( filename : String ) ## Loads the save into memory. Does NOT apply the load as this allows for other actions (such as resetting the levle/world).[br]Don't forget to run `apply_save` after loading
signal save_complete
signal save_error ( error_message : String )
signal start_autosave
signal stop_autosave
signal quick_save
signal quick_load
signal toggle_save_icon_generation ( toggled : bool ) ## Enable/Disable the generation of a screenshot during save for the save icon.
var _autosave_timer : Timer = Timer . new ( )
var _enable_save_icon_generation : bool = true
var _loaded_save_resource : SaveGameDataResource = SaveGameDataResource . new ( )
var _save_game_settings : SaveGameSettings ## Contains the save paths, filename prepends, and other save settings
var _save_icon_size : Vector2i = Vector2i ( 896 , 504 ) ## If Vector2.ZERO, uses the user's resolution
var _save_level_data_component : SaveLevelDataComponent ## Contains the save paths and filenames
func _ready ( ) - > void :
@ -37,29 +42,39 @@ func _ready() -> void:
load_save . connect ( _on_load_game_save )
quick_load . connect ( _on_quick_load )
quick_save . connect ( _on_quick_save )
start_autosave . connect ( _on_start_autosave )
stop_autosave . connect ( _on_stop_autosave )
toggle_save_icon_generation . connect ( _on_toggle_save_icon_generation )
_autosave_timer . name = " AutosaveTimer "
_autosave_timer . one_shot = false
_autosave_timer . timeout . connect ( _on_autosave_timer_timeout )
add_child ( _autosave_timer )
func _unhandled_input ( event : InputEvent ) - > void :
if event . is_action_pressed ( " quick_save " ) :
_on_quick_save ( )
quick_save . emit ( )
if event . is_action_pressed ( " quick_load " ) :
_on_quick_load ( )
quick_load . emit ( )
func list_saves ( include_quick_saves : bool = true , include_auto_ saves : bool = true ) - > Array [ SaveFileDetailsResource ] :
func list_saves ( include_quick_saves : bool = true , include_autosaves : bool = true ) - > Array [ SaveFileDetailsResource ] :
var save_files : Array [ SaveFileDetailsResource ] = [ ]
if not _load_save_level_data_component ( ) :
if not _load_save_settings ( ) :
return save_files
var save_game_data_path : String = _save_level_data_component . settings . save_game_data_path
var save_game_data_path : String = _save_game_ settings . save_game_data_path
if ! DirAccess . dir_exists_absolute ( save_game_data_path ) :
return save_files
for filename : String in ResourceLoader . list_directory ( save_game_data_path ) :
# TODO: Rework so the settings determine the file_name using prepends
if filename . begins_with ( " quicksave_ " ) and not include_quick_saves : continue
elif filename . begins_with ( " autosave_ " ) and not include_auto_saves : continue
elif ! filename . begins_with ( " save_ " ) and ! filename . begins_with ( " quicksave_ " ) and ! filename . begins_with ( " autosave_ " ) : continue
if filename . begins_with ( _save_game_settings . quicksave_file_name_prepend ) and not include_quick_saves :
continue
elif filename . begins_with ( _save_game_settings . autosave_file_name_prepend ) and not include_autosaves :
continue
elif ! filename . begins_with ( _save_game_settings . save_file_name_prepend ) and ! filename . begins_with ( _save_game_settings . quicksave_file_name_prepend ) and ! filename . begins_with ( _save_game_settings . autosave_file_name_prepend ) :
continue
var _save_path : String = save_game_data_path + filename
var _save_icon : String = filename . replace ( " .tres " , " .png " )
@ -70,9 +85,9 @@ func list_saves(include_quick_saves: bool = true, include_auto_saves: bool = tru
# Should work out a better method for getting the save name (filename != save name)
var _save_file : SaveGameDataResource = ResourceLoader . load ( _save_path )
_save_resource . save_name = _save_file . save_name
_save_resource . save_date = _save_file . save_date
_save_resource . filename = filename
_save_resource . date_created = Time . get_datetime_string_from_unix_time ( FileAccess . get_modified_time ( _save_path ) )
## In 4.5, this can probably be replaced with FileAccess.get_size(_save_path)
## This should reduce the need for opening the file here
@ -87,9 +102,23 @@ func list_saves(include_quick_saves: bool = true, include_auto_saves: bool = tru
return save_files
func _create_autosave ( ) - > void :
if not _load_save_settings ( ) : return
_autosave_timer . stop ( )
autosave_start . emit ( )
_rotate_autosaves ( )
var autosave_filename : String = _save_game_settings . autosave_file_name_prepend + " 01.tres "
_save_game_as_resource ( " Auto Save " , autosave_filename )
autosave_complete . emit ( )
_autosave_timer . start ( _save_game_settings . autosave_duration )
## Sort the save files list by date created, descending
func _custom_save_file_sort ( a : SaveFileDetailsResource , b : SaveFileDetailsResource ) - > bool :
return a . date_created > b . date_created
return a . save_date > b . save_date
## Save the properties defined on the SaveDataComponents attached to various nodes (such as Block)
func _generate_save_game_resource ( ) - > SaveGameDataResource :
@ -111,9 +140,9 @@ func _generate_save_game_resource() -> SaveGameDataResource:
func _generate_save_icon_texture ( save_icon : String ) - > Texture2D :
var _icon_texture : Texture2D = ImageTexture . new ( )
if save_icon != null and ! FileAccess . file_exists ( save_icon ) :
_icon_texture = _save_level_data_component . default_save_icon_resource
_icon_texture = _save_game_settings . default_save_icon_resource
elif save_icon == null :
_icon_texture = _save_level_data_component . default_save_icon_resource
_icon_texture = _save_game_settings . default_save_icon_resource
else :
var _icon_image : Image = Image . new ( )
_icon_image . load ( save_icon )
@ -123,19 +152,10 @@ func _generate_save_icon_texture(save_icon: String) -> Texture2D:
return _icon_texture
## Find the SaveLevelDataComponent within the level which stores the save settings
func _load_save_level_data_component ( ) - > bool :
_save_level_data_component = get_tree ( ) . get_first_node_in_group ( " save_level_data_component " )
if _save_level_data_component == null :
push_error ( " Could not find SaveLevelDataComponent node in level " )
return false
return true
func _load_game_resource ( resource_filename : String ) - > void :
if not _load_save_level_data_component ( ) : return
if not _load_save_settings ( ) : return
var save_game_file_path : String = _save_level_data_component . settings . save_game_data_path + resource_filename
var save_game_file_path : String = _save_game_settings . save_game_data_path + resource_filename
if ! FileAccess . file_exists ( save_game_file_path ) :
load_error . emit ( " Failed to load save. File does not exist: %s " % save_game_file_path )
return
@ -147,16 +167,54 @@ func _load_game_resource(resource_filename: String) -> void:
load_complete . emit ( )
## Find the SaveLevelDataComponent within the tree which stores the save settings
func _load_save_settings ( ) - > bool :
var _save_level_data_component : SaveLevelDataComponent = get_tree ( ) . get_first_node_in_group ( " save_level_data_component " )
if _save_level_data_component == null :
push_error ( " Could not find SaveLevelDataComponent node in level " )
return false
_save_game_settings = _save_level_data_component . settings
return true
## Increment autosave numbers and if we've reach max number of autosaves, remove the oldest one
func _rotate_autosaves ( ) - > void :
var saves_dir = DirAccess . open ( _save_game_settings . save_game_data_path )
if saves_dir == null :
DirAccess . make_dir_absolute ( _save_game_settings . save_game_data_path )
var autosaves : Array [ String ] = [ ]
for filename in saves_dir . get_files ( ) :
if ! filename . begins_with ( _save_game_settings . autosave_file_name_prepend ) : continue
if ! filename . ends_with ( " .tres " ) : continue
autosaves . append ( filename )
if autosaves . size ( ) == _save_game_settings . max_autosaves : # Delete oldest save
DirAccess . remove_absolute ( _save_game_settings . save_game_data_path + autosaves . pop_back ( ) )
var filepath : String = _save_game_settings . save_game_data_path + _save_game_settings . autosave_file_name_prepend
for index in range ( autosaves . size ( ) , 0 , - 1 ) :
var old_save_path : String = " %s %02d .tres " % [ filepath , index ]
var old_screenshot_path : String = " %s %02d .png " % [ filepath , index ]
var new_save_path : String = " %s %02d .tres " % [ filepath , index + 1 ]
var new_screenshot_path : String = " %s %02d .png " % [ filepath , index + 1 ]
DirAccess . copy_absolute ( old_save_path , new_save_path )
if FileAccess . file_exists ( old_screenshot_path ) :
DirAccess . copy_absolute ( old_screenshot_path , new_screenshot_path )
func _save_game_as_resource ( save_name , resource_filename : String ) - > void :
if not _load_save_level_data_component ( ) : return
if not _load_save_settings ( ) : return
if ! DirAccess . dir_exists_absolute ( _save_level_data_component . settings . save_game_data_path ) :
DirAccess . make_dir_absolute ( _save_level_data_component . settings . save_game_data_path )
if ! DirAccess . dir_exists_absolute ( _save_game_ settings . save_game_data_path ) :
DirAccess . make_dir_absolute ( _save_game_ settings . save_game_data_path )
var save_game_file_path : String = _save_level_data_component . settings . save_game_data_path + resource_filename
var save_game_file_path : String = _save_game_ settings . save_game_data_path + resource_filename
var _save_resource : SaveGameDataResource = _generate_save_game_resource ( )
_save_resource . save_name = save_name
_save_resource . save_date = Time . get_datetime_string_from_system ( false , true )
var result : int = ResourceSaver . save ( _save_resource , save_game_file_path )
if result != OK :
@ -191,13 +249,16 @@ func _on_apply_save() -> void:
apply_complete . emit ( )
func _on_autosave_timer_timeout ( ) - > void :
_create_autosave ( )
## Delete both the save file and the related screenshot
func _on_delete_save ( filename : String ) - > void :
if filename . length ( ) < 1 :
delete_error . emit ( " Empty filename provided " )
return
var save_file_path : String = _save_level_data_component . settings . save_game_data_path + filename
var save_file_path : String = _save_game_ settings . save_game_data_path + filename
DirAccess . remove_absolute ( save_file_path )
DirAccess . remove_absolute ( save_file_path . replace ( " .tres " , " .png " ) ) # Delete icon
@ -205,24 +266,32 @@ func _on_load_game_save(resource_filename: String) -> void:
_load_game_resource ( resource_filename )
func _on_quick_load ( ) - > void :
if not _load_save_level_data_component ( ) : return
_load_game_resource ( _save_level_data_component . settings . quicksave_file_name )
if not _load_save_settings ( ) : return
_load_game_resource ( _save_game_settings . quicksave_file_name_prepend + " game_data.tres " )
func _on_quick_save ( ) - > void :
if not _load_save_level_data_component ( ) : return
if not _load_save_settings ( ) : return
_save_game_as_resource ( " Quick Save " , _save_level_data_component . settings . quicksave_file_name )
_save_game_as_resource ( " Quick Save " , _save_game_settings . quicksave_file_name_prepend + " game_data.tres " )
## Save the game, with a filename of `<save_prepend><current_date>.tres
func _on_save_game_as_resource ( save_name : String ) - > void :
if not _load_save_level_data_component ( ) : return
if not _load_save_settings ( ) : return
var current_date : String = Time . get_datetime_string_from_system ( ) . replace ( " : " , " " )
var _filename : String = " save_ " + current_date + " .tres "
var _filename : String = _save_game_settings . save_file_name_prepend + current_date + " .tres "
_loaded_save_resource . save_name = save_name
_save_game_as_resource ( save_name , _filename )
save_complete . emit ( )
func _on_start_autosave ( ) - > void :
if _save_game_settings == null :
_load_save_settings ( )
_autosave_timer . start ( _save_game_settings . autosave_duration )
func _on_stop_autosave ( ) - > void :
_autosave_timer . stop ( )
func _on_toggle_save_icon_generation ( toggled : bool ) - > void :
_enable_save_icon_generation = toggled