feat: savegames save as .tres
feat: savegames restore player position fix: savegames now more robust
This commit is contained in:
parent
bc91204aa2
commit
eefc38fb42
|
|
@ -49,6 +49,9 @@ func pull_save_state(save: SaveGame) -> void:
|
||||||
save.current_room = State.rooms.ADULTHOOD
|
save.current_room = State.rooms.ADULTHOOD
|
||||||
save_game = save
|
save_game = save
|
||||||
|
|
||||||
|
# Call parent to restore player position
|
||||||
|
super.pull_save_state(save)
|
||||||
|
|
||||||
func prepare_transition():
|
func prepare_transition():
|
||||||
for child in %Stations.get_children():
|
for child in %Stations.get_children():
|
||||||
if child is Node3D:
|
if child is Node3D:
|
||||||
|
|
|
||||||
|
|
@ -29,14 +29,24 @@ func _ready():
|
||||||
card_picker.cards_picked.connect(card_board.populate_board)
|
card_picker.cards_picked.connect(card_board.populate_board)
|
||||||
|
|
||||||
func pull_save_state(save: SaveGame) -> void:
|
func pull_save_state(save: SaveGame) -> void:
|
||||||
save.board_state = card_board.get_save_dict()
|
|
||||||
save.current_room = State.rooms.ADULTHOOD
|
save.current_room = State.rooms.ADULTHOOD
|
||||||
save.mementos_complete = Scenes.completed_sequences
|
#FIXME: fix the bloddy card board loading algorythm
|
||||||
|
#card_board.initialise_from_save(save)
|
||||||
|
|
||||||
|
# Call parent to restore player position
|
||||||
|
super.pull_save_state(save)
|
||||||
|
|
||||||
func _on_scene_finished(_id: int, _repeat:bool):
|
func _on_scene_finished(_id: int, _repeat:bool):
|
||||||
await get_tree().create_timer(3).timeout
|
await get_tree().create_timer(3).timeout
|
||||||
save_room()
|
save_room()
|
||||||
|
|
||||||
|
func save_room():
|
||||||
|
# Update board state before saving
|
||||||
|
save_game.board_state = card_board.get_save_dict()
|
||||||
|
save_game.mementos_complete = Scenes.completed_sequences
|
||||||
|
save_game.sequences_enabled = Scenes.enabled_sequences
|
||||||
|
super.save_room()
|
||||||
|
|
||||||
func prepare_transition():
|
func prepare_transition():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,17 +62,26 @@ func _ready():
|
||||||
func pull_save_state(save: SaveGame) -> void:
|
func pull_save_state(save: SaveGame) -> void:
|
||||||
save_game = save
|
save_game = save
|
||||||
save_game.current_room = id
|
save_game.current_room = id
|
||||||
save_game.board_state = card_board.get_save_dict()
|
|
||||||
|
# Load board state from save first, before overwriting it
|
||||||
card_board.initialise_from_save(save_game)
|
card_board.initialise_from_save(save_game)
|
||||||
|
|
||||||
Scenes.started_sequences = save_game.mementos_complete
|
Scenes.started_sequences = save_game.mementos_complete
|
||||||
Scenes.completed_sequences = save_game.mementos_complete
|
Scenes.completed_sequences = save_game.mementos_complete
|
||||||
|
|
||||||
|
# Call parent to restore player position
|
||||||
|
super.pull_save_state(save)
|
||||||
|
|
||||||
|
|
||||||
func _on_scene_finished(_id: int, _repeat:bool):
|
func _on_scene_finished(_id: int, _repeat:bool):
|
||||||
await get_tree().create_timer(3).timeout
|
await get_tree().create_timer(3).timeout
|
||||||
save_room()
|
save_room()
|
||||||
|
|
||||||
|
func save_room():
|
||||||
|
# Update board state before saving
|
||||||
|
save_game.board_state = card_board.get_save_dict()
|
||||||
|
save_game.mementos_complete = Scenes.completed_sequences
|
||||||
|
super.save_room()
|
||||||
|
|
||||||
func prepare_transition():
|
func prepare_transition():
|
||||||
save_room()
|
save_room()
|
||||||
|
|
|
||||||
|
|
@ -1871,7 +1871,7 @@ light_size = 20.0
|
||||||
omni_range = 16.8518
|
omni_range = 16.8518
|
||||||
|
|
||||||
[node name="MaskInteractable" parent="logic" instance=ExtResource("22_ks23q")]
|
[node name="MaskInteractable" parent="logic" instance=ExtResource("22_ks23q")]
|
||||||
transform = Transform3D(-0.8827416, 0, 0.4698562, 0, 1, 0, -0.4698562, 0, -0.8827416, -0.025371574, 0.55708295, 2.5263817)
|
transform = Transform3D(-0.8827416, 0, 0.4698562, 0, 1, 0, -0.4698562, 0, -0.8827416, 0.028929986, 0.58693635, 2.552513)
|
||||||
interaction = ExtResource("12_viwxf")
|
interaction = ExtResource("12_viwxf")
|
||||||
|
|
||||||
[node name="MindBoardInteractable" parent="logic" instance=ExtResource("22_ks23q")]
|
[node name="MindBoardInteractable" parent="logic" instance=ExtResource("22_ks23q")]
|
||||||
|
|
@ -1890,7 +1890,7 @@ interaction = ExtResource("13_v3447")
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.36574292, 0.099999994, 0.032779038)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.36574292, 0.099999994, 0.032779038)
|
||||||
|
|
||||||
[node name="ClothesInteractable" parent="logic" instance=ExtResource("22_ks23q")]
|
[node name="ClothesInteractable" parent="logic" instance=ExtResource("22_ks23q")]
|
||||||
transform = Transform3D(0.7935111, 0, -0.60855323, 0, 1, 0, 0.60855323, 0, 0.7935111, 1.6713148, 1.089737, -0.92289597)
|
transform = Transform3D(0.7935111, 0, -0.60855323, 0, 1, 0, 0.60855323, 0, 0.7935111, 1.6334484, 1.1331886, -0.8914416)
|
||||||
interaction = ExtResource("12_x3dlb")
|
interaction = ExtResource("12_x3dlb")
|
||||||
|
|
||||||
[node name="Sprite3D" parent="logic/ClothesInteractable/View" index="0"]
|
[node name="Sprite3D" parent="logic/ClothesInteractable/View" index="0"]
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,23 @@ func start_room():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
func pull_save_state(_save: SaveGame) -> void:
|
func pull_save_state(_save: SaveGame) -> void:
|
||||||
pass
|
# Try to restore player position from save
|
||||||
|
restore_player_from_save(_save)
|
||||||
|
|
||||||
|
## Attempts to find player controller and restore position/rotation from save
|
||||||
|
func restore_player_from_save(save: SaveGame) -> void:
|
||||||
|
var player: PlayerController = null
|
||||||
|
|
||||||
|
# Try to find player controller in common locations
|
||||||
|
if has_node("%PlayerController"):
|
||||||
|
player = get_node("%PlayerController")
|
||||||
|
elif has_node("logic/PlayerController"):
|
||||||
|
player = get_node("logic/PlayerController")
|
||||||
|
|
||||||
|
if player and player is PlayerController:
|
||||||
|
player.restore_from_save(save)
|
||||||
|
else:
|
||||||
|
print_debug("RoomTemplate: Could not find PlayerController to restore position")
|
||||||
|
|
||||||
func save_room():
|
func save_room():
|
||||||
save_game.save_to_file(get_tree().root.get_texture())
|
save_game.save_to_file(get_tree().root.get_texture())
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
class_name SaveGame extends Resource
|
class_name SaveGame extends Resource
|
||||||
|
|
||||||
|
|
||||||
var _is_initialised: bool = false
|
var _is_initialised: bool = false
|
||||||
|
|
||||||
var current_room_path: String:
|
var current_room_path: String:
|
||||||
|
|
@ -38,6 +39,18 @@ var current_room_path: String:
|
||||||
childhood_mementos = value
|
childhood_mementos = value
|
||||||
if _is_initialised: changed.emit()
|
if _is_initialised: changed.emit()
|
||||||
@export var is_childhood_board_complete: bool = false
|
@export var is_childhood_board_complete: bool = false
|
||||||
|
@export var player_position: Vector3 = Vector3.ZERO:
|
||||||
|
set(value):
|
||||||
|
player_position = value
|
||||||
|
if _is_initialised: changed.emit()
|
||||||
|
@export var player_yaw: float = 0.0:
|
||||||
|
set(value):
|
||||||
|
player_yaw = value
|
||||||
|
if _is_initialised: changed.emit()
|
||||||
|
@export var player_pitch: float = 0.0:
|
||||||
|
set(value):
|
||||||
|
player_pitch = value
|
||||||
|
if _is_initialised: changed.emit()
|
||||||
@export var thumbnail: Texture = preload("res://import/interface-elements/empty_save_slot.png"):
|
@export var thumbnail: Texture = preload("res://import/interface-elements/empty_save_slot.png"):
|
||||||
set(value):
|
set(value):
|
||||||
thumbnail = value
|
thumbnail = value
|
||||||
|
|
@ -67,107 +80,81 @@ func _validate_property(property: Dictionary):
|
||||||
if property.name == "is_empty":
|
if property.name == "is_empty":
|
||||||
property.usage |= PROPERTY_USAGE_READ_ONLY
|
property.usage |= PROPERTY_USAGE_READ_ONLY
|
||||||
|
|
||||||
func _init(initial_filepath = "") -> void:
|
## Creates a NEW save game (for starting a new game)
|
||||||
if initial_filepath == "":
|
func _init(filepath_or_debug: String = "") -> void:
|
||||||
filepath = "%s/%s.json" % [State.user_saves_path, unique_save_name]
|
if filepath_or_debug == "":
|
||||||
elif initial_filepath == "DEBUG":
|
filepath = "%s/%s.tres" % [State.user_saves_path, unique_save_name]
|
||||||
filepath = initial_filepath
|
elif filepath_or_debug == "DEBUG":
|
||||||
else:
|
filepath = "DEBUG"
|
||||||
filepath = initial_filepath
|
|
||||||
unique_save_name = initial_filepath.get_file()
|
|
||||||
|
|
||||||
read_save_file()
|
|
||||||
_is_initialised = true
|
|
||||||
|
|
||||||
func read_save_file() -> void:
|
|
||||||
if not DirAccess.dir_exists_absolute(filepath.get_base_dir()):
|
|
||||||
DirAccess.make_dir_absolute(filepath.get_base_dir())
|
|
||||||
|
|
||||||
if filepath == "DEBUG":
|
|
||||||
if OS.has_feature("debug") or OS.has_feature("demo"):
|
if OS.has_feature("debug") or OS.has_feature("demo"):
|
||||||
push_warning("Created DEBUG savegame. Progress will not be stored!")
|
push_warning("Created DEBUG savegame. Progress will not be stored!")
|
||||||
else:
|
else:
|
||||||
print_debug(get_stack())
|
print_debug(get_stack())
|
||||||
push_error("Created DEBUG savegame outside of demo or debug environment. This is unintentional and will lead to data loss. Please contact support and attatch the stack above.")
|
push_error("Created DEBUG savegame outside of demo or debug environment. This is unintentional and will lead to data loss. Please contact support and attatch the stack above.")
|
||||||
#TODO maybe cause a crash here?
|
|
||||||
return
|
|
||||||
|
|
||||||
if FileAccess.file_exists(filepath):
|
|
||||||
print_debug("Savegame: Reading from: %s" % filepath)
|
|
||||||
var file := FileAccess.open(filepath, FileAccess.READ)
|
|
||||||
var raw_json := FileAccess.get_file_as_string(filepath)
|
|
||||||
file.close()
|
|
||||||
var parsed: Dictionary = JSON.parse_string(raw_json)
|
|
||||||
|
|
||||||
var tmp_img: Image
|
|
||||||
|
|
||||||
if FileAccess.file_exists("%s/thumbnails/%s.png" % [filepath.get_base_dir(), unique_save_name]):
|
|
||||||
tmp_img = Image.load_from_file("%s/thumbnails/%s.png" % [filepath.get_base_dir(), unique_save_name])
|
|
||||||
|
|
||||||
var are_types_valid := (
|
|
||||||
parsed["unique_save_name"] is String and
|
|
||||||
parsed["current_room"] is float and
|
|
||||||
parsed["mementos_complete"] is float and
|
|
||||||
parsed["board_state"] is Dictionary and
|
|
||||||
parsed["is_childhood_board_complete"] is bool and
|
|
||||||
parsed["last_saved"] is float# and FIXME
|
|
||||||
#parsed["demo"] is bool and last_saved != 0
|
|
||||||
)
|
|
||||||
|
|
||||||
if are_types_valid:
|
|
||||||
for key in parsed.keys():
|
|
||||||
set(key, parsed[key])
|
|
||||||
|
|
||||||
for dict:Dictionary in [board_state["cards"], board_state["stickies"]]:
|
|
||||||
for key in dict.keys():
|
|
||||||
if dict[key] is String:
|
|
||||||
if dict[key].begins_with("("):
|
|
||||||
dict[key] = parse_vec_from_string(dict[key])
|
|
||||||
|
|
||||||
var cards: Dictionary[StringName, Variant]
|
|
||||||
var stickies: Dictionary[StringName, Variant]
|
|
||||||
var randoms: Array[StringName]
|
|
||||||
|
|
||||||
for cardname:String in board_state["cards"]:
|
|
||||||
cards[StringName(cardname)] = board_state["cards"][cardname]
|
|
||||||
for sticky_name:String in board_state["stickies"]:
|
|
||||||
stickies[StringName(sticky_name)] = board_state["stickies"][sticky_name]
|
|
||||||
for random_name:StringName in board_state["randoms"]:
|
|
||||||
randoms.append( random_name )
|
|
||||||
|
|
||||||
board_state = {
|
|
||||||
"cards": cards,
|
|
||||||
"stickies": stickies,
|
|
||||||
"randoms": randoms
|
|
||||||
}
|
|
||||||
|
|
||||||
is_valid = are_types_valid \
|
|
||||||
and current_room >= 0 \
|
|
||||||
and current_room < State.rooms.keys().size() \
|
|
||||||
and validate_board_state()
|
|
||||||
|
|
||||||
if not is_valid:
|
|
||||||
push_error("Parsing of Save failed.")
|
|
||||||
|
|
||||||
if tmp_img != null:
|
|
||||||
thumbnail = ImageTexture.create_from_image(tmp_img)
|
|
||||||
is_empty = false
|
|
||||||
else:
|
else:
|
||||||
print_debug("Savegame: Creating (in memory) for path: %s" % filepath)
|
filepath = filepath_or_debug
|
||||||
is_valid = true
|
unique_save_name = filepath_or_debug.get_file().get_basename()
|
||||||
|
|
||||||
func _get_save_dict() -> Dictionary:
|
# Ensure save directory exists
|
||||||
return {
|
if filepath != "DEBUG" and not DirAccess.dir_exists_absolute(filepath.get_base_dir()):
|
||||||
"unique_save_name": unique_save_name,
|
DirAccess.make_dir_absolute(filepath.get_base_dir())
|
||||||
"current_room": current_room,
|
|
||||||
"mementos_complete": mementos_complete,
|
print_debug("Savegame: Creating new save for path: %s" % filepath)
|
||||||
"sequences_enabled": sequences_enabled,
|
is_valid = true
|
||||||
"childhood_mementos": childhood_mementos,
|
_is_initialised = true
|
||||||
"board_state": board_state,
|
|
||||||
"is_childhood_board_complete": is_childhood_board_complete,
|
## Static factory method to load an EXISTING save from disk
|
||||||
"last_saved": last_saved,
|
static func load_from_file(save_filepath: String) -> SaveGame:
|
||||||
"is_demo": is_demo
|
if not FileAccess.file_exists(save_filepath):
|
||||||
}
|
push_error("SaveGame: File does not exist: %s" % save_filepath)
|
||||||
|
return null
|
||||||
|
|
||||||
|
print_debug("Savegame: Loading from: %s" % save_filepath)
|
||||||
|
|
||||||
|
var loaded: SaveGame = ResourceLoader.load(save_filepath, "", ResourceLoader.CACHE_MODE_IGNORE)
|
||||||
|
|
||||||
|
if not loaded:
|
||||||
|
push_error("Failed to load SaveGame resource from: %s" % save_filepath)
|
||||||
|
return null
|
||||||
|
|
||||||
|
# Update filepath to actual location (in case file was moved)
|
||||||
|
loaded.filepath = save_filepath
|
||||||
|
loaded.unique_save_name = save_filepath.get_file().get_basename()
|
||||||
|
|
||||||
|
# Ensure randoms array exists (backwards compatibility)
|
||||||
|
if "randoms" not in loaded.board_state:
|
||||||
|
loaded.board_state["randoms"] = []
|
||||||
|
|
||||||
|
# Load thumbnail separately (not stored in .tres)
|
||||||
|
var thumbnail_path := "%s/thumbnails/%s.png" % [save_filepath.get_base_dir(), loaded.unique_save_name]
|
||||||
|
if FileAccess.file_exists(thumbnail_path):
|
||||||
|
var tmp_img: Image = Image.load_from_file(thumbnail_path)
|
||||||
|
if tmp_img != null:
|
||||||
|
loaded.thumbnail = ImageTexture.create_from_image(tmp_img)
|
||||||
|
|
||||||
|
# Validate the loaded data
|
||||||
|
loaded.is_valid = loaded.current_room >= 0 \
|
||||||
|
and loaded.current_room < State.rooms.keys().size() \
|
||||||
|
and loaded.validate_board_state()
|
||||||
|
|
||||||
|
if not loaded.is_valid:
|
||||||
|
push_error("Validation of loaded save failed: %s" % save_filepath)
|
||||||
|
return null
|
||||||
|
|
||||||
|
loaded._is_initialised = true
|
||||||
|
|
||||||
|
return loaded
|
||||||
|
|
||||||
|
## Captures current player position and camera rotation
|
||||||
|
func capture_player_state() -> void:
|
||||||
|
if State.player:
|
||||||
|
player_position = State.player.global_position
|
||||||
|
# Access yaw and pitch nodes
|
||||||
|
var yaw: Node3D = State.player.get_node("Yaw")
|
||||||
|
var pitch: Node3D = yaw.get_node("Pitch")
|
||||||
|
player_yaw = yaw.rotation.y
|
||||||
|
player_pitch = pitch.rotation.x
|
||||||
|
print_debug("SaveGame: Captured player state - pos: %s, yaw: %.2f, pitch: %.2f" % [player_position, player_yaw, player_pitch])
|
||||||
|
|
||||||
func save_to_file(screen_shot: Texture) -> void:
|
func save_to_file(screen_shot: Texture) -> void:
|
||||||
print_debug("Savegame: Saving to file: %s" % filepath)
|
print_debug("Savegame: Saving to file: %s" % filepath)
|
||||||
|
|
@ -180,7 +167,12 @@ func save_to_file(screen_shot: Texture) -> void:
|
||||||
push_warning("Savegame: Not saving empty savegame.")
|
push_warning("Savegame: Not saving empty savegame.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Capture player state before saving
|
||||||
|
capture_player_state()
|
||||||
|
|
||||||
last_saved = int(Time.get_unix_time_from_system())
|
last_saved = int(Time.get_unix_time_from_system())
|
||||||
|
|
||||||
|
# Save thumbnail separately as PNG
|
||||||
var thumbnail_image: Image = screen_shot.get_image()
|
var thumbnail_image: Image = screen_shot.get_image()
|
||||||
thumbnail_image.convert(Image.Format.FORMAT_RGB8)
|
thumbnail_image.convert(Image.Format.FORMAT_RGB8)
|
||||||
thumbnail_image.linear_to_srgb()
|
thumbnail_image.linear_to_srgb()
|
||||||
|
|
@ -193,10 +185,13 @@ func save_to_file(screen_shot: Texture) -> void:
|
||||||
|
|
||||||
var thumbnail_path: String = "%s/thumbnails/%s.png" % [filepath.get_base_dir(), unique_save_name]
|
var thumbnail_path: String = "%s/thumbnails/%s.png" % [filepath.get_base_dir(), unique_save_name]
|
||||||
thumbnail_image.save_png(thumbnail_path)
|
thumbnail_image.save_png(thumbnail_path)
|
||||||
print_debug(filepath.get_base_dir())
|
|
||||||
var file := FileAccess.open(filepath, FileAccess.WRITE)
|
# Save the resource using Godot's native serialization
|
||||||
file.store_string(JSON.stringify(_get_save_dict()))
|
var save_result := ResourceSaver.save(self, filepath)
|
||||||
file.close()
|
if save_result != OK:
|
||||||
|
push_error("Failed to save resource to: %s (Error code: %d)" % [filepath, save_result])
|
||||||
|
else:
|
||||||
|
print_debug("Successfully saved to: %s" % filepath)
|
||||||
|
|
||||||
|
|
||||||
func calculate_completed_sequences() -> int:
|
func calculate_completed_sequences() -> int:
|
||||||
|
|
@ -226,7 +221,3 @@ func validate_board_state() -> bool:
|
||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
return false
|
return false
|
||||||
|
|
||||||
func parse_vec_from_string(string: String) -> Vector2:
|
|
||||||
var string_array := string.replace("(", "").replace(")", "").split(", ")
|
|
||||||
return Vector2(float(string_array[0]), float(string_array[1]))
|
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ func _player_active(value: bool) -> void:
|
||||||
|
|
||||||
func expand() -> void:
|
func expand() -> void:
|
||||||
shown = true
|
shown = true
|
||||||
|
_process_billboard()
|
||||||
|
|
||||||
if tween and tween.is_valid():
|
if tween and tween.is_valid():
|
||||||
tween.kill()
|
tween.kill()
|
||||||
|
|
@ -72,7 +73,6 @@ func collapse() -> void:
|
||||||
tween.parallel().tween_property(frame, "scale", Vector3.ONE * 2.0, 1.0).set_trans(Tween.TRANS_QUAD)
|
tween.parallel().tween_property(frame, "scale", Vector3.ONE * 2.0, 1.0).set_trans(Tween.TRANS_QUAD)
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
func _process(_delta: float) -> void:
|
||||||
_process_billboard()
|
|
||||||
_process_hover()
|
_process_hover()
|
||||||
|
|
||||||
func _process_billboard() -> void:
|
func _process_billboard() -> void:
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,16 @@ func _ready():
|
||||||
func _on_player_enable(enable: bool) -> void:
|
func _on_player_enable(enable: bool) -> void:
|
||||||
enabled = enable
|
enabled = enable
|
||||||
|
|
||||||
|
## Restores player position and camera rotation from save game
|
||||||
|
func restore_from_save(save: SaveGame) -> void:
|
||||||
|
if save.player_position != Vector3.ZERO:
|
||||||
|
global_position = save.player_position
|
||||||
|
yaw.rotation.y = save.player_yaw
|
||||||
|
pitch.rotation.x = save.player_pitch
|
||||||
|
print_debug("PlayerController: Restored position %s, yaw %.2f, pitch %.2f" % [save.player_position, save.player_yaw, save.player_pitch])
|
||||||
|
else:
|
||||||
|
print_debug("PlayerController: No saved position data, using default spawn")
|
||||||
|
|
||||||
func _process(_delta) -> void:
|
func _process(_delta) -> void:
|
||||||
if not enabled:
|
if not enabled:
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -43,10 +43,10 @@ func _load_games():
|
||||||
var filepaths: PackedStringArray = save_game_dir.get_files()
|
var filepaths: PackedStringArray = save_game_dir.get_files()
|
||||||
|
|
||||||
for path in filepaths:
|
for path in filepaths:
|
||||||
if path is String and path.ends_with(".json"):
|
if path is String and path.ends_with(".tres"):
|
||||||
var save := SaveGame.new("%s/%s" % [State.user_saves_path, path.get_basename()])
|
var save := SaveGame.load_from_file("%s/%s" % [State.user_saves_path, path])
|
||||||
# HACK: Skip empty saves (we decide later what to do with them)
|
# Skip invalid/empty saves
|
||||||
if not save.is_empty:
|
if save != null and not save.is_empty:
|
||||||
saves.append(save)
|
saves.append(save)
|
||||||
|
|
||||||
_sort_saves()
|
_sort_saves()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue