2024-10-01 23:26:54 +00:00
class_name SaveGame extends Resource
2026-01-16 12:03:39 +00:00
2025-03-25 21:34:13 +00:00
var _is_initialised : bool = false
2024-10-02 23:10:35 +00:00
2025-12-12 23:22:21 +00:00
var current_room_path : String :
get ( ) :
return Main . room_paths [ current_room ]
2025-03-25 21:34:13 +00:00
@ export var filepath : String :
set ( value ) :
filepath = value
if _is_initialised :
changed . emit ( )
2025-12-12 23:22:21 +00:00
2025-12-13 10:06:15 +00:00
@ export var unique_save_name : String = " frame_of_mind_ %s _ %s " % [ Time . get_date_string_from_system ( ) , Time . get_time_string_from_system ( ) . replace ( " : " , " - " ) ] + " - " + str ( randi ( ) ) :
2025-12-13 23:53:48 +00:00
set ( value ) :
2025-03-25 21:34:13 +00:00
unique_save_name = value
if _is_initialised : changed . emit ( )
2025-12-12 23:22:21 +00:00
2025-03-25 21:34:13 +00:00
@ 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
2026-01-16 12:03:39 +00:00
@ 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 ( )
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 ( )
2024-10-02 23:10:35 +00:00
2025-03-25 21:34:13 +00:00
@ export var is_valid : bool = false
2025-05-26 17:43:26 +00:00
@ export var is_demo : bool = OS . has_feature ( " Demo " )
2025-03-25 21:34:13 +00:00
@ export var is_empty : bool = true :
get ( ) :
2026-01-16 11:08:25 +00:00
return not FileAccess . file_exists ( filepath ) or ( current_room == State . rooms . NULL )
2026-01-16 12:03:39 +00:00
2025-03-25 21:34:13 +00:00
@ export var save_manually : bool = false :
set ( val ) :
if val : save_to_file ( thumbnail )
2025-12-08 09:43:02 +00:00
2024-10-06 09:38:15 +00:00
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
2024-10-06 09:38:15 +00:00
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
2024-10-06 09:38:15 +00:00
2026-01-16 12:03:39 +00:00
## Creates a NEW save game (for starting a new game)
func _init ( filepath_or_debug : String = " " ) - > void :
if filepath_or_debug == " " :
filepath = " %s / %s .tres " % [ State . user_saves_path , unique_save_name ]
elif filepath_or_debug == " DEBUG " :
filepath = " DEBUG "
2025-09-04 23:54:21 +00:00
if OS . has_feature ( " debug " ) or OS . has_feature ( " demo " ) :
push_warning ( " Created DEBUG savegame. Progress will not be stored! " )
else :
2025-12-13 10:06:15 +00:00
print_debug ( get_stack ( ) )
2025-09-04 23:54:21 +00:00
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. " )
2025-03-25 21:34:13 +00:00
else :
2026-01-16 12:03:39 +00:00
filepath = filepath_or_debug
unique_save_name = filepath_or_debug . get_file ( ) . get_basename ( )
# Ensure save directory exists
if filepath != " DEBUG " and not DirAccess . dir_exists_absolute ( filepath . get_base_dir ( ) ) :
DirAccess . make_dir_absolute ( filepath . get_base_dir ( ) )
print_debug ( " Savegame: Creating new save for path: %s " % filepath )
is_valid = true
_is_initialised = true
## Static factory method to load an EXISTING save from disk
static func load_from_file ( save_filepath : String ) - > SaveGame :
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 ] )
2024-10-06 09:38:15 +00:00
2025-12-12 18:19:37 +00:00
func save_to_file ( screen_shot : Texture ) - > void :
2025-12-13 10:10:11 +00:00
print_debug ( " Savegame: Saving to file: %s " % filepath )
2025-12-13 23:53:48 +00:00
2025-09-04 23:54:21 +00:00
if filepath == " DEBUG " :
2025-12-13 10:10:11 +00:00
push_warning ( " Savegame: Saving DEBUG save skipped. This is intentional. " )
2025-09-04 23:54:21 +00:00
return
2025-12-08 09:43:02 +00:00
2025-10-29 21:49:29 +00:00
if current_room == State . rooms . NULL :
2025-12-13 10:10:11 +00:00
push_warning ( " Savegame: Not saving empty savegame. " )
2025-10-29 21:49:29 +00:00
return
2025-12-08 09:43:02 +00:00
2026-01-16 12:03:39 +00:00
# Capture player state before saving
capture_player_state ( )
2025-12-12 18:19:37 +00:00
last_saved = int ( Time . get_unix_time_from_system ( ) )
2026-01-16 12:03:39 +00:00
# Save thumbnail separately as PNG
2025-12-12 18:19:37 +00:00
var thumbnail_image : Image = screen_shot . get_image ( )
2025-05-26 17:43:26 +00:00
thumbnail_image . convert ( Image . Format . FORMAT_RGB8 )
thumbnail_image . linear_to_srgb ( )
2025-12-12 18:19:37 +00:00
thumbnail_image . resize ( 384 , 216 , Image . INTERPOLATE_LANCZOS ) # nonexistent call in ViewportTexture
thumbnail_image . crop ( 384 , 216 )
2025-12-08 09:43:02 +00:00
2025-12-12 18:19:37 +00:00
var save_dir : = DirAccess . open ( filepath . get_base_dir ( ) )
2025-03-25 21:34:13 +00:00
if not save_dir . dir_exists ( " thumbnails " ) :
save_dir . make_dir ( " thumbnails " )
2025-12-08 09:43:02 +00:00
2025-12-12 18:19:37 +00:00
var thumbnail_path : String = " %s /thumbnails/ %s .png " % [ filepath . get_base_dir ( ) , unique_save_name ]
thumbnail_image . save_png ( thumbnail_path )
2026-01-16 12:03:39 +00:00
# Save the resource using Godot's native serialization
var save_result : = ResourceSaver . save ( self , filepath )
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 )
2025-12-08 09:43:02 +00:00
2025-03-25 21:34:13 +00:00
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
2025-12-08 09:43:02 +00:00
2025-03-25 21:34:13 +00:00
for sticky_position in board_state . stickies . values ( ) :
connections += int ( sticky_position is String )
2025-12-08 09:43:02 +00:00
2025-03-25 21:34:13 +00:00
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-13 11:56:02 +00:00
if not ( sticky is int or sticky is Vector2 or sticky is float 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