Document current tutorial flow (#7264)

This commit is contained in:
2026-06-15 11:04:55 -07:00
committed by GitHub
parent 827d07e4af
commit ede235ec7a
3 changed files with 159 additions and 258 deletions
+130 -234
View File
@@ -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<ActionResult> CheckAndExecuteEvents(
const GameStateW& state,
const SettingsGetter& settings,
const std::shared_ptr<RandomGenerator>& 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.
+13 -13
View File
@@ -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.
---
+16 -11
View File
@@ -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 <b>Fight!</b> 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_<gameId>`.
`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`)