frame-of-mind/src/dev-util/savegame.gd

227 lines
7.7 KiB
GDScript3
Raw Normal View History

2024-10-01 23:26:54 +00:00
@tool
class_name SaveGame extends Resource
2025-03-25 21:34:13 +00:00
var _is_initialised: bool = false
2025-03-25 21:34:13 +00:00
@export var filepath: String:
set(value):
filepath = value
if _is_initialised:
read_save_file()
changed.emit()
@export var unique_save_name: String = "frame_of_mind_%s_%s" % [Time.get_date_string_from_system(), Time.get_time_string_from_system().replace(":", "-")]:
set(value):
unique_save_name = value
if _is_initialised: changed.emit()
@export var current_room: State.rooms = State.rooms.NULL:
set(value):
current_room = value
if _is_initialised: changed.emit()
@export_flags("Intro", "Childhood", "Voice Training", "Jui Jutsu") var mementos_complete: int = 0:
set(value):
mementos_complete = value
if _is_initialised: changed.emit()
2025-10-29 21:49:29 +00:00
@export_flags_2d_physics var sequences_enabled: int = 63:
set(value):
sequences_enabled = value
if _is_initialised: changed.emit()
2025-06-03 21:18:34 +00:00
@export var board_state: Dictionary = {"cards": {}, "stickies": {}, "randoms": []}:
2025-03-25 21:34:13 +00:00
set(value):
board_state = value
if _is_initialised: changed.emit()
2025-10-29 21:49:29 +00:00
@export var childhood_mementos: Dictionary = {"cards": {}, "stickies": {}, "randoms": []}:
set(value):
childhood_mementos = value
if _is_initialised: changed.emit()
2025-06-03 21:18:34 +00:00
@export var is_childhood_board_complete: bool = false
2025-03-25 21:34:13 +00:00
@export var thumbnail: Texture = preload("res://import/interface-elements/empty_save_slot.png"):
set(value):
thumbnail = value
if _is_initialised: changed.emit()
@export var last_saved: int = int(Time.get_unix_time_from_system()):
set(value):
last_saved = value
if _is_initialised: changed.emit()
2025-03-25 21:34:13 +00:00
@export var is_valid: bool = false
@export var is_demo: bool = OS.has_feature("Demo")
2025-03-25 21:34:13 +00:00
@export var is_empty: bool = true:
get():
return not FileAccess.file_exists("%s.json:" % filepath)
@export var save_manually: bool = false:
set(val):
if val: save_to_file(thumbnail)
func _validate_property(property: Dictionary):
2025-03-25 21:34:13 +00:00
if property.name == filepath:
property.usage |= PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED
if property.name == "thumbnail":
2025-03-25 21:34:13 +00:00
property.usage |= PROPERTY_USAGE_READ_ONLY
if property.name == "is_valid":
property.usage |= PROPERTY_USAGE_READ_ONLY
if property.name == "is_empty":
property.usage |= PROPERTY_USAGE_READ_ONLY
func _init(initial_filepath = "") -> void:
if initial_filepath == "":
filepath = "%s/%s.json" % [State.user_saves_path, unique_save_name]
elif initial_filepath == "DEBUG":
filepath = initial_filepath
else:
filepath = initial_filepath
2025-03-25 21:34:13 +00:00
unique_save_name = initial_filepath.get_file()
read_save_file()
2025-03-25 21:34:13 +00:00
_is_initialised = true
if not DirAccess.dir_exists_absolute(filepath.get_base_dir()):
DirAccess.make_dir_absolute(filepath.get_base_dir())
func read_save_file():
if filepath == "DEBUG":
if OS.has_feature("debug") or OS.has_feature("demo"):
push_warning("Created DEBUG savegame. Progress will not be stored!")
else:
print(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.")
#TODO maybe cause a crash here?
return
2025-03-25 21:34:13 +00:00
if FileAccess.file_exists(filepath):
2025-10-29 21:49:29 +00:00
print("Opening existing Savegame: %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
2025-03-25 21:34:13 +00:00
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])
2025-03-25 21:34:13 +00:00
var are_types_valid = (
2025-02-24 15:06:21 +00:00
parsed["unique_save_name"] is String and
2025-03-25 21:34:13 +00:00
parsed["current_room"] is float and
parsed["mementos_complete"] is float and
parsed["board_state"] is Dictionary and
2025-06-03 21:18:34 +00:00
parsed["is_childhood_board_complete"] is bool and
2025-10-29 21:49:29 +00:00
parsed["last_saved"] is float# and FIXME
#parsed["demo"] is bool and last_saved != 0
)
2025-03-25 21:34:13 +00:00
if are_types_valid:
for key in parsed.keys():
set(key, parsed[key])
2025-10-29 21:49:29 +00:00
for dict:Dictionary in [board_state["cards"], board_state["stickies"]]:
2025-03-25 21:34:13 +00:00
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]
2025-06-03 21:18:34 +00:00
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]
2025-06-03 21:18:34 +00:00
for random_name:StringName in board_state["randoms"]:
2025-10-29 21:49:29 +00:00
randoms.append( random_name )
board_state = {
"cards": cards,
2025-06-03 21:18:34 +00:00
"stickies": stickies,
"randoms": randoms
}
2025-03-25 21:34:13 +00:00
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)
2025-03-25 21:34:13 +00:00
is_empty = false
else:
2025-10-29 21:49:29 +00:00
print("Creating empty Savegame: %s" % filepath)
2025-03-25 21:34:13 +00:00
is_valid = true
func _get_save_dict() -> Dictionary:
return {
"unique_save_name": unique_save_name,
"current_room": current_room,
"mementos_complete": mementos_complete,
"sequences_enabled": sequences_enabled,
"childhood_mementos": childhood_mementos,
"board_state": board_state,
2025-06-03 21:18:34 +00:00
"is_childhood_board_complete": is_childhood_board_complete,
"last_saved": last_saved,
"is_demo": is_demo
}
func save_to_file(current_screen: Texture):
if filepath == "DEBUG":
push_warning("Saving DEBUG save skipped. This is intentional.")
return
2025-10-29 21:49:29 +00:00
if current_room == State.rooms.NULL:
print("Not saving empty savegame.")
return
2024-10-16 10:24:38 +00:00
last_saved = Time.get_unix_time_from_system()
var thumbnail_image: Image = current_screen.get_image()
thumbnail_image.convert(Image.Format.FORMAT_RGB8)
thumbnail_image.linear_to_srgb()
2025-02-06 18:15:39 +00:00
thumbnail_image.resize(384, 261, Image.INTERPOLATE_LANCZOS) # nonexistent call in ViewportTexturew
2025-03-25 21:34:13 +00:00
var thumbnail_path: String = "%s/thumbnails/%s.png" % [filepath.get_base_dir(), unique_save_name]
var save_dir = DirAccess.open(filepath.get_base_dir())
if not save_dir.dir_exists("thumbnails"):
save_dir.make_dir("thumbnails")
2025-03-25 21:34:13 +00:00
thumbnail_image.save_png("%s/thumbnails/%s.png" % [filepath.get_base_dir(), unique_save_name])
#thumbnail_image.save_png("%s/test.png" % State.user_saves_path)
print(filepath.get_base_dir())
var file = FileAccess.open(filepath, FileAccess.WRITE)
file.store_string(JSON.stringify(_get_save_dict()))
2025-03-25 21:34:13 +00:00
file.close()
func calculate_completed_sequences() -> int:
var i: int = mementos_complete - ((mementos_complete >> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
i = (i + (i >> 4)) & 0x0F0F0F0F;
i *= 0x01010101;
return i >> 24;
func calculate_total_connections() -> int:
var connections:= 0
for sticky_position in board_state.stickies.values():
connections += int(sticky_position is String)
return connections
func validate_board_state() -> bool:
if board_state.keys().has("cards") and board_state.keys().has("stickies"):
for card in board_state.cards.values():
if not card is Vector2:
push_error("Save %s could not be parsed: Corrupted Cards." % unique_save_name)
return false
for sticky in board_state.stickies.values():
2025-12-02 20:32:30 +00:00
if not (sticky is int or sticky is Vector2 or board_state.cards.keys().has(sticky)):
2025-03-25 21:34:13 +00:00
push_error("Save %s could not be parsed: Corrupted Sticky Notes.")
return false
return true
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]))