Skip to content

Commit

Permalink
Merge pull request #18 from theludovyc/fix-#17
Browse files Browse the repository at this point in the history
fix #17 Handle saves
  • Loading branch information
Jeremi360 authored Oct 26, 2024
2 parents df5f53f + aaea76d commit d7335c2
Show file tree
Hide file tree
Showing 12 changed files with 493 additions and 4 deletions.
187 changes: 187 additions & 0 deletions addons/rakugo_game_template/scripts/SaveHelper.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
extends Object
class_name SaveHelper
## Helps to save data in [JSON] format
##
## Here is a sample on how to save data:
## [codeblock]
## var data_to_save := {"something":"Something"}
## if SaveHelper.save(data_to_save) != OK:
## push_error("Cannot save data")
## [/codeblock]
## And how to load and use them:
## [codeblock]
## SaveHelper.update_save_file_names()
##
## if SaveHelper.load_last_save() != OK:
## push_error("Cannot load data")
## var something := SaveHelper.last_saved_data["something"]
## [/codeblock]


## where the data will be saved
const save_dir_path = "user://saves"

## to avoid errors
const json_extension = "json"

## updated with SaveHelper.update_save_files_names [br]
## saved here to avoid multiples disk access [br]
## after update it, can be used to check if save(s) exist or not [br]
## should be clear with SaveHelper.clear_save_files_names to avoid
## memory consumption
static var save_file_names:PackedStringArray

## the last saved file name without extension .json
static var last_saved_file_name := ""

## use it to save the file_name to load between scenes
static var save_file_name_to_load := ""

## last loaded data [br]
## empty [Dictionary] by default
static var last_loaded_data:Dictionary = {}

## save data in a file [br]
## the file will be created in save_dir_path [br]
## the file_name will be generated from the systemTime in local time [br]
## the file_name name will look like YYYY-MM-DDTHH:MM:SS.json [br]
## yes, the user can modify his system time, so you can use this feature and/or create easter-eggs [br]
## if cannot create saves directory return ERR_CANT_CREATE [br]
## if cannot create and write in the save file return ERR_FILE_CANT_WRITE [br]
## return OK in other cases
static func save(data:Dictionary) -> Error:
if not DirAccess.dir_exists_absolute(save_dir_path):
if DirAccess.make_dir_absolute(save_dir_path) != OK:
push_error("Cannot create saves directory in user://")
return ERR_CANT_CREATE

var file_name := Time.get_datetime_string_from_system()

var file := FileAccess.open(save_dir_path + "/" + file_name + "." + json_extension, FileAccess.WRITE)
if file == null:
push_error("Cannot create the save file in " + save_dir_path)
return ERR_FILE_CANT_WRITE

var json_data = JSON.stringify(data)

file.store_string(json_data)

last_saved_file_name = file_name

return OK

## return a file_path into the save directory from the file_name [br]
## if file_name is empty return empty String [br]
## if the extension .json is missing it will be added
static func get_save_file_path(file_name:String) -> String:
if file_name.is_empty():
return ""

if not file_name.get_extension() == json_extension:
file_name += "." + json_extension

return save_dir_path + "/" + file_name

## return a file_path into the save directory from the file_name [br]
## if the extension .json is present it will be removed
## if file_name is empty return empty String
static func get_save_file_path_without_extension(file_name:String) -> String:
if file_name.is_empty():
return ""

return save_dir_path + "/" + file_name.trim_suffix("." + json_extension)

## load data from file [br]
## file_name should looks like YYYY-MM-DDTHH:MM:SS.json [br]
## if file_name is empty return ERR_INVALID_PARAMETER [br]
## if the extension .json is missing it will be added [br]
## if the file cannot be opened and read return ERR_FILE_CANT_READ [br]
## if the file cannot be parsed to [JSON] return ERR_INVALID_DATA [br]
## return OK in other cases and save the parsed result in last_loaded_data
static func load(file_name:String) -> Error:
if file_name.is_empty():
push_warning("file_name is empty !")
return ERR_INVALID_PARAMETER

var file_path := get_save_file_path(file_name)

var file := FileAccess.open(file_path, FileAccess.READ)
if file == null:
push_error("Cannot open the save file: " + file_path)
return ERR_FILE_CANT_READ

var json_data := file.get_as_text(true)

var parsed_json = JSON.parse_string(json_data)

if parsed_json == null:
last_loaded_data = {}

push_error("Cannot parse to json the save file")
return ERR_INVALID_DATA

last_loaded_data = parsed_json

return OK

## Call SaveHelper.load(save_file_name_to_load) [br]
## if save_file_name_to_load is empty return ERR_INVALID_PARAMETER
static func load_saved_file_name() -> Error:
return SaveHelper.load(save_file_name_to_load)

## warning, make disk access, avoid multiples use [br]
## set SaveHelper.save_file_names
static func update_save_file_names() -> Error:
var dirAccess := DirAccess.open(save_dir_path)
if dirAccess == null:
push_warning("Cannot open the save directory")
return FAILED

save_file_names.clear()

dirAccess.list_dir_begin()

var file_name = dirAccess.get_next()

while not file_name.is_empty():
if not dirAccess.current_is_dir() \
and file_name.get_extension() == json_extension:
save_file_names.push_back(file_name.left(file_name.rfind(".")))

file_name = dirAccess.get_next()

if not save_file_names.is_empty():
save_file_names.sort()

return OK

## set SaveHelper.save_file_name_to_load with last saved name [br]
## if save_files_names is empty return ERR_DOES_NOT_EXIST [br]
## return OK in other cases
static func update_with_last_saved_name() -> Error:
if save_file_names.is_empty():
push_warning("No save to load")
return ERR_DOES_NOT_EXIST

save_file_name_to_load = save_file_names[-1]

return OK

## move to trash the save file [br]
## file_name should looks like YYYY-MM-DDTHH:MM:SS.json [br]
## if file_name is empty return ERR_INVALID_PARAMETER [br]
## if the extension .json is missing it will be added [br]
## if the file cannot be found return ERR_FILE_NOT_FOUND [br]
## return OS.move_to_trash(...) in other cases
static func delete(file_name:String) -> Error:
if file_name.is_empty():
push_warning("file_name is empty !")
return ERR_INVALID_PARAMETER

var file_path := get_save_file_path(file_name)

if not FileAccess.file_exists(file_path):
push_error("Cannot found the save file: " + file_path)
return ERR_FILE_NOT_FOUND

return OS.move_to_trash(ProjectSettings.globalize_path(file_path))
2 changes: 1 addition & 1 deletion scenes/Game/Sprite2D.gd
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ extends Sprite2D
#I permit to see if the pause is effective

func _process(delta):
rotate(delta)
rotate(delta / 2.0)
pass
17 changes: 17 additions & 0 deletions scenes/Game/game.gd
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ class_name Game
@onready var pause_menu = %PauseMenu
@onready var end_menu = %EndMenu

@onready var pause_checker_sprite = $PauseChecker

func _ready() -> void:
if SaveHelper.save_file_name_to_load.is_empty():
return

if not SaveHelper.load_saved_file_name() == OK:
return

pause_checker_sprite.rotation = \
SaveHelper.last_loaded_data.get("pause_checker_sprite_rot", 0)

func _process(_delta):
if pause_menu.visible == false and Input.is_action_just_pressed("ui_cancel"):
pause_menu.show()
Expand All @@ -19,3 +31,8 @@ func _on_gameover():
end_menu.set_gameover()
end_menu.show()
get_tree().paused = true

func _on_pause_menu_ask_to_save() -> void:
pause_menu.save_this_please({
"pause_checker_sprite_rot":pause_checker_sprite.rotation
})
1 change: 1 addition & 0 deletions scenes/Game/game.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,6 @@ position = Vector2(637, 143)
texture = ExtResource("3_6luik")
script = ExtResource("4_lltyx")

[connection signal="ask_to_save" from="GUI/PauseMenu" to="." method="_on_pause_menu_ask_to_save"]
[connection signal="pressed" from="RemoveMe/HBoxContainer/WinButton" to="." method="_on_win"]
[connection signal="pressed" from="RemoveMe/HBoxContainer/LooseButton" to="." method="_on_gameover"]
21 changes: 21 additions & 0 deletions scenes/LoadSaveMenu/loadSaveMenu.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[gd_scene load_steps=2 format=3 uid="uid://choevp6ilq78t"]

[ext_resource type="Script" path="res://scenes/LoadSaveMenu/load_save_menu.gd" id="1_nyk5l"]

[node name="LoadSaveMenu" type="ScrollContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
horizontal_scroll_mode = 0
script = ExtResource("1_nyk5l")

[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 3

[node name="ConfirmationDialog" type="ConfirmationDialog" parent="."]
unique_name_in_owner = true

[connection signal="confirmed" from="ConfirmationDialog" to="." method="_on_confirmation_dialog_confirmed"]
85 changes: 85 additions & 0 deletions scenes/LoadSaveMenu/load_save_menu.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
extends ScrollContainer

signal no_save_to_load

const confirm_load = "Are you sure you want to load this save?\n"
const confirm_delete = "Are you sure you want to delete this save ?\n"

var SavePanel = preload("res://scenes/LoadSaveMenu/savePanelContainer.tscn")

@onready var vbox_container = $VBoxContainer

@onready var confirm_dialog = %ConfirmationDialog

enum Modes{
Loading,
Deleting
}

var popup_mode = Modes.Loading
var current_save_file_name:String = ""
var current_save_panel:Node = null

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
if SaveHelper.save_file_names.is_empty():
SaveHelper.update_save_file_names()

if SaveHelper.save_file_names.is_empty():
push_warning("No save to load")
pass

for save_file_name in SaveHelper.save_file_names:
var save_panel = SavePanel.instantiate()

vbox_container.add_child(save_panel)

save_panel.init(save_file_name)

save_panel.load_button.pressed.connect(_on_load_button_pressed.bind(save_file_name))
save_panel.delete_button.pressed.connect(
_on_delete_button_pressed.bind(save_panel, save_file_name))

func _on_load_button_pressed(save_file_name:String):
popup_mode = Modes.Loading

current_save_file_name = save_file_name

confirm_dialog.dialog_text = confirm_load + save_file_name

confirm_dialog.popup_centered()

func _on_delete_button_pressed(save_panel:Node, save_file_name:String):
popup_mode = Modes.Deleting

current_save_file_name = save_file_name

current_save_panel = save_panel

confirm_dialog.dialog_text = confirm_delete + save_file_name

confirm_dialog.popup_centered()

func _on_confirmation_dialog_confirmed() -> void:
match(popup_mode):
Modes.Loading:
SaveHelper.save_file_name_to_load = current_save_file_name

SceneLoader.change_scene(RGT_Globals.first_game_scene_setting)

Modes.Deleting:
SaveHelper.delete(current_save_file_name)

# move to trash the screenshot
OS.move_to_trash(
ProjectSettings.globalize_path(
SaveHelper.get_save_file_path_without_extension(current_save_file_name) + ".png"
))

SaveHelper.update_save_file_names()

if SaveHelper.save_file_names.is_empty():
no_save_to_load.emit()

current_save_panel.queue_free()

49 changes: 49 additions & 0 deletions scenes/LoadSaveMenu/savePanelContainer.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
[gd_scene load_steps=2 format=3 uid="uid://wal8k0gos06y"]

[ext_resource type="Script" path="res://scenes/LoadSaveMenu/save_panel_container.gd" id="1_np60c"]

[node name="SavePanelContainer" type="PanelContainer"]
script = ExtResource("1_np60c")

[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 2
theme_override_constants/margin_left = 12
theme_override_constants/margin_top = 12
theme_override_constants/margin_right = 12
theme_override_constants/margin_bottom = 12

[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer"]
layout_mode = 2
theme_override_constants/separation = 8

[node name="SaveTextureRect" type="TextureRect" parent="MarginContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 4
expand_mode = 5
stretch_mode = 5

[node name="NameLabel" type="Label" parent="MarginContainer/HBoxContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(8, 8)
layout_mode = 2
size_flags_horizontal = 3
horizontal_alignment = 1
vertical_alignment = 1
autowrap_mode = 1

[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_vertical = 4
alignment = 1

[node name="LoadButton" type="Button" parent="MarginContainer/HBoxContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Load"

[node name="DeleteButton" type="Button" parent="MarginContainer/HBoxContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Delete"
Loading

0 comments on commit d7335c2

Please sign in to comment.