Compare commits
No commits in common. "e9700b760d2caa3391a24e1fe8f471a96cf23558" and "bc91204aa27e1bce2b9673a0a3da35757ca96311" have entirely different histories.
e9700b760d
...
bc91204aa2
|
|
@ -49,9 +49,6 @@ 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,24 +29,14 @@ 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
|
||||||
#FIXME: fix the bloddy card board loading algorythm
|
save.mementos_complete = Scenes.completed_sequences
|
||||||
#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,26 +62,17 @@ 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.028929986, 0.58693635, 2.552513)
|
transform = Transform3D(-0.8827416, 0, 0.4698562, 0, 1, 0, -0.4698562, 0, -0.8827416, -0.025371574, 0.55708295, 2.5263817)
|
||||||
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.6334484, 1.1331886, -0.8914416)
|
transform = Transform3D(0.7935111, 0, -0.60855323, 0, 1, 0, 0.60855323, 0, 0.7935111, 1.6713148, 1.089737, -0.92289597)
|
||||||
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,23 +41,7 @@ func start_room():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
func pull_save_state(_save: SaveGame) -> void:
|
func pull_save_state(_save: SaveGame) -> void:
|
||||||
# Try to restore player position from save
|
pass
|
||||||
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,221 +1,232 @@
|
||||||
class_name SaveGame extends Resource
|
class_name SaveGame extends Resource
|
||||||
|
|
||||||
## Save game data container
|
var _is_initialised: bool = false
|
||||||
## This is primarily a data class - file I/O helpers are thin wrappers around ResourceSaver/Loader
|
|
||||||
|
|
||||||
# === Computed Properties ===
|
|
||||||
|
|
||||||
var current_room_path: String:
|
var current_room_path: String:
|
||||||
get:
|
get():
|
||||||
return Main.room_paths[current_room] if Main else ""
|
return Main.room_paths[current_room]
|
||||||
|
|
||||||
var is_empty: bool:
|
@export var filepath: String:
|
||||||
get:
|
set(value):
|
||||||
return not FileAccess.file_exists(filepath) or (current_room == State.rooms.NULL)
|
filepath = value
|
||||||
|
if _is_initialised:
|
||||||
|
changed.emit()
|
||||||
|
|
||||||
# === Data Fields ===
|
@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()):
|
||||||
|
set(value):
|
||||||
|
unique_save_name = value
|
||||||
|
if _is_initialised: changed.emit()
|
||||||
|
|
||||||
@export var filepath: String = ""
|
@export var current_room: State.rooms = State.rooms.NULL:
|
||||||
@export var unique_save_name: String = ""
|
set(value):
|
||||||
@export var current_room: State.rooms = State.rooms.NULL
|
current_room = value
|
||||||
@export_flags("Intro", "Childhood", "Voice Training", "Jui Jutsu") var mementos_complete: int = 0
|
if _is_initialised: changed.emit()
|
||||||
@export_flags_2d_physics var sequences_enabled: int = 63
|
@export_flags("Intro", "Childhood", "Voice Training", "Jui Jutsu") var mementos_complete: int = 0:
|
||||||
@export var board_state: Dictionary = {"cards": {}, "stickies": {}, "randoms": []}
|
set(value):
|
||||||
@export var childhood_mementos: Dictionary = {"cards": {}, "stickies": {}, "randoms": []}
|
mementos_complete = value
|
||||||
|
if _is_initialised: changed.emit()
|
||||||
|
@export_flags_2d_physics var sequences_enabled: int = 63:
|
||||||
|
set(value):
|
||||||
|
sequences_enabled = value
|
||||||
|
if _is_initialised: changed.emit()
|
||||||
|
@export var board_state: Dictionary = {"cards": {}, "stickies": {}, "randoms": []}:
|
||||||
|
set(value):
|
||||||
|
board_state = value
|
||||||
|
if _is_initialised: changed.emit()
|
||||||
|
@export var childhood_mementos: Dictionary = {"cards": {}, "stickies": {}, "randoms": []}:
|
||||||
|
set(value):
|
||||||
|
childhood_mementos = value
|
||||||
|
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
|
@export var thumbnail: Texture = preload("res://import/interface-elements/empty_save_slot.png"):
|
||||||
@export var player_yaw: float = 0.0
|
set(value):
|
||||||
@export var player_pitch: float = 0.0
|
thumbnail = value
|
||||||
@export var last_saved: int = 0
|
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()
|
||||||
|
|
||||||
@export var is_valid: bool = false
|
@export var is_valid: bool = false
|
||||||
@export var is_demo: bool = false
|
@export var is_demo: bool = OS.has_feature("Demo")
|
||||||
|
@export var is_empty: bool = true:
|
||||||
var thumbnail: Texture = preload("res://import/interface-elements/empty_save_slot.png")
|
get():
|
||||||
|
return not FileAccess.file_exists(filepath) or (current_room == State.rooms.NULL)
|
||||||
# === Editor Conveniences ===
|
|
||||||
|
|
||||||
@export var save_manually: bool = false:
|
@export var save_manually: bool = false:
|
||||||
set(val):
|
set(val):
|
||||||
if val:
|
if val: save_to_file(thumbnail)
|
||||||
save_to_file(thumbnail)
|
|
||||||
|
|
||||||
func _validate_property(property: Dictionary):
|
func _validate_property(property: Dictionary):
|
||||||
if property.name == "filepath":
|
if property.name == filepath:
|
||||||
property.usage |= PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED
|
property.usage |= PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED
|
||||||
if property.name in ["thumbnail", "is_valid", "is_empty"]:
|
if property.name == "thumbnail":
|
||||||
|
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
|
property.usage |= PROPERTY_USAGE_READ_ONLY
|
||||||
|
|
||||||
# === Static Helpers ===
|
func _init(initial_filepath = "") -> void:
|
||||||
|
if initial_filepath == "":
|
||||||
## Generates a unique save filepath
|
filepath = "%s/%s.json" % [State.user_saves_path, unique_save_name]
|
||||||
static func generate_save_path() -> String:
|
elif initial_filepath == "DEBUG":
|
||||||
var timestamp := "%s_%s" % [Time.get_date_string_from_system(), Time.get_time_string_from_system().replace(":", "-")]
|
filepath = initial_filepath
|
||||||
var unique_name := "frame_of_mind_%s-%d" % [timestamp, randi()]
|
|
||||||
return "%s/%s.tres" % [State.user_saves_path, unique_name]
|
|
||||||
|
|
||||||
## Creates a new save game with generated filepath
|
|
||||||
static func create_new() -> SaveGame:
|
|
||||||
var save := SaveGame.new()
|
|
||||||
save.filepath = generate_save_path()
|
|
||||||
save.unique_save_name = save.filepath.get_file().get_basename()
|
|
||||||
save.is_valid = true
|
|
||||||
save.is_demo = OS.has_feature("Demo")
|
|
||||||
save.last_saved = int(Time.get_unix_time_from_system())
|
|
||||||
|
|
||||||
# Ensure save directory exists
|
|
||||||
if not DirAccess.dir_exists_absolute(save.filepath.get_base_dir()):
|
|
||||||
DirAccess.make_dir_absolute(save.filepath.get_base_dir())
|
|
||||||
|
|
||||||
print_debug("SaveGame: Created new save: %s" % save.filepath)
|
|
||||||
return save
|
|
||||||
|
|
||||||
## Creates a DEBUG save (not persisted to disk)
|
|
||||||
static func create_debug() -> SaveGame:
|
|
||||||
var save := SaveGame.new()
|
|
||||||
save.filepath = "DEBUG"
|
|
||||||
save.unique_save_name = "DEBUG"
|
|
||||||
save.is_valid = true
|
|
||||||
save.is_demo = OS.has_feature("Demo")
|
|
||||||
|
|
||||||
if OS.has_feature("debug") or OS.has_feature("demo"):
|
|
||||||
push_warning("Created DEBUG savegame. Progress will not be stored!")
|
|
||||||
else:
|
else:
|
||||||
push_error("Created DEBUG savegame outside of demo/debug environment. This will lead to data loss!")
|
filepath = initial_filepath
|
||||||
|
unique_save_name = initial_filepath.get_file()
|
||||||
|
|
||||||
return save
|
read_save_file()
|
||||||
|
_is_initialised = true
|
||||||
|
|
||||||
## Loads an existing save from disk
|
func read_save_file() -> void:
|
||||||
static func load_from_file(save_filepath: String) -> SaveGame:
|
if not DirAccess.dir_exists_absolute(filepath.get_base_dir()):
|
||||||
if not FileAccess.file_exists(save_filepath):
|
DirAccess.make_dir_absolute(filepath.get_base_dir())
|
||||||
push_error("SaveGame: File does not exist: %s" % save_filepath)
|
|
||||||
return null
|
|
||||||
|
|
||||||
print_debug("SaveGame: Loading from: %s" % save_filepath)
|
if filepath == "DEBUG":
|
||||||
|
if OS.has_feature("debug") or OS.has_feature("demo"):
|
||||||
var loaded: SaveGame = ResourceLoader.load(save_filepath, "", ResourceLoader.CACHE_MODE_IGNORE)
|
push_warning("Created DEBUG savegame. Progress will not be stored!")
|
||||||
|
else:
|
||||||
if not loaded:
|
print_debug(get_stack())
|
||||||
push_error("Failed to load SaveGame resource from: %s" % save_filepath)
|
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.")
|
||||||
return null
|
#TODO maybe cause a crash here?
|
||||||
|
|
||||||
# Update filepath metadata
|
|
||||||
loaded.filepath = save_filepath
|
|
||||||
loaded.unique_save_name = save_filepath.get_file().get_basename()
|
|
||||||
|
|
||||||
# Backwards compatibility
|
|
||||||
if "randoms" not in loaded.board_state:
|
|
||||||
loaded.board_state["randoms"] = []
|
|
||||||
|
|
||||||
# Load thumbnail separately (not stored in .tres)
|
|
||||||
_load_thumbnail(loaded)
|
|
||||||
|
|
||||||
# Validate
|
|
||||||
loaded.is_valid = loaded._validate()
|
|
||||||
|
|
||||||
if not loaded.is_valid:
|
|
||||||
push_error("Validation of loaded save failed: %s" % save_filepath)
|
|
||||||
return null
|
|
||||||
|
|
||||||
return loaded
|
|
||||||
|
|
||||||
## Helper to load thumbnail from separate PNG file
|
|
||||||
static func _load_thumbnail(save: SaveGame) -> void:
|
|
||||||
var thumbnail_path := "%s/thumbnails/%s.png" % [save.filepath.get_base_dir(), save.unique_save_name]
|
|
||||||
if FileAccess.file_exists(thumbnail_path):
|
|
||||||
var img := Image.load_from_file(thumbnail_path)
|
|
||||||
if img:
|
|
||||||
save.thumbnail = ImageTexture.create_from_image(img)
|
|
||||||
|
|
||||||
# === Instance Methods ===
|
|
||||||
|
|
||||||
## Captures current player position/rotation from State.player
|
|
||||||
func capture_player_state() -> void:
|
|
||||||
if not State.player:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
player_position = State.player.global_position
|
if FileAccess.file_exists(filepath):
|
||||||
var yaw: Node3D = State.player.get_node("Yaw")
|
print_debug("Savegame: Reading from: %s" % filepath)
|
||||||
var pitch: Node3D = yaw.get_node("Pitch")
|
var file := FileAccess.open(filepath, FileAccess.READ)
|
||||||
player_yaw = yaw.rotation.y
|
var raw_json := FileAccess.get_file_as_string(filepath)
|
||||||
player_pitch = pitch.rotation.x
|
file.close()
|
||||||
|
var parsed: Dictionary = JSON.parse_string(raw_json)
|
||||||
|
|
||||||
print_debug("SaveGame: Captured player state - pos: %s, yaw: %.2f, pitch: %.2f" % [player_position, player_yaw, player_pitch])
|
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:
|
||||||
|
print_debug("Savegame: Creating (in memory) for path: %s" % filepath)
|
||||||
|
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,
|
||||||
|
"is_childhood_board_complete": is_childhood_board_complete,
|
||||||
|
"last_saved": last_saved,
|
||||||
|
"is_demo": is_demo
|
||||||
|
}
|
||||||
|
|
||||||
## Saves to disk with thumbnail
|
|
||||||
func save_to_file(screen_shot: Texture) -> void:
|
func save_to_file(screen_shot: Texture) -> void:
|
||||||
|
print_debug("Savegame: Saving to file: %s" % filepath)
|
||||||
|
|
||||||
if filepath == "DEBUG":
|
if filepath == "DEBUG":
|
||||||
push_warning("SaveGame: DEBUG save skipped (intentional).")
|
push_warning("Savegame: Saving DEBUG save skipped. This is intentional.")
|
||||||
return
|
return
|
||||||
|
|
||||||
if current_room == State.rooms.NULL:
|
if current_room == State.rooms.NULL:
|
||||||
push_warning("SaveGame: Not saving empty savegame.")
|
push_warning("Savegame: Not saving empty savegame.")
|
||||||
return
|
return
|
||||||
|
|
||||||
print_debug("SaveGame: Saving to file: %s" % filepath)
|
|
||||||
|
|
||||||
# Capture current state
|
|
||||||
capture_player_state()
|
|
||||||
last_saved = int(Time.get_unix_time_from_system())
|
last_saved = int(Time.get_unix_time_from_system())
|
||||||
|
var thumbnail_image: Image = screen_shot.get_image()
|
||||||
|
thumbnail_image.convert(Image.Format.FORMAT_RGB8)
|
||||||
|
thumbnail_image.linear_to_srgb()
|
||||||
|
thumbnail_image.resize(384, 216, Image.INTERPOLATE_LANCZOS) # nonexistent call in ViewportTexture
|
||||||
|
thumbnail_image.crop(384, 216)
|
||||||
|
|
||||||
# Save thumbnail
|
|
||||||
_save_thumbnail(screen_shot)
|
|
||||||
|
|
||||||
# Save resource
|
|
||||||
var result := ResourceSaver.save(self, filepath)
|
|
||||||
if result != OK:
|
|
||||||
push_error("Failed to save resource to: %s (Error: %d)" % [filepath, result])
|
|
||||||
else:
|
|
||||||
print_debug("Successfully saved to: %s" % filepath)
|
|
||||||
|
|
||||||
## Processes and saves thumbnail as PNG
|
|
||||||
func _save_thumbnail(screen_shot: Texture) -> void:
|
|
||||||
var img: Image = screen_shot.get_image()
|
|
||||||
img.convert(Image.Format.FORMAT_RGB8)
|
|
||||||
img.linear_to_srgb()
|
|
||||||
img.resize(384, 216, Image.INTERPOLATE_LANCZOS)
|
|
||||||
img.crop(384, 216)
|
|
||||||
|
|
||||||
# Ensure thumbnails directory exists
|
|
||||||
var save_dir := DirAccess.open(filepath.get_base_dir())
|
var save_dir := DirAccess.open(filepath.get_base_dir())
|
||||||
if not save_dir.dir_exists("thumbnails"):
|
if not save_dir.dir_exists("thumbnails"):
|
||||||
save_dir.make_dir("thumbnails")
|
save_dir.make_dir("thumbnails")
|
||||||
|
|
||||||
var thumbnail_path := "%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]
|
||||||
img.save_png(thumbnail_path)
|
thumbnail_image.save_png(thumbnail_path)
|
||||||
|
print_debug(filepath.get_base_dir())
|
||||||
|
var file := FileAccess.open(filepath, FileAccess.WRITE)
|
||||||
|
file.store_string(JSON.stringify(_get_save_dict()))
|
||||||
|
file.close()
|
||||||
|
|
||||||
## Validates save data integrity
|
|
||||||
func _validate() -> bool:
|
|
||||||
if current_room < 0 or current_room >= State.rooms.keys().size():
|
|
||||||
return false
|
|
||||||
return validate_board_state()
|
|
||||||
|
|
||||||
# === Helper Methods ===
|
|
||||||
|
|
||||||
func calculate_completed_sequences() -> int:
|
func calculate_completed_sequences() -> int:
|
||||||
var i: int = mementos_complete - ((mementos_complete >> 1) & 0x55555555)
|
var i: int = mementos_complete - ((mementos_complete >> 1) & 0x55555555);
|
||||||
i = (i & 0x33333333) + ((i >> 2) & 0x33333333)
|
i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
|
||||||
i = (i + (i >> 4)) & 0x0F0F0F0F
|
i = (i + (i >> 4)) & 0x0F0F0F0F;
|
||||||
i *= 0x01010101
|
i *= 0x01010101;
|
||||||
return i >> 24
|
return i >> 24;
|
||||||
|
|
||||||
func calculate_total_connections() -> int:
|
func calculate_total_connections() -> int:
|
||||||
var connections := 0
|
var connections:= 0
|
||||||
|
|
||||||
for sticky_position in board_state.stickies.values():
|
for sticky_position in board_state.stickies.values():
|
||||||
connections += int(sticky_position is String)
|
connections += int(sticky_position is String)
|
||||||
|
|
||||||
return connections
|
return connections
|
||||||
|
|
||||||
func validate_board_state() -> bool:
|
func validate_board_state() -> bool:
|
||||||
if not board_state.has("cards") or not board_state.has("stickies"):
|
if board_state.keys().has("cards") and board_state.keys().has("stickies"):
|
||||||
return false
|
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():
|
||||||
|
if not (sticky is int or sticky is Vector2 or sticky is float or board_state.cards.keys().has(sticky)):
|
||||||
|
push_error("Save %s could not be parsed: Corrupted Sticky Notes.")
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
for card in board_state.cards.values():
|
func parse_vec_from_string(string: String) -> Vector2:
|
||||||
if not card is Vector2:
|
var string_array := string.replace("(", "").replace(")", "").split(", ")
|
||||||
push_error("Save %s: Corrupted cards" % unique_save_name)
|
return Vector2(float(string_array[0]), float(string_array[1]))
|
||||||
return false
|
|
||||||
|
|
||||||
for sticky in board_state.stickies.values():
|
|
||||||
if not (sticky is int or sticky is Vector2 or sticky is float or board_state.cards.has(sticky)):
|
|
||||||
push_error("Save %s: Corrupted sticky notes" % unique_save_name)
|
|
||||||
return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,6 @@ 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()
|
||||||
|
|
@ -73,6 +72,7 @@ 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,16 +128,6 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ func _ready() -> void:
|
||||||
|
|
||||||
func _new_game() -> void:
|
func _new_game() -> void:
|
||||||
print_debug("main_menu.gd: start_new_game()")
|
print_debug("main_menu.gd: start_new_game()")
|
||||||
State.save_game = SaveGame.create_new()
|
State.save_game = SaveGame.new()
|
||||||
_start_game()
|
_start_game()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(".tres"):
|
if path is String and path.ends_with(".json"):
|
||||||
var save := SaveGame.load_from_file("%s/%s" % [State.user_saves_path, path])
|
var save := SaveGame.new("%s/%s" % [State.user_saves_path, path.get_basename()])
|
||||||
# Skip invalid/empty saves
|
# HACK: Skip empty saves (we decide later what to do with them)
|
||||||
if save != null and not save.is_empty:
|
if not save.is_empty:
|
||||||
saves.append(save)
|
saves.append(save)
|
||||||
|
|
||||||
_sort_saves()
|
_sort_saves()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue