Add manual campaign checkpoint saves

This commit is contained in:
2026-06-18 04:21:55 +09:00
parent 87aed5a449
commit cf09306b8e
9 changed files with 340 additions and 95 deletions

View File

@@ -2,6 +2,7 @@ extends RefCounted
class_name CampaignState
const SAVE_PATH := "user://campaign_save.json"
const MANUAL_SAVE_PATH := "user://campaign_manual_save.json"
const SAVE_VERSION := 3
var save_version := SAVE_VERSION
@@ -151,38 +152,73 @@ func reset_save(scenario_id: String) -> bool:
func load_game() -> bool:
if not FileAccess.file_exists(SAVE_PATH):
return false
var parsed = JSON.parse_string(FileAccess.get_file_as_string(SAVE_PATH))
if typeof(parsed) != TYPE_DICTIONARY:
return false
save_version = int(parsed.get("save_version", 1))
current_scenario_id = str(parsed.get("current_scenario_id", ""))
completed_scenarios.clear()
for scenario_id in parsed.get("completed_scenarios", []):
completed_scenarios.append(str(scenario_id))
gold = int(parsed.get("gold", 0))
inventory = _copy_dictionary(parsed.get("inventory", {}))
roster = _copy_dictionary(parsed.get("roster", {}))
flags = _copy_dictionary(parsed.get("flags", {}))
applied_post_battle_choices = _copy_dictionary(parsed.get("applied_post_battle_choices", {}))
pending_post_battle_choice_scenario_id = str(parsed.get("pending_post_battle_choice_scenario_id", ""))
joined_officers.clear()
var saved_joined = parsed.get("joined_officers", initial_joined_officers)
for officer_id in saved_joined:
var normalized := str(officer_id)
if normalized.is_empty() or joined_officers.has(normalized):
continue
joined_officers.append(normalized)
return true
return _load_game_from_path(SAVE_PATH)
func save_game() -> bool:
var file := FileAccess.open(SAVE_PATH, FileAccess.WRITE)
return _save_game_to_path(SAVE_PATH)
func has_manual_save() -> bool:
return FileAccess.file_exists(MANUAL_SAVE_PATH)
func save_manual_game() -> bool:
return _save_game_to_path(MANUAL_SAVE_PATH)
func load_manual_game() -> bool:
if not has_manual_save():
return false
var previous_state := to_dict()
if not _load_game_from_path(MANUAL_SAVE_PATH):
return false
_clear_invalid_pending_post_battle_choice()
_normalize_current_scenario()
_backfill_applied_post_battle_choices()
_reconcile_completed_officer_transitions()
if save_game():
return true
_apply_save_data(previous_state)
return false
func get_manual_save_summary() -> Dictionary:
if not has_manual_save():
return {}
var parsed = JSON.parse_string(FileAccess.get_file_as_string(MANUAL_SAVE_PATH))
if typeof(parsed) != TYPE_DICTIONARY:
return {}
var completed_count := 0
var completed = parsed.get("completed_scenarios", [])
if typeof(completed) == TYPE_ARRAY:
completed_count = completed.size()
var current_id := str(parsed.get("current_scenario_id", ""))
return {
"save_version": int(parsed.get("save_version", 1)),
"current_scenario_id": current_id,
"current_scenario_title": get_scenario_title(current_id),
"completed_count": completed_count,
"total_count": scenario_order.size(),
"gold": int(parsed.get("gold", 0)),
"pending_choice": not str(parsed.get("pending_post_battle_choice_scenario_id", "")).is_empty()
}
func _load_game_from_path(path: String) -> bool:
if not FileAccess.file_exists(path):
return false
var parsed = JSON.parse_string(FileAccess.get_file_as_string(path))
if typeof(parsed) != TYPE_DICTIONARY:
return false
_apply_save_data(parsed)
return true
func _save_game_to_path(path: String) -> bool:
var file := FileAccess.open(path, FileAccess.WRITE)
if file == null:
push_error("Unable to save campaign state: %s" % SAVE_PATH)
push_error("Unable to save campaign state: %s" % path)
return false
file.store_string(JSON.stringify(to_dict()))
return true
@@ -202,8 +238,8 @@ func apply_battle_result(battle_state) -> Dictionary:
}
var already_completed := completed_scenarios.has(battle_state.battle_id)
var rewards := battle_state.get_rewards()
var post_battle_choices := battle_state.get_post_battle_choices()
var rewards: Dictionary = battle_state.get_rewards()
var post_battle_choices: Array = battle_state.get_post_battle_choices()
if already_completed:
var return_scenario_id := current_scenario_id if not is_campaign_complete() else ""
return {
@@ -678,7 +714,7 @@ func get_chapter_progress_entries() -> Array:
var scenarios := []
for scenario_id in scenario_ids:
var completed := completed_scenarios.has(scenario_id)
var current := scenario_id == current_scenario_id
var current: bool = scenario_id == current_scenario_id
if completed_scenarios.has(scenario_id):
completed_count += 1
if current:
@@ -763,6 +799,27 @@ func to_dict() -> Dictionary:
}
func _apply_save_data(parsed: Dictionary) -> void:
save_version = int(parsed.get("save_version", 1))
current_scenario_id = str(parsed.get("current_scenario_id", ""))
completed_scenarios.clear()
for scenario_id in parsed.get("completed_scenarios", []):
completed_scenarios.append(str(scenario_id))
gold = int(parsed.get("gold", 0))
inventory = _copy_dictionary(parsed.get("inventory", {}))
roster = _copy_dictionary(parsed.get("roster", {}))
flags = _copy_dictionary(parsed.get("flags", {}))
applied_post_battle_choices = _copy_dictionary(parsed.get("applied_post_battle_choices", {}))
pending_post_battle_choice_scenario_id = str(parsed.get("pending_post_battle_choice_scenario_id", ""))
joined_officers.clear()
var saved_joined = parsed.get("joined_officers", initial_joined_officers)
for officer_id in saved_joined:
var normalized := str(officer_id)
if normalized.is_empty() or joined_officers.has(normalized):
continue
joined_officers.append(normalized)
func _copy_dictionary(value) -> Dictionary:
if typeof(value) == TYPE_DICTIONARY:
return value.duplicate(true)