diff --git a/AGRARIAN_DEVELOPMENT_ROADMAP.md b/AGRARIAN_DEVELOPMENT_ROADMAP.md index 6c98ddf..1382e37 100644 --- a/AGRARIAN_DEVELOPMENT_ROADMAP.md +++ b/AGRARIAN_DEVELOPMENT_ROADMAP.md @@ -737,7 +737,10 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe backwards-compatible `PlayerId` plus `FAgrarianSavedPlayerIdentity`, prefer a valid `APlayerState` network ID when available, and retain safe display/pawn metadata without storing credentials. -- [ ] Save player stats. +- [x] Save player stats. Player save records capture + `FAgrarianSavedPlayer::Survival`, including health, stamina, exhaustion, + hunger, thirst, body temperature, injury, bleeding, sprain, sickness, death + state, and death reason, then restore through `ApplySavedState`. - [ ] Save long-term character care history placeholders without applying aging gameplay yet. - [ ] Save player inventory. - [x] Save placed structures. diff --git a/Docs/PersistenceDesignDocument.md b/Docs/PersistenceDesignDocument.md index a5b31aa..6a79c1d 100644 --- a/Docs/PersistenceDesignDocument.md +++ b/Docs/PersistenceDesignDocument.md @@ -409,6 +409,13 @@ identity block also records the display player name, raw network ID, whether the network ID was used, and the last known pawn name. It deliberately does not store passwords, tokens, emails, or platform credentials. +Player stats are stored through `FAgrarianSavedPlayer::Survival`, which captures +the replicated `FAgrarianSurvivalSnapshot`. The MVP stat save includes health, +stamina, exhaustion, hunger, thirst, body temperature, generic injury, +bleeding, sprain, sickness, death state, and death reason. Loading applies the +snapshot through `UAgrarianSurvivalComponent::ApplySavedState`, keeping clamping +and replicated change notifications inside the survival component. + ## Testing Gates Minimum persistence smoke test: diff --git a/Scripts/verify_player_stats_persistence.py b/Scripts/verify_player_stats_persistence.py new file mode 100644 index 0000000..93ac48d --- /dev/null +++ b/Scripts/verify_player_stats_persistence.py @@ -0,0 +1,58 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + +EXPECTED = { + ROOT / "Source" / "AgrarianGame" / "AgrarianTypes.h": [ + "struct FAgrarianSurvivalSnapshot", + "float Health = 100.0f;", + "float Stamina = 100.0f;", + "float Exhaustion = 0.0f;", + "float Hunger = 100.0f;", + "float Thirst = 100.0f;", + "float BodyTemperature = 37.0f;", + "float InjurySeverity = 0.0f;", + "float BleedingSeverity = 0.0f;", + "float SprainSeverity = 0.0f;", + "float SicknessSeverity = 0.0f;", + "bool bIsDead = false;", + "FName LastDeathReason = NAME_None;", + ], + ROOT / "Source" / "AgrarianGame" / "AgrarianSaveGame.h": [ + "FAgrarianSurvivalSnapshot Survival;", + ], + ROOT / "Source" / "AgrarianGame" / "AgrarianPersistenceSubsystem.cpp": [ + "SavedPlayer.Survival = SurvivalComponent->Survival;", + "SurvivalComponent->ApplySavedState(SavedPlayer->Survival, SavedPlayer->CareHistory);", + ], + ROOT / "Docs" / "PersistenceDesignDocument.md": [ + "`FAgrarianSavedPlayer::Survival`", + "The MVP stat save includes health", + "bleeding, sprain, sickness, death state, and death reason", + "`UAgrarianSurvivalComponent::ApplySavedState`", + ], + ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md": [ + "[x] Save player stats.", + "`FAgrarianSavedPlayer::Survival`", + "state, and death reason", + ], +} + + +def main() -> None: + missing = [] + for path, snippets in EXPECTED.items(): + text = path.read_text(encoding="utf-8") + for snippet in snippets: + if snippet not in text: + missing.append(f"{path.relative_to(ROOT)}: {snippet}") + + if missing: + raise RuntimeError("Player stats persistence verification failed: " + "; ".join(missing)) + + print("PASS: player stats persist through the saved survival snapshot.") + + +if __name__ == "__main__": + main()