> For the complete documentation index, see [llms.txt](https://malbersanimations.gitbook.io/animal-controller/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://malbersanimations.gitbook.io/animal-controller/malbers-questforge/save-system/quest-dialogue-save-system.md).

# Quest/Dialogue Save System

### Overview

Quest Forge uses two complementary singleton components to persist game state: **Quest Save System** and **Dialogue Save System**. In their default configuration these are tightly integrated — a single JSON file on disk captures quest progress, dialogue history, location discoveries, active waypoints, fog-of-war exploration data, and the player's world position all in one atomic write.

**Quest Save System** (`QuestSaveSystem`) is the primary owner of the save file. It serialises the full state of `QuestManager`, collects sub-data from its sibling systems, writes it all to disk as human-readable JSON via JSON.NET, and reads it back on load.

**Dialogue Save System** (`DialogueSaveSystem`) tracks which `DialogueTrigger` conversations have been completed and stores the `DialogueManager`'s runtime variables (flags, strings, integers). It contributes its data to the quest save and restores from it — or, optionally, manages its own standalone file.

Both components call `DontDestroyOnLoad` and survive scene transitions.

***

<figure><img src="/files/YAxuLK4B0cmPxv4rzHr3" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/C8MiSjBKk9rdBxfFl0uv" alt=""><figcaption></figcaption></figure>

### Scene Setup

Place both components on a single persistent GameObject (e.g. your `_Managers` object). The recommended configuration is:

* **Quest Save System** + **Dialogue Save System** on the same GameObject, with `Integrate With Quest Saves` enabled on the Dialogue Save System.
* Both singletons auto-destroy duplicates in Awake.
* Quest Save System must have access to `QuestManager` — it calls `QuestManager.Instance` in Start.

***

## Quest Save System

### Inspector Properties

#### Save Settings

| Property               | Type   | Default        | Description                                                                                                                    |
| ---------------------- | ------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| **Save File Name**     | string | `"quest_save"` | Base name of the save file, without the `.json` extension.                                                                     |
| **Auto Save Enabled**  | bool   | true           | Enables both the state-change auto-save and the periodic timer save.                                                           |
| **Auto Save Interval** | float  | 300 s          | How often a periodic auto-save fires (in seconds). Set to 0 to disable periodic saves while keeping state-change saves active. |
| **Save On Quit**       | bool   | true           | Calls `SaveGame()` in `OnApplicationQuit`.                                                                                     |
| **Load On Start**      | bool   | true           | If a save file exists, calls `LoadGame()` in Start automatically.                                                              |
| **Debug Mode**         | bool   | false          | Enables verbose logging via `QuestLogger` at Debug level.                                                                      |

#### Save Location

| Property                     | Type   | Default | Description                                                                                                |
| ---------------------------- | ------ | ------- | ---------------------------------------------------------------------------------------------------------- |
| **Use Persistent Data Path** | bool   | true    | Writes to `Application.persistentDataPath/Saves/{saveFileName}.json`. Recommended for shipping.            |
| **Custom Save Path**         | string | `""`    | Directory used when Use Persistent Data Path is off. The `.json` filename is still appended automatically. |

***

### Auto-Save Triggers

Quest Save System monitors for changes every Update frame and saves immediately when any of the following occur:

| Trigger                     | What Changed                                                                                                        |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| **Quest state change**      | The count of active or completed quests differs from the last-saved counts.                                         |
| **Tracked quest change**    | The currently tracked quest ID changed (without a count change).                                                    |
| **Periodic timer**          | `autoSaveInterval` seconds have elapsed since the last save.                                                        |
| **Application quit**        | `OnApplicationQuit` fires and `saveOnQuit` is true.                                                                 |
| **Quest trigger activated** | A `DialogueTrigger` or scene quest trigger marks itself activated.                                                  |
| **Location discovered**     | A new location is marked discovered via `MarkLocationDiscovered()`.                                                 |
| **Dialogue completed**      | `DialogueSaveSystem.MarkDialogueTriggerCompleted()` calls `QuestSaveSystem.SaveGame()` when integration is enabled. |

After any state-change or trigger save, the periodic timer resets so the next periodic save fires a full interval later.

***

### What Gets Saved

Every call to `SaveGame()` captures a complete snapshot of the following:

| Section                    | Contents                                                                                                         |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| **Metadata**               | Save file name, timestamp, save version (`"1.0"`).                                                               |
| **Player position**        | World-space position and Y-axis (yaw) rotation. Written from `QuestManager.playerTransform`.                     |
| **Active quests**          | Full `QuestInstanceSaveData` for every quest currently in progress.                                              |
| **Completed quests**       | Full `QuestInstanceSaveData` for every finished quest.                                                           |
| **Failed quests**          | Full `QuestInstanceSaveData` for every failed quest.                                                             |
| **Abandoned quests**       | Full `QuestInstanceSaveData` for every abandoned quest.                                                          |
| **Available quest IDs**    | String list of quest IDs that are currently available to the player.                                             |
| **Dialogue data**          | Completed trigger IDs, trigger metadata, and all `DialogueManager` runtime variables (flags, strings, integers). |
| **Quest trigger data**     | IDs of scene quest triggers that have been activated and destroyed/deactivated.                                  |
| **Location discoveries**   | `HashSet` of discovered location IDs and per-location metadata.                                                  |
| **World map waypoints**    | Name, world position, colour (RGBA hex), and icon index for every custom waypoint.                               |
| **Fog-of-war breadcrumbs** | List of world positions recorded as the player explored.                                                         |

***

### Load Sequence

When `LoadGame()` runs, the following steps execute in order:

1. **Deserialise** — JSON file is read and converted to `QuestSaveData`.
2. **Version check** — If `saveVersion != "1.0"`, `MigrateSaveData()` is attempted. If migration fails, loading is aborted.
3. **Clear quests** — All four quest dictionaries in `QuestManager` are emptied via reflection.
4. **Restore quests** — Each `QuestInstanceSaveData` is converted back to a live `QuestInstance` and injected into the correct dictionary. `UpdateObjectiveStatus()` is called on each instance.
5. **Refresh available quests** — `questManager.ForceRefreshAvailableQuests()`.
6. **Restore dialogue** — `DialogueSaveSystem.RestoreSaveData()` repopulates completed triggers and restores dialogue variables to `DialogueManager`.
7. **Restore quest triggers** — The `activatedQuestTriggers` HashSet and metadata dictionary are repopulated.
8. **Restore locations** — `LocationManager.LoadFromSaveData()` syncs all discovered location IDs.
9. **Restore waypoints** — `POIManager.ClearAllWaypoints()`, then each `WaypointSaveEntry` is re-created via `POIManager.CreateWaypoint()`. Colour is parsed from the hex string; icon sprite is resolved from `WaypointPickerUI.GetIconByIndex()`.
10. **Restore fog of war** — `WorldMapFogOfWar.LoadBreadcrumbs()` if breadcrumbs are present.
11. **Restore player position** *(deferred one frame)* — A coroutine waits for `WaitForEndOfFrame` to ensure physics and character controllers are initialised before the teleport.
12. **Notify listeners** *(deferred one frame)* — A second coroutine publishes `QuestProgressUpdated` for all active quests and `QuestCompleted` for all completed quests on `QuestEventBus`, allowing POI Markers, UI, and other subscribers to re-evaluate their state.

***

### Save File Format

Files are plain UTF-8 JSON, indented, with `DateTimeFormat = "yyyy-MM-ddTHH:mm:ss"`. Vector3 values are serialised by a custom `Vector3Converter`. The files are human-readable and can be inspected or edited directly in any text editor.

Default path (Windows): `C:\Users\{User}\AppData\LocalLow\{Company}\{Product}\Saves\quest_save.json`

***

### Public API

#### Core Save / Load

| Method                             | Returns | Description                                                                              |
| ---------------------------------- | ------- | ---------------------------------------------------------------------------------------- |
| `SaveGame(string slotName = null)` | bool    | Saves to the named slot, or the default `saveFileName` if null. Returns true on success. |
| `LoadGame(string slotName = null)` | bool    | Loads from the named slot. Returns true on success.                                      |
| `QuickSave()`                      | void    | Saves to the hardcoded `"quicksave"` slot.                                               |
| `QuickLoad()`                      | void    | Loads from the `"quicksave"` slot.                                                       |
| `CreateNamedSave(string saveName)` | bool    | Alias for `SaveGame(saveName)`.                                                          |

#### Save File Management

| Method                                                    | Returns       | Description                                                                                                                                              |
| --------------------------------------------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SaveFileExists(string slotName = null)`                  | bool          | Returns true if the named (or default) save file is present on disk.                                                                                     |
| `DeleteSave(string slotName = null)`                      | bool          | Deletes the save file. Returns true if deletion succeeded.                                                                                               |
| `GetSaveInfo(string slotName = null)`                     | QuestSaveData | Deserialises the save file and returns the data object **without** applying it to the game state. Use for save-slot UI (timestamps, quest counts, etc.). |
| `GetAvailableSaveSlots()`                                 | List\<string> | Scans the saves directory and returns all slot names (file names without `.json`).                                                                       |
| `ExportSaveAsJson(string slotName = null)`                | string        | Returns the raw JSON content of the save file as a string.                                                                                               |
| `ImportSaveFromJson(string json, string slotName = null)` | bool          | Validates the JSON, writes it to disk, and returns true on success. Does **not** automatically load — call `LoadGame()` afterwards.                      |

#### Quest Trigger API

Scene objects that represent one-time quest triggers call these methods to persist their activated state.

| Method                                                                                                     | Returns              | Description                                                                                                                               |
| ---------------------------------------------------------------------------------------------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `MarkQuestTriggerActivated(triggerId, questName, actionPerformed, position, wasDestroyed, wasDeactivated)` | void                 | Records the trigger as used. Creates rich metadata on first call; increments `timesActivated` on subsequent calls. Triggers an auto-save. |
| `IsQuestTriggerActivated(string triggerId)`                                                                | bool                 | Returns true if the trigger has been recorded as used.                                                                                    |
| `GetQuestTriggerMetadata(string triggerId)`                                                                | QuestTriggerMetadata | Returns the stored metadata, or null if the trigger has never been activated.                                                             |
| `GetActivatedQuestTriggerCount()`                                                                          | int                  | Total count of recorded activated triggers.                                                                                               |
| `ResetQuestTrigger(string triggerId)`                                                                      | void                 | Removes the trigger from the activated set, allowing it to fire again.                                                                    |
| `ResetAllQuestTriggers()`                                                                                  | void                 | Clears all activated trigger data.                                                                                                        |

#### Location Discovery API

| Method                                                   | Returns                   | Description                                                                            |
| -------------------------------------------------------- | ------------------------- | -------------------------------------------------------------------------------------- |
| `GetLocationDiscoveryData()`                             | LocationDiscoverySaveData | Returns the live discovery data object (creates empty if none exists).                 |
| `MarkLocationDiscovered(LocationDiscoverySaveData data)` | void                      | Replaces the cached discovery data with the updated version and triggers an auto-save. |
| `IsLocationDiscovered(string locationId)`                | bool                      | Returns true if the location ID is in the discovered set.                              |
| `GetDiscoveredLocationCount()`                           | int                       | Total number of discovered locations.                                                  |
| `ResetAllLocationDiscoveries()`                          | void                      | Clears discovery data and calls `LocationManager.ResetAllDiscoveries()`.               |

***

## Dialogue Save System

### Inspector Properties

| Property                       | Type | Default | Description                                                                                                                                                                                                     |
| ------------------------------ | ---- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Integrate With Quest Saves** | bool | true    | When enabled, dialogue state is bundled into the quest save file. Calling `MarkDialogueTriggerCompleted()` automatically triggers `QuestSaveSystem.SaveGame()`. Recommended — keeps a single unified save file. |
| **Debug Mode**                 | bool | false   | Enables verbose logging at Debug level.                                                                                                                                                                         |

***

### Integration Modes

**Integrated (default)** — `integrateWithQuestSaves = true`:

* Dialogue data lives inside `QuestSaveData.dialogueData`.
* `QuestSaveSystem` calls `DialogueSaveSystem.CreateSaveData()` during every save and `RestoreSaveData()` during every load.
* No separate file is ever written by `DialogueSaveSystem`.
* Any call to `MarkDialogueTriggerCompleted()` triggers `QuestSaveSystem.SaveGame()` immediately.

**Standalone** — `integrateWithQuestSaves = false`:

* Use `SaveToFile(path)` and `LoadFromFile(path)` directly on `DialogueSaveSystem`.
* Useful for projects that have a separate dialogue system with its own save pipeline.

***

### Runtime State

| Internal Collection         | Type                                          | Contents                                                                               |
| --------------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------- |
| `completedDialogueTriggers` | `HashSet<string>`                             | IDs of all triggers that have fired. O(1) lookup.                                      |
| `triggerMetadata`           | `Dictionary<string, DialogueTriggerMetadata>` | Rich metadata for each trigger: dialogue name, fire count, timestamps, world position. |

***

### Public API

#### Trigger State

| Method                                                                   | Returns                 | Description                                                                                                                                     |
| ------------------------------------------------------------------------ | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `MarkDialogueTriggerCompleted(triggerId, dialogueName, triggerPosition)` | void                    | Adds the ID to the completed set and creates or updates metadata. If integration is enabled, triggers `QuestSaveSystem.SaveGame()` immediately. |
| `IsDialogueTriggerCompleted(string triggerId)`                           | bool                    | `HashSet.Contains()` — O(1) lookup.                                                                                                             |
| `ResetDialogueTrigger(string triggerId)`                                 | void                    | Removes the ID, allowing the trigger to fire again. Does not remove metadata.                                                                   |
| `ResetAllDialogueTriggers()`                                             | void                    | Clears both the completed set and all metadata.                                                                                                 |
| `GetTriggerMetadata(string triggerId)`                                   | DialogueTriggerMetadata | Returns the metadata object, or null if the trigger has never fired.                                                                            |
| `GetCompletedDialogueCount()`                                            | int                     | Count of unique completed trigger IDs.                                                                                                          |
| `GetCompletedDialogueTriggers()`                                         | List\<string>           | A copy of all completed trigger IDs. Safe to iterate without modification risk.                                                                 |

#### Save / Load (Standalone Mode)

| Method                                       | Returns          | Description                                                                                                                                                                                                                 |
| -------------------------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `CreateSaveData()`                           | DialogueSaveData | Packages current runtime state — completed triggers, metadata, and all `DialogueManager` variables — into a serialisable object. Called by `QuestSaveSystem` internally during integrated saves.                            |
| `RestoreSaveData(DialogueSaveData saveData)` | void             | Clears runtime state and restores from the provided data object. Also calls `DialogueManager.RestoreVariables()` to reinstall flags, strings, and integers. Called by `QuestSaveSystem` internally during integrated loads. |
| `SaveToFile(string savePath)`                | bool             | Serialises and writes a standalone dialogue save file. Returns true on success.                                                                                                                                             |
| `LoadFromFile(string savePath)`              | bool             | Reads and deserialises a standalone dialogue save file. Returns true on success.                                                                                                                                            |

***

## Data Structures Reference

### `QuestSaveData` — Top-Level Container

The root object serialised to the `.json` file.

| Field                   | Type                         | Description                                                           |
| ----------------------- | ---------------------------- | --------------------------------------------------------------------- |
| `saveVersion`           | string                       | Always `"1.0"`. Triggers migration if mismatched on load.             |
| `saveName`              | string                       | The slot name used when saving.                                       |
| `saveTime`              | DateTime                     | ISO-formatted timestamp of the save.                                  |
| `playerName`            | string                       | Reserved for future use.                                              |
| `totalPlayTime`         | int                          | Reserved for future use (seconds).                                    |
| `playerPosition`        | Vector3                      | World position at save time.                                          |
| `playerYRotation`       | float                        | Y-axis (yaw) rotation in degrees.                                     |
| `hasPlayerPosition`     | bool                         | False for saves predating position tracking — skips teleport on load. |
| `activeQuests`          | List\<QuestInstanceSaveData> | All in-progress quests.                                               |
| `completedQuests`       | List\<QuestInstanceSaveData> | All finished quests.                                                  |
| `failedQuests`          | List\<QuestInstanceSaveData> | All failed quests.                                                    |
| `abandonedQuests`       | List\<QuestInstanceSaveData> | All abandoned quests.                                                 |
| `availableQuestIds`     | List\<string>                | Quest IDs currently available to accept.                              |
| `dialogueData`          | DialogueSaveData             | Dialogue trigger state and runtime variables.                         |
| `questTriggerData`      | QuestTriggerSaveData         | Scene trigger activation history.                                     |
| `locationDiscoveryData` | LocationDiscoverySaveData    | Discovered location IDs and metadata.                                 |
| `worldMapData`          | WorldMapSaveData             | Custom waypoints and fog-of-war breadcrumbs.                          |

Helper methods: `GetTotalQuestCount()`, `GetFormattedSaveTime()`.

***

### `QuestInstanceSaveData` — Per-Quest State

| Field            | Type                  | Description                                                               |
| ---------------- | --------------------- | ------------------------------------------------------------------------- |
| `questId`        | string                | The `Quest.QuestID` used to look up the `Quest` ScriptableObject on load. |
| `questName`      | string                | Stored for readability in the JSON file; not used for lookup.             |
| `status`         | QuestStatus           | Active / Completed / Failed / Abandoned.                                  |
| `startTime`      | DateTime              | When the quest was accepted.                                              |
| `completeTime`   | DateTime?             | When the quest was completed (nullable).                                  |
| `isTracked`      | bool                  | Whether the player had this quest tracked in the HUD.                     |
| `timesCompleted` | int                   | Supports repeatable quests.                                               |
| `progress`       | QuestProgressSaveData | Full copy of all objective counters and flags.                            |

***

### `QuestProgressSaveData` — Objective Counters

All seven progress dictionaries are deep-copied on save and restored on load via direct calls to `QuestProgress` setter methods.

| Field                | Key → Value              |
| -------------------- | ------------------------ |
| `killCounts`         | EnemyType → count        |
| `itemCounts`         | ItemId → count           |
| `flags`              | FlagId → bool            |
| `locations`          | LocationId → Vector3     |
| `stringValues`       | Key → string             |
| `interactionCounts`  | ObjectId → count         |
| `uniqueInteractions` | ObjectId → List\<string> |

***

### `DialogueSaveData` — Dialogue State

| Field                       | Type                                   | Description                                   |
| --------------------------- | -------------------------------------- | --------------------------------------------- |
| `completedDialogueTriggers` | List\<string>                          | All trigger IDs that have been fired.         |
| `triggerMetadata`           | Dict\<string, DialogueTriggerMetadata> | Per-trigger rich data.                        |
| `saveTime`                  | DateTime                               | Timestamp of the last save.                   |
| `dialogueFlags`             | Dict\<string, bool>                    | All boolean variables from `DialogueManager`. |
| `dialogueStrings`           | Dict\<string, string>                  | All string variables from `DialogueManager`.  |
| `dialogueIntegers`          | Dict\<string, int>                     | All integer variables from `DialogueManager`. |

#### `DialogueTriggerMetadata`

| Field              | Description                                                    |
| ------------------ | -------------------------------------------------------------- |
| `triggerId`        | The trigger's unique ID.                                       |
| `dialogueName`     | Name of the dialogue asset that was played.                    |
| `timesTriggered`   | How many times this trigger has fired (for non-once triggers). |
| `firstTriggerTime` | DateTime of the first activation.                              |
| `lastTriggerTime`  | DateTime of the most recent activation.                        |
| `triggerPosition`  | World position of the trigger object.                          |

***

### `QuestTriggerSaveData` — Scene Trigger History

| Field               | Description                                                                                     |
| ------------------- | ----------------------------------------------------------------------------------------------- |
| `activatedTriggers` | List of trigger IDs that have fired and should remain spent.                                    |
| `triggerMetadata`   | Per-trigger rich data (quest name, action, timestamps, position, wasDestroyed, wasDeactivated). |

***

### `LocationDiscoverySaveData` — Location History

| Field                                      | Description                                                                                                                 |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- |
| `discoveredLocationIds`                    | `HashSet<string>` of all discovered location IDs.                                                                           |
| `locationMetadata`                         | Per-location rich data: name, player position at discovery, timestamp, whether quest-related, and which quest triggered it. |
| `totalLocationsDiscovered`                 | Running count.                                                                                                              |
| `firstDiscoveryTime` / `lastDiscoveryTime` | Optional aggregate timestamps.                                                                                              |

***

### `WorldMapSaveData` — Map State

| Field                    | Description                                                       |
| ------------------------ | ----------------------------------------------------------------- |
| `mapWaypoints`           | List of `WaypointSaveEntry` — one per custom waypoint.            |
| `explorationBreadcrumbs` | List of `Vector3` world positions recorded for fog-of-war reveal. |
| `lastZoom`               | Last map zoom level (only applied if `saveMapViewState = true`).  |
| `lastPanOffset`          | Last map pan offset (only applied if `saveMapViewState = true`).  |

#### `WaypointSaveEntry`

| Field         | Description                                                                                                                            |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `name`        | Display name of the waypoint.                                                                                                          |
| `position`    | World-space position.                                                                                                                  |
| `createdTime` | When the player placed it.                                                                                                             |
| `colorHex`    | RGBA hex string without `#` (e.g. `"FFFF00FF"`). Parsed with `ColorUtility.TryParseHtmlString()` on load. Empty = use yellow fallback. |
| `iconIndex`   | Zero-based index into `WaypointPickerUI.availableIcons`. `-1` = use `POIManager` default icon.                                         |

***

### Other Tips

* **JSON.NET is required** — Both save systems use `Newtonsoft.Json`. Ensure the JSON.NET for Unity package is present in the project. Missing it will cause `JsonConvert` type errors at compile time.
* **Quest definitions must exist on load** — `RestoreQuestInstance()` looks up the `Quest` ScriptableObject by ID via `QuestManager.GetQuestDefinition()`. If you rename or delete a `Quest` asset between saves, that quest is silently skipped. Keep a log of removed quest IDs if players may have saves containing them.
* **Save version migration is stubbed** — The current migration framework logs a warning and returns `false` for any version other than `"1.0"`, aborting the load. Add version-specific migration blocks inside `MigrateSaveData()` before shipping any format-breaking update.
* **Player position is deferred** — The teleport happens at end-of-frame, not during `LoadGame()`. If your game has any first-frame logic that reads the player position, it may see the pre-load position. Defer your own post-load logic with a short `yield return null` if needed.
* **Quest events are deferred** — POI Markers and UI components that subscribe to `QuestEventBus` in `Start()` will not receive events published during `LoadGame()` because their `Start()` may not have run yet. The save system handles this by firing events at end-of-frame. Custom listeners should follow the same pattern.
* **Reflection for QuestManager access** — `RestoreQuestInstance()` and `ClearAllQuests()` use `System.Reflection` to write into `QuestManager`'s private dictionaries. This is necessary because QuestManager encapsulates those collections. If `QuestManager`'s private field names are ever refactored, update the string literals in these methods accordingly.
* **`GetSaveInfo()` for UI** — Use this method to preview a save slot in a save/load menu without actually loading the game state. It deserialises the full `QuestSaveData` object, which includes timestamps, quest counts, and player name, without touching `QuestManager`.
* **Fog-of-war breadcrumbs can grow large** — Every exploration step is stored as a `Vector3`. For large open worlds, the breadcrumb list can grow to thousands of entries. Consider periodically pruning it via `WorldMapFogOfWar` settings.
