mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 20:55:44 +00:00
Pace post-battle tutorial guidance by month (#8682)
This commit is contained in:
-44
@@ -1,8 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Eagle0.Tutorial;
|
||||
using Net.Eagle0.Eagle.Common;
|
||||
using Net.Eagle0.Eagle.Views;
|
||||
using NUnit.Framework;
|
||||
using Shardok;
|
||||
using UnityEngine;
|
||||
@@ -432,31 +430,6 @@ namespace eagle0.Tests {
|
||||
} finally { Object.DestroyImmediate(gameObject); }
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AlmsDialogueWaitsForTheMonthAfterImproveDialogueWasPresented() {
|
||||
var gameObject = new GameObject("DialogueManager test");
|
||||
try {
|
||||
var manager = gameObject.AddComponent<DialogueManager>();
|
||||
var updater = new eagle.GameModelUpdater(987654321, null, null);
|
||||
var model = GetPrivateField<eagle.IGameModel>(updater, "_currentModel");
|
||||
SetCurrentDate(model, year: 12, month: 4);
|
||||
manager.OnEagleModelUpdated(model);
|
||||
InvokePrivate(manager, "RecordPresentedMonth", "post_battle_rebuild");
|
||||
|
||||
Assert.IsTrue(
|
||||
InvokePrivate<bool>(manager, "ShouldWaitToPresent", "post_battle_alms"));
|
||||
|
||||
SetCurrentDate(model, year: 12, month: 5);
|
||||
|
||||
Assert.IsFalse(
|
||||
InvokePrivate<bool>(manager, "ShouldWaitToPresent", "post_battle_alms"));
|
||||
Assert.IsFalse(InvokePrivate<bool>(
|
||||
manager,
|
||||
"ShouldWaitToPresent",
|
||||
"tutorial_attack_ready"));
|
||||
} finally { Object.DestroyImmediate(gameObject); }
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FinalUiHighlightsPersistUntilPlayerActs() {
|
||||
var managerObject = new GameObject("DialogueManager test");
|
||||
@@ -537,14 +510,6 @@ namespace eagle0.Tests {
|
||||
return (T)method.Invoke(target, null);
|
||||
}
|
||||
|
||||
private static T InvokePrivate<T>(object target, string methodName, params object[] args) {
|
||||
var method = target.GetType().GetMethod(
|
||||
methodName,
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.NotNull(method, $"Missing private method '{methodName}'");
|
||||
return (T)method.Invoke(target, args);
|
||||
}
|
||||
|
||||
private static void InvokePrivate(object target, string methodName, params object[] args) {
|
||||
var method = target.GetType().GetMethod(
|
||||
methodName,
|
||||
@@ -578,14 +543,5 @@ namespace eagle0.Tests {
|
||||
Assert.NotNull(property);
|
||||
property.SetValue(controller, model);
|
||||
}
|
||||
|
||||
private static void SetCurrentDate(eagle.IGameModel model, int year, int month) {
|
||||
var property = model.GetType().GetProperty(
|
||||
"GsView",
|
||||
BindingFlags.Instance | BindingFlags.Public);
|
||||
Assert.NotNull(property);
|
||||
var gameState = (GameStateView)property.GetValue(model);
|
||||
gameState.CurrentDate = new Date { Year = year, Month = month };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-36
@@ -33,8 +33,6 @@ namespace Eagle0.Tutorial {
|
||||
public Color unitHighlightSecondaryColor = new Color(1f, 0.85f, 0f, 0.2f);
|
||||
|
||||
private const string TutorialUnitOverlayIdPrefix = "tutorial_unit_highlight_";
|
||||
private const string PostBattleRebuildDialogueId = "post_battle_rebuild";
|
||||
private const string PostBattleAlmsDialogueId = "post_battle_alms";
|
||||
|
||||
private DialogueScript _activeScript;
|
||||
private int _currentStepIndex;
|
||||
@@ -53,7 +51,6 @@ namespace Eagle0.Tutorial {
|
||||
private readonly HashSet<string> _receivedServerDialogueIds = new HashSet<string>();
|
||||
private bool _persistingHighlight;
|
||||
private long? _progressGameId;
|
||||
private int? _postBattleRebuildPresentedMonth;
|
||||
|
||||
/// <summary>
|
||||
/// Whether a dialogue is currently being displayed.
|
||||
@@ -137,7 +134,6 @@ namespace Eagle0.Tutorial {
|
||||
TryPlayPendingServerDialogue();
|
||||
return;
|
||||
}
|
||||
if (ShouldWaitToPresent(dialogue.Id)) return;
|
||||
|
||||
var model = _eagleModel;
|
||||
if (model == null || !TryCreateServerScript(
|
||||
@@ -296,7 +292,6 @@ namespace Eagle0.Tutorial {
|
||||
foreach (var id in TutorialDialogueProgressStore.LoadCompletedScriptIds(gameId)) {
|
||||
_completedScriptIds.Add(id);
|
||||
}
|
||||
_postBattleRebuildPresentedMonth = null;
|
||||
}
|
||||
|
||||
private void StartScript(DialogueScript script) {
|
||||
@@ -308,7 +303,6 @@ namespace Eagle0.Tutorial {
|
||||
|
||||
_activeScript = script;
|
||||
_currentStepIndex = 0;
|
||||
RecordPresentedMonth(script.id);
|
||||
// In combat, always anchor to top so the panel doesn't cover the hex grid.
|
||||
// On the strategic map, always use the default position.
|
||||
var position = _shardokController?.Model != null ? "top" : null;
|
||||
@@ -385,9 +379,6 @@ namespace Eagle0.Tutorial {
|
||||
_completedScriptIds.Remove(id);
|
||||
_receivedServerDialogueIds.Remove(id);
|
||||
}
|
||||
if (ids.Contains(PostBattleRebuildDialogueId)) {
|
||||
_postBattleRebuildPresentedMonth = null;
|
||||
}
|
||||
|
||||
RemovePendingServerDialogues(ids, _pendingServerDialogues);
|
||||
RemovePendingServerDialogues(ids, _pendingTacticalServerDialogues);
|
||||
@@ -413,7 +404,6 @@ namespace Eagle0.Tutorial {
|
||||
_receivedServerDialogueIds.Clear();
|
||||
_pendingServerDialogues.Clear();
|
||||
_pendingTacticalServerDialogues.Clear();
|
||||
_postBattleRebuildPresentedMonth = null;
|
||||
SaveProgress();
|
||||
}
|
||||
|
||||
@@ -425,32 +415,6 @@ namespace Eagle0.Tutorial {
|
||||
_receivedServerDialogueIds.Clear();
|
||||
_pendingServerDialogues.Clear();
|
||||
_pendingTacticalServerDialogues.Clear();
|
||||
_postBattleRebuildPresentedMonth = null;
|
||||
}
|
||||
|
||||
private bool ShouldWaitToPresent(string dialogueId) {
|
||||
if (dialogueId != PostBattleAlmsDialogueId ||
|
||||
!_postBattleRebuildPresentedMonth.HasValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Strategic dialogue can remain queued while a tactical battle is open. Use the
|
||||
// month John was actually presented, rather than the server emission month, so Elena
|
||||
// cannot appear directly behind him after returning to the strategic map.
|
||||
var currentMonth = CurrentMonth();
|
||||
return currentMonth.HasValue &&
|
||||
currentMonth.Value <= _postBattleRebuildPresentedMonth.Value;
|
||||
}
|
||||
|
||||
private void RecordPresentedMonth(string dialogueId) {
|
||||
if (dialogueId == PostBattleRebuildDialogueId) {
|
||||
_postBattleRebuildPresentedMonth = CurrentMonth();
|
||||
}
|
||||
}
|
||||
|
||||
private int? CurrentMonth() {
|
||||
var date = _eagleModel?.CurrentDate;
|
||||
return date == null ? null : date.Year * 12 + date.Month;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -40,10 +40,7 @@ object StrategicTutorialDialogueSelector {
|
||||
TutorialDialogueContent.gameOver(marekId)
|
||||
),
|
||||
Option.when(tarnVanished)(TutorialDialogueContent.postBattleAftermath(marekId)),
|
||||
Option.when(
|
||||
(tarnVanished && !hasCapturedHeroes(after)) ||
|
||||
(hasCapturedHeroes(startingState) && !hasCapturedHeroes(after))
|
||||
)(
|
||||
Option.when(shouldOfferPostBattleRebuild(startingState, after))(
|
||||
TutorialDialogueContent.postBattleRebuild(
|
||||
marekId = marekId,
|
||||
ranilId = heroId(after, "John Ranil")
|
||||
@@ -162,6 +159,13 @@ object StrategicTutorialDialogueSelector {
|
||||
.get(OnmaaProvinceId)
|
||||
.exists(province => province.rulingFactionId.contains(PlayerFactionId) && province.support < DevelopedSupport)
|
||||
|
||||
private def shouldOfferPostBattleRebuild(before: GameState, after: GameState): Boolean =
|
||||
before.tutorialState
|
||||
.flatMap(_.emittedRound(TutorialDialogueContent.postBattleAftermathDialogueId))
|
||||
.contains(after.currentRoundId - 1) &&
|
||||
!hasEmitted(before, TutorialDialogueContent.postBattleRebuildDialogueId) &&
|
||||
!hasCapturedHeroes(after)
|
||||
|
||||
private def hasCapturedHeroes(state: GameState): Boolean =
|
||||
state.provinces.values.exists(province =>
|
||||
province.rulingFactionId.contains(PlayerFactionId) && province.capturedHeroes.nonEmpty
|
||||
|
||||
+11
-10
@@ -1,15 +1,16 @@
|
||||
package net.eagle0.eagle.model.view.action_result
|
||||
|
||||
object TutorialDialogueContent {
|
||||
val playerFactionId: Int = 3
|
||||
val postBattleRebuildDialogueId: String = "post_battle_rebuild"
|
||||
val postBattleAlmsDialogueId: String = "post_battle_alms"
|
||||
val heroDepartedDialogueId: String = "tutorial_hero_departed"
|
||||
val heroDepartedAgainDialogueId: String = "tutorial_hero_departed_again"
|
||||
val attackPrepareOrganizeDialogueId = "tutorial_attack_prepare_organize"
|
||||
val attackPrepareTrainDialogueId = "tutorial_attack_prepare_train"
|
||||
val attackPrepareArmDialogueId = "tutorial_attack_prepare_arm"
|
||||
val attackReadyDialogueId = "tutorial_attack_ready"
|
||||
val playerFactionId: Int = 3
|
||||
val postBattleAftermathDialogueId: String = "post_battle_aftermath"
|
||||
val postBattleRebuildDialogueId: String = "post_battle_rebuild"
|
||||
val postBattleAlmsDialogueId: String = "post_battle_alms"
|
||||
val heroDepartedDialogueId: String = "tutorial_hero_departed"
|
||||
val heroDepartedAgainDialogueId: String = "tutorial_hero_departed_again"
|
||||
val attackPrepareOrganizeDialogueId = "tutorial_attack_prepare_organize"
|
||||
val attackPrepareTrainDialogueId = "tutorial_attack_prepare_train"
|
||||
val attackPrepareArmDialogueId = "tutorial_attack_prepare_arm"
|
||||
val attackReadyDialogueId = "tutorial_attack_ready"
|
||||
|
||||
val openingFirstDialogueTextId: String = "tutorial_opening_dialogue_1"
|
||||
val openingSecondDialogueTextId: String = "tutorial_opening_dialogue_2"
|
||||
@@ -111,7 +112,7 @@ object TutorialDialogueContent {
|
||||
)
|
||||
|
||||
def postBattleAftermath(marekId: Int): TutorialDialogue = TutorialDialogue(
|
||||
id = "post_battle_aftermath",
|
||||
id = postBattleAftermathDialogueId,
|
||||
steps = Vector(
|
||||
step(marekId, "post_battle_aftermath_dialogue_1"),
|
||||
step(marekId, "post_battle_aftermath_dialogue_2")
|
||||
|
||||
+56
-5
@@ -3,7 +3,7 @@ package net.eagle0.eagle.library
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultWithResultingState
|
||||
import net.eagle0.eagle.model.action_result.concrete.{ActionResultC, ChangedHeroC}
|
||||
import net.eagle0.eagle.model.action_result.types.ActionResultType
|
||||
import net.eagle0.eagle.model.state.{BattalionTypeId, GameType, RoundPhase, TutorialPhase, TutorialState}
|
||||
import net.eagle0.eagle.model.state.{BattalionTypeId, CapturedHero, GameType, RoundPhase, TutorialPhase, TutorialState}
|
||||
import net.eagle0.eagle.model.state.battalion.concrete.BattalionC
|
||||
import net.eagle0.eagle.model.state.date.Date
|
||||
import net.eagle0.eagle.model.state.faction.concrete.FactionC
|
||||
@@ -144,10 +144,8 @@ class StrategicTutorialDialogueSelectorTest extends AnyFlatSpec with Matchers {
|
||||
val gameState = state()
|
||||
|
||||
selectedIds(gameState, gameState, ActionResultType.GameStart) should contain("tutorial_opening")
|
||||
selectedIds(gameState, gameState, ActionResultType.TutorialTarnDisappears) should contain allOf (
|
||||
"post_battle_aftermath",
|
||||
"post_battle_rebuild"
|
||||
)
|
||||
selectedIds(gameState, gameState, ActionResultType.TutorialTarnDisappears) should contain("post_battle_aftermath")
|
||||
selectedIds(gameState, gameState, ActionResultType.TutorialTarnDisappears) should not contain "post_battle_rebuild"
|
||||
selectedIds(gameState, gameState, ActionResultType.TutorialTarnDisappears) should not contain "post_battle_alms"
|
||||
selectedIds(gameState, gameState, ActionResultType.TutorialFactionAppears) should contain(
|
||||
"tutorial_eagle_appears"
|
||||
@@ -183,6 +181,59 @@ class StrategicTutorialDialogueSelectorTest extends AnyFlatSpec with Matchers {
|
||||
factionDialogue.steps(1).focus shouldBe Some(TutorialFocus.Button("InfoButton"))
|
||||
}
|
||||
|
||||
it should "offer John's Improve guidance only in the month after Marek's captured hero guidance" in {
|
||||
val mareksMonth = state(
|
||||
currentRoundId = 4,
|
||||
emittedTutorialDialogueRounds = Map(TutorialDialogueContent.postBattleAftermathDialogueId -> 4)
|
||||
)
|
||||
selectedIds(mareksMonth, mareksMonth, ActionResultType.HeroStatGained) should not contain "post_battle_rebuild"
|
||||
|
||||
val nextMonth = state(
|
||||
currentRoundId = 5,
|
||||
emittedTutorialDialogueRounds = Map(TutorialDialogueContent.postBattleAftermathDialogueId -> 4)
|
||||
)
|
||||
val dialogue = selectedDialogues(
|
||||
mareksMonth,
|
||||
nextMonth,
|
||||
ActionResultType.NewRoundAction
|
||||
).find(_.id == TutorialDialogueContent.postBattleRebuildDialogueId)
|
||||
.getOrElse(fail("Improve guidance was not emitted"))
|
||||
dialogue.steps(1).speakerHeroId shouldBe ranil.id
|
||||
dialogue.steps(1).focus shouldBe Some(TutorialFocus.Button("ImproveProvinceButton"))
|
||||
|
||||
val stillHandlingCapturedHero = state(
|
||||
currentRoundId = 5,
|
||||
onmaa = ProvinceC(
|
||||
id = OnmaaId,
|
||||
rulingFactionId = Some(PlayerFactionId),
|
||||
support = 20,
|
||||
capturedHeroes = Vector(CapturedHero(heroId = 200, recruitmentAttempted = false, previousFactionId = 5))
|
||||
),
|
||||
emittedTutorialDialogueRounds = Map(TutorialDialogueContent.postBattleAftermathDialogueId -> 4)
|
||||
)
|
||||
selectedIds(mareksMonth, stillHandlingCapturedHero, ActionResultType.NewRoundAction) should not contain
|
||||
"post_battle_rebuild"
|
||||
selectedIds(stillHandlingCapturedHero, nextMonth, ActionResultType.CapturedHeroResolved) should contain(
|
||||
"post_battle_rebuild"
|
||||
)
|
||||
|
||||
val laterMonth = state(
|
||||
currentRoundId = 6,
|
||||
emittedTutorialDialogueRounds = Map(TutorialDialogueContent.postBattleAftermathDialogueId -> 4)
|
||||
)
|
||||
selectedIds(laterMonth, laterMonth, ActionResultType.HeroStatGained) should not contain "post_battle_rebuild"
|
||||
|
||||
val alreadyEmitted = state(
|
||||
currentRoundId = 5,
|
||||
emittedTutorialDialogueRounds = Map(
|
||||
TutorialDialogueContent.postBattleAftermathDialogueId -> 4,
|
||||
TutorialDialogueContent.postBattleRebuildDialogueId -> 5
|
||||
)
|
||||
)
|
||||
selectedIds(alreadyEmitted, alreadyEmitted, ActionResultType.HeroStatGained) should not contain
|
||||
"post_battle_rebuild"
|
||||
}
|
||||
|
||||
it should "offer Elena's Alms guidance only in the month after John's Improve guidance" in {
|
||||
val johnsMonth = state(
|
||||
currentRoundId = 4,
|
||||
|
||||
Reference in New Issue
Block a user