132 lines
3.5 KiB
GDScript
132 lines
3.5 KiB
GDScript
extends Node
|
|
## SaveManager — autoload singleton
|
|
## Sauvegarde automatique (toutes les 30s + à la mort).
|
|
## Persiste : XP, niveau, inventaire, achievements débloqués, position spawn.
|
|
|
|
const SAVE_PATH := "user://save.json"
|
|
const AUTOSAVE_INTERVAL: float = 30.0
|
|
|
|
var _autosave_timer: float = 0.0
|
|
var _player: Node = null
|
|
|
|
|
|
func _ready() -> void:
|
|
# Connect mort du joueur dès que le nœud player existe
|
|
# (on le cherche au premier process car le joueur est spawné après)
|
|
pass
|
|
|
|
|
|
func register_player(player: Node) -> void:
|
|
_player = player
|
|
if player.has_signal("player_died"):
|
|
if not player.player_died.is_connected(_on_player_died):
|
|
player.player_died.connect(_on_player_died)
|
|
|
|
|
|
func _process(delta: float) -> void:
|
|
_autosave_timer += delta
|
|
if _autosave_timer >= AUTOSAVE_INTERVAL:
|
|
_autosave_timer = 0.0
|
|
save_game()
|
|
|
|
|
|
func save_game() -> void:
|
|
var data: Dictionary = {}
|
|
|
|
# --- PlayerProgress ---
|
|
var pp: Node = get_node_or_null("/root/PlayerProgress")
|
|
if pp != null:
|
|
data["xp"] = pp.current_xp
|
|
data["level"] = pp.level
|
|
|
|
# --- Inventory ---
|
|
var main: Node = get_tree().get_first_node_in_group("main")
|
|
if main != null and main.get("inventory") != null:
|
|
var inv: Node = main.inventory
|
|
var slots: Array = []
|
|
if inv.has_method("get_all_slots"):
|
|
slots = inv.call("get_all_slots")
|
|
else:
|
|
# Fallback: read slots array directly if exposed
|
|
if "slots" in inv:
|
|
for s in inv.slots:
|
|
slots.append(s)
|
|
data["inventory"] = slots
|
|
|
|
# --- Achievements ---
|
|
var am: Node = get_node_or_null("/root/AchievementManager")
|
|
if am != null and am.get("_unlocked") != null:
|
|
data["achievements"] = am._unlocked
|
|
|
|
# --- Spawn position ---
|
|
if is_instance_valid(_player):
|
|
var pos: Vector3 = _player.global_position
|
|
data["spawn"] = {"x": pos.x, "y": pos.y, "z": pos.z}
|
|
|
|
# Write JSON
|
|
var file := FileAccess.open(SAVE_PATH, FileAccess.WRITE)
|
|
if file == null:
|
|
push_warning("SaveManager: impossible d'ouvrir " + SAVE_PATH)
|
|
return
|
|
file.store_string(JSON.stringify(data, "\t"))
|
|
file.close()
|
|
|
|
|
|
func load_game() -> bool:
|
|
if not FileAccess.file_exists(SAVE_PATH):
|
|
return false
|
|
|
|
var file := FileAccess.open(SAVE_PATH, FileAccess.READ)
|
|
if file == null:
|
|
return false
|
|
var raw: String = file.get_as_text()
|
|
file.close()
|
|
|
|
var parsed = JSON.parse_string(raw)
|
|
if parsed == null or not (parsed is Dictionary):
|
|
push_warning("SaveManager: save corrompu")
|
|
return false
|
|
|
|
var data: Dictionary = parsed
|
|
|
|
# --- PlayerProgress ---
|
|
var pp: Node = get_node_or_null("/root/PlayerProgress")
|
|
if pp != null:
|
|
if data.has("xp"):
|
|
pp.current_xp = int(data["xp"])
|
|
if data.has("level"):
|
|
pp.level = int(data["level"])
|
|
pp.emit_state()
|
|
|
|
# --- Achievements ---
|
|
var am: Node = get_node_or_null("/root/AchievementManager")
|
|
if am != null and data.has("achievements"):
|
|
am._unlocked = data["achievements"]
|
|
|
|
# --- Inventory ---
|
|
var main: Node = get_tree().get_first_node_in_group("main")
|
|
if main != null and main.get("inventory") != null and data.has("inventory"):
|
|
var inv: Node = main.inventory
|
|
var slots: Array = data["inventory"]
|
|
if inv.has_method("load_slots"):
|
|
inv.call("load_slots", slots)
|
|
elif "slots" in inv:
|
|
# Best-effort direct assignment
|
|
for i: int in range(min(slots.size(), inv.slots.size())):
|
|
inv.slots[i] = slots[i]
|
|
|
|
# --- Spawn position ---
|
|
if is_instance_valid(_player) and data.has("spawn"):
|
|
var sp: Dictionary = data["spawn"]
|
|
_player.global_position = Vector3(
|
|
float(sp.get("x", 0.0)),
|
|
float(sp.get("y", 55.0)),
|
|
float(sp.get("z", 0.0))
|
|
)
|
|
|
|
return true
|
|
|
|
|
|
func _on_player_died() -> void:
|
|
save_game()
|