From ede235ec7ad482d353bf52ffbe015da965a17246 Mon Sep 17 00:00:00 2001 From: Dan Crosby Date: Mon, 15 Jun 2026 11:04:55 -0700 Subject: [PATCH] Document current tutorial flow (#7264) --- docs/TUTORIAL_BATTLE_SYSTEM.md | 364 ++++++++++++--------------------- docs/TUTORIAL_CONTENT.md | 26 +-- docs/TUTORIAL_SYSTEM.md | 27 ++- 3 files changed, 159 insertions(+), 258 deletions(-) diff --git a/docs/TUTORIAL_BATTLE_SYSTEM.md b/docs/TUTORIAL_BATTLE_SYSTEM.md index e7b731f746..e851e636ab 100644 --- a/docs/TUTORIAL_BATTLE_SYSTEM.md +++ b/docs/TUTORIAL_BATTLE_SYSTEM.md @@ -1,267 +1,163 @@ # Tutorial Battle System -This document describes the tutorial battle system for first-time players. The system provides a scripted introductory battle that teaches combat basics while telling a story. +This document describes the current opening tutorial battle. It is based on the live implementation in `TutorialGameCreation.scala`, `TutorialBattleController.cpp`, `ShardokGamesManager.cpp`, `ResolveBattleAction.scala`, and the Unity dialogue scripts. + +For the Unity tutorial UI/dialogue architecture, see `TUTORIAL_SYSTEM.md`. For copy and content notes, see `TUTORIAL_CONTENT.md`. --- -## Overview +## Current Flow -When a new player starts their first game in tutorial mode, they experience: +When a player creates a tutorial game at the default `OpeningBattle` phase: -1. **Narrative Intro** - Story screens introducing the scenario -2. **Immediate Battle** - Skip strategic map, start directly in combat -3. **Scripted Flee** - Enemy flees when player is "on the ropes" -4. **Reinforcements** - Allied heroes arrive mid-battle -5. **Heroes Join** - Reinforcement heroes join player's faction after battle +1. `TutorialGameCreation.createTutorialGame()` creates a `GameType.Tutorial(TutorialPhase.OpeningBattle)` game starting in `RoundPhase.BattleRequest`. +2. Province 14, **Onmaa**, belongs to Sadar Rakon's faction and already has a defending army. +3. Bregos Fyar's faction has a hostile attacking army, led by Ikhaan Tarn, already moving from province 31 to Onmaa. +4. `RequestBattlesAction` immediately creates the Shardok battle and tags tutorial reinforcement hero IDs `100` and `101`. +5. `GamesManager` stores the generated `TutorialBattleConfig` for this game and passes it to Shardok when the battle starts. +6. Shardok creates the battle with the configured timed reinforcement events. +7. Unity plays the active dialogue scripts from `tutorial_strategic.json` and `tutorial_battle.json`. + +There is also a tutorial phase-start path. Starting after `OpeningBattle` applies precomputed tutorial setup results from `TutorialPhaseResultsLoader`; this is why the Unity post-battle trigger logic handles a "battle was never observed" auto-resolve path. --- -## Battle Configuration +## Battle Setup -### Defender (Player - John Ranil) -- **Heroes**: John Ranil + 2 random vassals (3 total) -- **Units**: - - 2x Light Infantry (300 troops each, 60 training/armament) - - 1x Longbowmen (200 troops, 60 training/armament) -- **Province**: 14 (Onmaa) with 40 support, 500 gold, 3000 food -- **Restriction**: Cannot flee (must defend) +### Defender: Sadar Rakon's Faction -### Attacker (Ikhaan Tarn) -- **Heroes**: Ikhaan Tarn + 2 sworn brothers (3 total) -- **Units**: - - 1x Heavy Cavalry (600 troops, 80 training/armament) - - 1x Heavy Infantry (500 troops, 80 training/armament) - - 1x Longbowmen (300 troops, 80 training/armament) -- **Origin Province**: 31 +Configured in `src/main/resources/net/eagle0/eagle/tutorial_parameters.json`: -### Flee Trigger -Attacker flees when ANY of these conditions are met: -- Player loses 1 unit -- 5 rounds have passed +- **Faction ID:** 3 +- **Province:** 14, Onmaa +- **Starting support:** 23 +- **Starting resources:** 50 gold, 4000 food +- **Heroes:** Sadar Rakon, Old Marek the Learned, Agamemnon +- **Battalions:** + - Rakon's Loyalists: Light Infantry, 529 size, 80 training, 75 armament + - Onmaa Defenders: Light Infantry, 311 size, 80 training, 60 armament + - Hunters of the Steppe: Longbowmen, 478 size, 80 training, 60 armament -### Reinforcements -When Tarn flees, these heroes arrive as player reinforcements: -- Elena Fyar -- Hedrick -- The Boulder +### Attacker: Bregos Fyar's Faction + +The opening attacking army is configured under Bregos Fyar's `attackingArmies`: + +- **Faction ID:** 2 +- **Origin province:** 31 +- **Destination province:** 14, Onmaa +- **Heroes:** Ikhaan Tarn, Tall Edgtheow, Waylaid Julius, Luke the Prank-tricker +- **Battalions:** + - Doomriders: Heavy Cavalry, 592 size, 80 training, 80 armament + - The Shardok's Guard: Heavy Infantry, 507 size, 80 training, 80 armament + - Bowmen of Nikemi: Longbowmen, 291 size, 80 training, 80 armament + - Swift Sabres: Light Cavalry, 450 size, 80 training, 80 armament + +Tarn's unit is made visible to the defender from the start through `TutorialBattleConfig.initial_visibilities`. --- -## Implementation Architecture +## Reinforcements -### Phase 1: Narrative Intro System +The current tutorial config has two timed reinforcement events. It does not currently configure a scripted Tarn flee event. -**Proto Changes:** -- `game_state_view.proto`: Added `TutorialNarrativeScreen` message and `pending_narrative_screens` field -- `game_parameters.proto`: Added `TutorialNarrativeScreen` and `tutorial_narrative_screens` field +| Event ID | Trigger | Hero ID | Hero | Battalion | +|----------|---------|---------|------|-----------| +| `elena_reinforcement_round_5` | End of round 5 or later | 101 | Elena Fyar | Fyar's Vanguard, Heavy Infantry, 535 size, 75 training, 75 armament | +| `ranil_reinforcement_round_7` | End of round 7 or later | 100 | John Ranil | Ranil's Riders, Heavy Cavalry, 458 size, 80 training, 80 armament | -**Unity Client:** -- `NarrativeScreenController.cs`: Modal screen with title, body text, optional image -- `EagleGameController.cs`: Checks for pending narrative screens on game start +Shardok creates reinforcement units at battle creation time with `PENDING_REINFORCEMENT` status. `TutorialBattleController` activates them when their event triggers and returns a `TUTORIAL_REINFORCEMENTS_ARRIVED` action result. Unity maps that action to profession-specific dialogue triggers: -**Narrative Content** (defined in `tutorial_game_parameters.json`): -``` -Screen 1: "The Engineer of Onmaa" - Player backstory -Screen 2: "The Traitor Arrives" - Tarn's attack -Screen 3: "Defend Your Home" - Call to action -``` +- `tutorial_reinforcement_paladin` for Elena Fyar +- `tutorial_reinforcement_engineer` for John Ranil -### Phase 2: Auto-Start Battle - -**Configuration:** -- `tutorial_game_parameters.json`: Contains `tutorialBattle` config with attacker, target, flee conditions -- `game_parameters.proto`: `TutorialBattleConfig` message - -**Game Creation:** -- `NewGameCreation.scala`: `setupTutorialBattle()` method creates: - - `HostileArmyGroup` with `Attacking` status (bypasses decision phase) - - `Army` for defender - - Both added to target province - -**Battle Creation:** -- When round advances to `BattleRequest` phase, `RequestBattlesAction` automatically creates the `ShardokBattle` - -### Phase 3: Scripted Flee Mechanism - -**C++ Component:** -- `TutorialBattleController.hpp/cpp`: Controller for scripted battle logic - -**Key Methods:** -```cpp -// Check all events and execute triggered actions (called at end of each round) -std::vector CheckAndExecuteEvents( - const GameStateW& state, - const SettingsGetter& settings, - const std::shared_ptr& randomGenerator); -``` - -**Integration:** -- `ShardokEngine.cpp`: Calls `CheckAndExecuteEvents()` at end of each round in `HandlePlayerTurnEnd()` -- Events fire in config order, each event fires at most once - -### Phase 4: Mid-Battle Reinforcements - -**Action Types** (in `action_type.proto`): -- `TUTORIAL_ENEMY_FLED = 78` -- `TUTORIAL_REINFORCEMENTS_ARRIVED = 79` - -**Implementation (TODO):** -- When scripted flee triggers, create reinforcement units in defender's reserves -- Units have `UnitStatus_RESERVE_UNIT` status -- Reinforcements enter battle on subsequent turns - -### Phase 5: Tutorial Popups - -**Content Definitions** (in `TutorialContentDefinitions.cs`): -- `tutorial_battle_started`: "Defend your province!" -- `tutorial_enemy_fled`: "The enemy retreats!" -- `tutorial_reinforcements_arrived`: "Allies have arrived!" -- `tutorial_battle_victory`: Victory celebration - -**Trigger Integration:** -- `TutorialTriggerRegistry.cs`: Handles tutorial action types from `ActionResultView` -- `ShardokGameController.cs`: Detects tutorial events and triggers popups - -### Phase 6: Post-Battle Hero Joining - -**Implementation (TODO):** -- `ResolveBattleAction.scala`: Check if battle was tutorial and defender won -- Create `TutorialHeroJoined` action results adding reinforcement heroes to player faction -- Set appropriate loyalty, vigor, location for joined heroes - -### Phase 7: Post-Battle Strategic Dialogue - -After the battle ends and the player returns to the strategic map, narrative dialogues guide them through the next steps. - -**Dialogue 1: Battle Aftermath** (trigger: `tutorial_battle_ended`) -- Old Marek reflects on the close call -- Tarn has vanished — rumors of sorcery or escape, no one knows -- Captured lieutenants: they followed orders, not to blame for Tarn's madness -- Player should try to recruit them or hold them -- No instruction text needed — Handle Captured Heroes UI is self-explanatory - -**Dialogue 2: Rebuild Support** (trigger: `tutorial_rebuild_support`) -- Fires when available commands no longer include HandleCapturedHeroCommand -- Marek explains the province needs support rebuilt after the battle -- Engineers (John Ranil) can **Improve** the province — build infrastructure, develop economy -- Paladins (Elena Fyar) can **Give Alms** — distribute food to win hearts -- Instructions highlight **Improve** and **Give Alms** buttons -- Goal: get Support to **40** before January for tax revenue - -**Implementation:** -- Dialogues defined in `tutorial_strategic.json` -- `tutorial_battle_ended`: fire when tutorial battle is removed from `RunningShardokGameModels` -- `tutorial_rebuild_support`: fire when available commands no longer include `HandleCapturedHeroCommand` -- Register `ImproveButton` and `AlmsButton` as highlight targets in `TutorialTargetRegistry` - -See `TUTORIAL_CONTENT.md` for full dialogue text. +The reinforcement action includes an `attacker_starting_position_index`; Shardok resolves that to currently open map positions and starts a reinforcement placement phase. --- -## File Summary +## Scripted Event Controller + +`TutorialBattleController` is a generic event-driven controller, even though the current tutorial battle only uses timed reinforcements. + +Supported trigger types: + +- `after_round` +- `units_lost` +- `damage_taken` +- `unit_killed` + +Supported action types: + +- `flee` +- `reinforcements` + +Events are checked at the end of each round, evaluated in config order, and each `event_id` fires at most once. + +--- + +## Battle Resolution + +`RequestBattlesAction` marks tutorial opening battles with `reinforcementHeroIds = Set(100, 101)`. `ResolveBattleAction` uses that metadata so reinforcement heroes are accepted when battle results return, even though they were not part of the original defending army. + +After the opening battle resolves, `ResolveBattleAction` applies `TutorialTarnDisappearsAction` for `GameType.Tutorial(TutorialPhase.OpeningBattle)`. Tarn is removed from captured/unaffiliated province state and from his faction's leaders list, matching the post-battle dialogue that he has vanished. + +`TutorialBattleAutoResolve` provides a synthetic defender victory for tutorial setup paths that skip past the opening battle. In that synthetic result: + +- The defender wins. +- Reinforcement heroes 100 and 101 survive. +- Tarn is marked outlawed/escaped. +- Other attacker heroes are captured. +- Defender battalions take about 30% casualties. +- Attacker battalions are destroyed. + +--- + +## Unity Dialogue Hooks + +Strategic tutorial dialogue: + +- `game_started`: opening Onmaa monologue, ending with `FightButton` highlighted. +- `tutorial_battle_ended`: post-battle aftermath. +- `tutorial_rebuild_support`: support rebuilding guidance after captured-hero handling is done. + +Battle tutorial dialogue: + +- `shardok_placement_started`: placement and unit overview. +- `shardok_battle_running`: first player turn guidance. +- Ability/terrain triggers such as `archery_available`, `melee_available`, `ability_charge_available`, `start_fire_available`, `thunderstorm`, `duel_available`, `hide_available`, and `engineer_near_enemy`. +- Reinforcement triggers `tutorial_reinforcement_paladin` and `tutorial_reinforcement_engineer`. +- Capture triggers `enemy_hero_captured` and `friendly_hero_captured`. +- `shardok_battle_reset`: replay dialogue after a battle reset. + +--- + +## Files To Check First -### New Files | File | Purpose | |------|---------| -| `TutorialBattleController.hpp/cpp` | C++ scripted flee and reinforcement logic | -| `NarrativeScreenController.cs` | Unity narrative screen display | - -### Modified Files -| File | Changes | -|------|---------| -| `tutorial_game_parameters.json` | Battle configuration and narrative content | -| `game_state_view.proto` | Narrative screens field | -| `game_parameters.proto` | Tutorial battle config | -| `player_info.proto` | Tutorial battle flags for Shardok | -| `action_type.proto` | Tutorial action types | -| `NewGameCreation.scala` | Auto-start battle setup | -| `EagleGameController.cs` | Narrative display integration | -| `TutorialContentDefinitions.cs` | Battle tutorial content | -| `TutorialTriggerRegistry.cs` | Tutorial event triggers | - ---- - -## Remaining Implementation - -### Disable Player Flee -In `FleeCommandFactory.cpp`: -- Check if tutorial mode is enabled -- If defender, don't offer flee commands - -### Reinforcement Units -In `TutorialBattleController::ExecuteScriptedFlee()`: -- Create `Unit` objects for Elena Fyar, Hedrick, The Boulder -- Add to defender's reserve units -- Generate `TUTORIAL_REINFORCEMENTS_ARRIVED` action - -### Post-Battle Hero Joining -In Eagle's battle resolution: -- Detect tutorial battle completion -- Create heroes in player's faction with proper stats -- Generate notification action results +| `src/main/resources/net/eagle0/eagle/tutorial_parameters.json` | Tutorial map, factions, starting provinces, armies | +| `src/main/scala/net/eagle0/eagle/service/new_game_creation/TutorialGameCreation.scala` | Opening battle setup and `TutorialBattleConfig` creation | +| `src/main/protobuf/net/eagle0/common/tutorial_battle_config.proto` | Scripted event config schema | +| `src/main/cpp/net/eagle0/shardok/server/ShardokGamesManager.cpp` | Adds pending reinforcement units and applies initial visibility | +| `src/main/cpp/net/eagle0/shardok/library/tutorial/TutorialBattleController.cpp` | Evaluates scripted events and creates Shardok action results | +| `src/main/scala/net/eagle0/eagle/library/actions/impl/action/RequestBattlesAction.scala` | Creates the opening battle and tags reinforcement hero IDs | +| `src/main/scala/net/eagle0/eagle/library/actions/impl/action/ResolveBattleAction.scala` | Resolves reinforcements and applies Tarn disappearance | +| `src/main/scala/net/eagle0/eagle/service/tutorial/TutorialBattleAutoResolve.scala` | Synthetic result for phase-start paths after the opening battle | +| `src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/Tutorial/Triggers/TutorialTriggerRegistry.cs` | Converts strategic and tactical events into dialogue triggers | +| `src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/Resources/Dialogues/tutorial_battle.json` | Active battle dialogue content | --- ## Testing Checklist -- [ ] New user sees narrative screens before battle -- [ ] Battle starts immediately after narratives (no strategic map) -- [ ] Player has correct units (3 heroes, 3 battalions) -- [ ] Attacker has correct units (3 heroes, 3 battalions) -- [ ] Player cannot flee -- [ ] Tarn flees after player loses 1 unit -- [ ] Tarn flees after 5 rounds (if no units lost) -- [ ] Reinforcements appear when Tarn flees -- [ ] Tutorial popups appear at correct moments -- [ ] Reinforcement heroes join faction after battle victory - ---- - -## Configuration Reference - -### tutorial_game_parameters.json -```json -{ - "tutorialBattle": { - "attackerFactionHead": "Ikhaan Tarn", - "targetProvinceId": 14, - "fleeAfterDefenderUnitsLost": 1, - "fleeAfterRounds": 5, - "reinforcements": ["Elena Fyar", "Hedrick", "The Boulder"] - }, - "tutorialNarrativeScreens": [ - { "title": "...", "bodyText": "...", "imagePath": "" } - ] -} -``` - -### TutorialBattleConfig Proto (Shardok) -```protobuf -message TutorialBattleConfig { - bool enabled = 1; - repeated TutorialEvent events = 2; - repeated UnitInitialSize initial_sizes = 3; // For damage tracking -} - -message TutorialEvent { - string event_id = 1; - TutorialTrigger trigger = 2; - TutorialAction action = 3; -} - -message TutorialTrigger { - oneof trigger_type { - RoundTrigger after_round = 1; - UnitsLostTrigger units_lost = 2; - DamageTakenTrigger damage_taken = 3; - UnitKilledTrigger unit_killed = 4; - } -} - -message TutorialAction { - oneof action_type { - FleeAction flee = 1; - ReinforcementsAction reinforcements = 2; // Uses CommonUnit - } -} -``` +- [ ] Tutorial game starts in BattleRequest and creates an Onmaa battle immediately. +- [ ] Opening strategic dialogue highlights the Fight button. +- [ ] Tarn's unit is visible to the defender from battle start. +- [ ] Placement dialogue fires during setup. +- [ ] First-turn battle-running dialogue fires on the player's first active turn. +- [ ] Elena Fyar arrives from the configured round-5 event. +- [ ] John Ranil arrives from the configured round-7 event. +- [ ] Reinforcement arrival dialogues fire once per hero. +- [ ] Battle reset clears battle dialogue completion for replay. +- [ ] Post-battle aftermath fires after the battle leaves `RunningShardokGameModels`. +- [ ] Rebuild-support dialogue waits until captured-hero handling and visible notifications are done. +- [ ] Starting a tutorial at a later phase uses precomputed setup and still reaches coherent strategic dialogue state. diff --git a/docs/TUTORIAL_CONTENT.md b/docs/TUTORIAL_CONTENT.md index 82f333739f..4c7f2f52ea 100644 --- a/docs/TUTORIAL_CONTENT.md +++ b/docs/TUTORIAL_CONTENT.md @@ -1,6 +1,8 @@ # Tutorial Content Guide -This document defines all tutorial content. Edit this to refine the text, then update `TutorialContentDefinitions.cs` to match. +This document describes tutorial content. The active tutorial experience is driven by the narrative dialogue JSON files under `src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/Resources/Dialogues/`. + +The older step-based modal/overlay content in `TutorialContentDefinitions.cs` is currently dormant: `RegisterAll()` returns before registering any sequences. Keep that in mind when editing this file; changing `TutorialContentDefinitions.cs` will not affect the live tutorial until the early return is removed and the overlap with dialogue triggers is audited. --- @@ -181,17 +183,16 @@ These fire after the first tutorial battle ends and the player returns to the st > > Any of our heroes can develop the land or give alms to the people — nothing wins hearts faster than a full belly. Engineers like John Ranil are especially effective at improving a province, and paladins like Elena Fyar are the best at winning hearts. Either way, we need the people's support before the tax collectors come round in January. -**Instructions:** Use **Improve** and **Give Alms** to raise Support. You must have at least **40** support in the province by January in order to collect gold and food in taxes. +**Instructions:** John Ranil's step highlights `ImproveProvinceButton` and explains **Improve**. Elena Fyar's step highlights `GiveAlmsButton` and explains **Give Alms**. You must have at least **40** support in the province by January in order to collect gold and food in taxes. -**Highlight targets:** `SupportField` +**Highlight targets:** `ImproveProvinceButton`, `GiveAlmsButton` ### Implementation Notes -- Both dialogues go in `tutorial_strategic.json` -- Need new trigger events: `tutorial_battle_ended` and `tutorial_rebuild_support` -- `tutorial_battle_ended` should fire when the tutorial battle is removed from `RunningShardokGameModels` in `EagleGameModel.ApplyGameStateViewDiff` -- `tutorial_rebuild_support` should fire when available commands no longer include `HandleCapturedHeroCommand` (i.e. the captured heroes phase is done and the player has regular commands available) -- The "rebuild" dialogue should highlight the Improve and Give Alms buttons in the command panel (requires registering them as highlight targets) +- Both dialogues are defined in `tutorial_strategic.json`. +- `tutorial_battle_ended` fires from `TutorialTriggerRegistry.CheckTutorialBattleEnded()` when the opening battle is gone and strategic commands are available. It also handles the server-side auto-resolve path where the Unity client never observed a running Shardok model. +- `tutorial_rebuild_support` fires from `TutorialTriggerRegistry.CheckTutorialRebuildSupport()` when available commands no longer include `HandleCapturedHeroCommand` and the notification panel has no visible info. +- `ImproveProvinceButton` and `GiveAlmsButton` are runtime command-button targets registered by `CommandButtonPanelController`. --- @@ -210,11 +211,10 @@ These fire after the first tutorial battle ends and the player returns to the st ## Adding New Tutorials 1. Add entry to this document -2. Update `TutorialContentDefinitions.cs`: - - For onboarding: add to `CreateOnboardingSequence()` - - For contextual: add to `RegisterStrategicTutorials()` or `RegisterTacticalTutorials()` -3. Ensure trigger event exists in `TutorialTriggerRegistry.cs` -4. Test the flow +2. For active tutorial content, add or edit a script in `Assets/Resources/Dialogues/tutorial_strategic.json` or `tutorial_battle.json`. +3. Ensure the trigger event exists in `TutorialTriggerRegistry.cs` or is fired directly by the relevant controller. +4. If reviving the dormant step-UI system, remove the early return in `TutorialContentDefinitions.RegisterAll()` only after auditing duplicate trigger coverage with the dialogue JSON. +5. Test the flow. --- diff --git a/docs/TUTORIAL_SYSTEM.md b/docs/TUTORIAL_SYSTEM.md index 92c5fdeedc..a23c85bc2e 100644 --- a/docs/TUTORIAL_SYSTEM.md +++ b/docs/TUTORIAL_SYSTEM.md @@ -3,7 +3,7 @@ This document describes how the Unity client's tutorial system is wired together: the classes involved, the trigger catalog, the step lifecycle, and the expected first-session flow. It complements two existing docs: - **`TUTORIAL_CONTENT.md`** — human-facing copy for tutorial steps and dialogues -- **`TUTORIAL_BATTLE_SYSTEM.md`** — the scripted first-battle scenario (Tarn vs. John Ranil) +- **`TUTORIAL_BATTLE_SYSTEM.md`** — the scripted opening battle at Onmaa It also overlaps slightly with the in-tree `Assets/Tutorial/TUTORIAL_PLAN.md`, which is an older implementation plan. @@ -20,12 +20,13 @@ There are **two parallel subsystems** that share the same trigger plumbing: | Step-based tutorial UI (modals, overlays, hints) | **Dormant** | `Tutorial/TutorialManager.cs`, `Tutorial/UI/*`, `Tutorial/Content/*` | | Narrative dialogue system | **Live** | `Tutorial/Dialogue/*`, `Resources/Dialogues/*.json` | -`TutorialContentDefinitions.RegisterAll()` returns early at line 17 with the comment *"Old tutorial content suppressed — replaced by narrative dialogue system."* As a result: +`TutorialContentDefinitions.RegisterAll()` returns early with the comment *"Old tutorial content suppressed — replaced by narrative dialogue system."* As a result: - `OnboardingSequence` is never assigned, so `StartOnboarding()` no-ops at the "No onboarding sequence assigned" branch. - No contextual tutorial sequences are registered with the trigger registry. - The trigger registry still fires events normally, but nothing on the step-UI side is listening. - `DialogueManager` consumes those same events and matches them against scripts in `Resources/Dialogues/` (`tutorial_strategic.json`, `tutorial_battle.json`). +- Dialogue completion is persisted per game through `TutorialDialogueProgressStore`, so completed scripts do not replay after a reconnect or scene reload for the same game. **In practice today, the entire active tutorial experience runs through the dialogue system.** The step-UI machinery is preserved for future use — content definitions still exist below the early return in `TutorialContentDefinitions.cs` for reference. @@ -37,6 +38,7 @@ There are **two parallel subsystems** that share the same trigger plumbing: - **`Tutorial/TutorialManager.cs`** — Singleton. Owns `OnboardingSequence`, the active step queue, the trigger registry, and the dialogue manager handle. Initialized by `EagleGameController.SetUpGame()` and `ShardokGameController.SetUpGame()`. - **`Tutorial/TutorialState.cs`** — PlayerPrefs persistence. Tracks `OnboardingCompleted`, `OnboardingStepReached`, completed sequence IDs (HashSet for O(1) lookup), dismissed hints, and a global "tutorials disabled" preference. - **`Tutorial/TutorialTargetRegistry.cs`** — Maps string IDs to `RectTransform`s for highlighting. Static targets are Inspector-assigned (`ProvinceInfoPanel`, `SupportField`, `CommitButton`, etc.); dynamic targets register at runtime (e.g., hero rows from `HeroesAndBattalionsPanelController`). +- **`Tutorial/TutorialDialogueCoordinator.cs`** — Recomputes durable dialogue triggers from the current strategic model and sends them directly to `DialogueManager`. This covers reconnects, phase-start tutorial games, and strategic beats that should be recoverable from state. ### Triggers - **`Tutorial/Triggers/TutorialTriggerRegistry.cs`** (~1100 lines) — Routes game events to both the step UI (when sequences are registered) and `DialogueManager`. Maintains one-shot session flags so the same first-encounter trigger doesn't fire twice. @@ -94,14 +96,14 @@ Panels can be anchored via `TutorialPanelAnchor` (`Center` / `Left` / `Right` / ## Trigger Catalog -All triggers are raised via `TutorialTriggerRegistry`. Today these flow to `DialogueManager.TriggerDialogue()` and are matched against the dialogue JSON; if step-UI sequences are re-registered, they will also route there. File:line citations are for the registry unless noted; line numbers may drift. +Most event-style triggers are raised via `TutorialTriggerRegistry`. Durable strategic dialogue beats are also recomputed by `TutorialDialogueCoordinator` on each model update and sent directly to `DialogueManager.TriggerDialogue()`. Both paths match against the dialogue JSON; if step-UI sequences are re-registered, only the registry path will route to them. File:line citations are for the registry unless noted; line numbers may drift. ### Bootstrap / first-session | Trigger | Fires from | When | |---------|-----------|------| -| `game_started` | `EagleGameController.SetUpGame()` | First time entering a tutorial game | +| `game_started` | `TutorialDialogueCoordinator.EligibleTriggers()` | Any tutorial strategic model update; dialogue progress keeps it from replaying | | `first_battle_available` | `OnModelUpdated()` ~L204 | A `RunningShardokGameModel` first appears | -| `battle_entered` | `OnBattleEntered()` ~L793 | User clicks **Battle!** | +| `battle_entered` | `OnBattleEntered()` ~L793 | User clicks **Fight!** | | `tutorial_battle_ended` | `CheckTutorialBattleEnded()` ~L594 | Tutorial battle removed from running models | | `tutorial_rebuild_support` | `CheckTutorialRebuildSupport()` ~L616 | Captured-heroes phase done | | `tutorial_taxes_collected` | `CheckTaxesCollected()` ~L680 | New Year action with positive tax delta | @@ -135,7 +137,7 @@ All triggers are raised via `TutorialTriggerRegistry`. Today these flow to `Dial | `hero_stat_gained` | ~L281 | `HeroStatGained` action result | | `hero_profession_gained` | ~L284 | `ProfessionGained` action result | | `tutorial_faction_appears` | ~L287 | `TutorialFactionAppears` action — the Fracture Covenant landing | -| `tutorial_hero_faction_appears` | ~L290 | `TutorialHeroFactionAppears` action (no dialogue script today) | +| `tutorial_hero_faction_appears` | ~L290 | `TutorialHeroFactionAppears` action; has a four-step strategic dialogue today | | `tutorial_hero_departed` | ~L299 | First `HeroesDeparted` action against another player (King's hero abandons service) | | `tutorial_hero_departed_again` | ~L302 | Second `HeroesDeparted` in a *different* month than the first | @@ -220,8 +222,8 @@ Script shape (see `tutorial_strategic.json`): "speakerName": "Old Marek the Learned", "speakerImagePath": "fixed/old_marek_the_learned.png", "dialogueText": "...", - "instructionText": "Click Battle! to enter tactical combat.", - "highlightTarget": "GoToBattleButton", + "instructionText": "Click Fight! to enter tactical combat.", + "highlightTarget": "FightButton", "persistHighlight": true, "highlightProvince": "Onmaa", "completionEvent": null @@ -247,10 +249,12 @@ Dialogue panel position defaults sensibly per scene (`top` during combat) and ca Reset paths: - `TutorialState.Reset()` clears everything. -- Entering a tutorial game (`GameType.Tutorial`) resets so it can be replayed. +- Entering a tutorial game (`GameType.Tutorial`) resets `TutorialState`; dialogue replay is controlled separately by the per-game dialogue progress key. - Settings → "Reset Tutorials" calls `TutorialManager.ResetAllProgress()`, which also calls `DialogueManager.ResetCompletedScripts()`. -`DialogueManager` tracks completed scripts in memory only — they reset whenever `TutorialState` does. +`TutorialDialogueProgressStore` separately stores completed dialogue script IDs per game under PlayerPrefs keys named `Eagle0_TutorialDialogueProgress_`. + +`DialogueManager` keeps the active game's completed script IDs in memory and saves them through `TutorialDialogueProgressStore`. `ResetCompletedScripts()` clears the current game's dialogue progress; `ClearCurrentGameProgress()` deletes the current game's persisted dialogue key. --- @@ -276,7 +280,7 @@ You play **Sadar Rakon**, formerly Ikhaan Tarn's most trusted lieutenant. Tarn's | # | Trigger | What happens | |---|---------|--------------| -| 1 | `game_started` | **Opening monologue** (3 steps): Sadar's backstory, the Reclamation, last stand at Onmaa. Ends highlighting the **Battle!** button (`persistHighlight`) | +| 1 | `game_started` | **Opening monologue** (3 steps): Sadar's backstory, the Reclamation, last stand at Onmaa. Ends highlighting the **Fight!** button (`FightButton`, `persistHighlight`) | | 2 | `tutorial_battle_ended` | **Aftermath**: Tarn has *vanished*; you've captured his lieutenants. Recruit them or hold them | | 3 | `tutorial_rebuild_support` | **Rebuild Onmaa** (3 steps): Marek frames it; **John Ranil** introduces *Improve* (engineer bonus); **Elena Fyar** introduces *Give Alms* (paladin bonus). Goal: 40 support by January | | 4 | `tutorial_loyalty_warning` | November-ish, low-loyalty hero may leave at year end → use *Give Gold* / *Feast* | @@ -288,6 +292,7 @@ You play **Sadar Rakon**, formerly Ikhaan Tarn's most trusted lieutenant. Tarn's | 10 | `tutorial_faction_appears` | **The twist**: Fracture Covenant lands at Ingia and Soria. *The Eagle* commands them. Tarn may be with them | | 11 | `tutorial_hero_departed` | First King's hero abandons service — "something deeper is at work" | | 12 | `tutorial_hero_departed_again` | Second one in a different month — "something is wrong, I can feel it" (foreshadowing) | +| 13 | `tutorial_hero_faction_appears` | John Ranil and Elena Fyar leave Sadar's service and reappear as independent tutorial factions | ### Tactical battle flow (`tutorial_battle.json`)