mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 20:55:44 +00:00
Gate armament tutorial on available upgrades (#8679)
* Gate armament tutorial on available upgrades * Delay attack-ready tutorial guidance
This commit is contained in:
+11
@@ -193,6 +193,17 @@ namespace eagle {
|
||||
newRow.Battalion = battalion;
|
||||
SetBattalionRowSelections(newRow, battalion);
|
||||
});
|
||||
|
||||
var registry = TutorialManager.Instance?.TargetRegistry;
|
||||
if (registry != null) {
|
||||
registry.RegisterTargets(
|
||||
"BattalionArmamentValues",
|
||||
Enumerable.Range(0, battalionsTable.RowCount)
|
||||
.Select(index => battalionsTable
|
||||
.ComponentAt<BattalionRowController>(index)
|
||||
.armamentLabel
|
||||
.GetComponent<RectTransform>()));
|
||||
}
|
||||
}
|
||||
|
||||
void Start() {
|
||||
|
||||
+58
-7
@@ -49,7 +49,44 @@ namespace eagle0.Tests {
|
||||
Assert.AreEqual("fixed/old_marek.png", script.steps[0].speakerImagePath);
|
||||
Assert.AreEqual("Hold the line.", script.steps[0].dialogueText);
|
||||
Assert.AreEqual("Click Fight!", script.steps[0].instructionText);
|
||||
Assert.AreEqual("FightButton", script.steps[0].highlightTarget);
|
||||
CollectionAssert.AreEqual(new[] { "FightButton" }, script.steps[0].highlightTargets);
|
||||
Assert.IsTrue(script.steps[0].persistHighlight);
|
||||
textProvider.Clear();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ServerDialogueConversionResolvesMultipleUiFocusTargets() {
|
||||
var textProvider = ClientTextProvider.Provider;
|
||||
textProvider.Clear();
|
||||
textProvider.HandleNewStreamingText("marek-name", "Old Marek", 0, true);
|
||||
textProvider.HandleNewStreamingText("arm-copy", "Arm the troops.", 0, true);
|
||||
|
||||
var uiElements = new Net.Eagle0.Eagle.Views.TutorialUiElements();
|
||||
uiElements.ElementIds.Add("VisitTownButton");
|
||||
uiElements.ElementIds.Add("BattalionArmamentValues");
|
||||
uiElements.ElementIds.Add("InfrastructureField");
|
||||
var dialogue = new Net.Eagle0.Eagle.Views.TutorialDialogue {
|
||||
Id = "tutorial_attack_prepare_arm"
|
||||
};
|
||||
dialogue.Steps.Add(new Net.Eagle0.Eagle.Views.TutorialDialogueStep {
|
||||
SpeakerHeroId = 17,
|
||||
DialogueTextId = "arm-copy",
|
||||
Focus = new Net.Eagle0.Eagle.Views.TutorialFocus { UiElements = uiElements }
|
||||
});
|
||||
|
||||
var converted = DialogueManager.TryCreateServerScript(
|
||||
dialogue,
|
||||
new Dictionary<int, EagleHeroView> {
|
||||
[17] = new EagleHeroView { Id = 17, NameTextId = "marek-name" }
|
||||
},
|
||||
new Dictionary<int, EagleProvinceView>(),
|
||||
textProvider,
|
||||
out var script);
|
||||
|
||||
Assert.IsTrue(converted);
|
||||
CollectionAssert.AreEqual(
|
||||
new[] { "VisitTownButton", "BattalionArmamentValues", "InfrastructureField" },
|
||||
script.steps[0].highlightTargets);
|
||||
Assert.IsTrue(script.steps[0].persistHighlight);
|
||||
textProvider.Clear();
|
||||
}
|
||||
@@ -394,10 +431,12 @@ namespace eagle0.Tests {
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FinalButtonHighlightPersistsUntilPlayerActs() {
|
||||
public void FinalUiHighlightsPersistUntilPlayerActs() {
|
||||
var managerObject = new GameObject("DialogueManager test");
|
||||
var panelObject = new GameObject("DialoguePanel test", typeof(RectTransform));
|
||||
var targetObject = new GameObject("LoyaltyLabel test", typeof(RectTransform));
|
||||
var secondTargetObject =
|
||||
new GameObject("InfrastructureField test", typeof(RectTransform));
|
||||
try {
|
||||
var manager = managerObject.AddComponent<DialogueManager>();
|
||||
var panel = panelObject.AddComponent<DialoguePanelController>();
|
||||
@@ -412,26 +451,38 @@ namespace eagle0.Tests {
|
||||
new DialogueScript {
|
||||
id = "tutorial_loyalty_warning",
|
||||
steps = new List<DialogueStep> { new DialogueStep {
|
||||
highlightTarget = "LoyaltyLabel",
|
||||
highlightTargets =
|
||||
new List<string> { "LoyaltyLabel", "InfrastructureField" },
|
||||
persistHighlight = true
|
||||
} }
|
||||
});
|
||||
|
||||
InvokePrivate(manager, "HighlightRect", targetObject.GetComponent<RectTransform>());
|
||||
Assert.NotNull(GetPrivateField<GameObject>(manager, "_activeHighlight"));
|
||||
InvokePrivate(
|
||||
manager,
|
||||
"HighlightRects",
|
||||
new List<RectTransform> {
|
||||
targetObject.GetComponent<RectTransform>(),
|
||||
secondTargetObject.GetComponent<RectTransform>()
|
||||
});
|
||||
Assert.AreEqual(
|
||||
2,
|
||||
GetPrivateField<List<GameObject>>(manager, "_activeHighlights").Count);
|
||||
|
||||
manager.EndDialogueWithoutCompletion();
|
||||
|
||||
Assert.NotNull(GetPrivateField<GameObject>(manager, "_activeHighlight"));
|
||||
Assert.AreEqual(
|
||||
2,
|
||||
GetPrivateField<List<GameObject>>(manager, "_activeHighlights").Count);
|
||||
|
||||
manager.OnPlayerAction();
|
||||
|
||||
Assert.IsNull(GetPrivateField<GameObject>(manager, "_activeHighlight"));
|
||||
Assert.IsEmpty(GetPrivateField<List<GameObject>>(manager, "_activeHighlights"));
|
||||
Assert.IsEmpty(GetPrivateField<List<Image>>(manager, "_highlightEdges"));
|
||||
} finally {
|
||||
Object.DestroyImmediate(managerObject);
|
||||
Object.DestroyImmediate(panelObject);
|
||||
Object.DestroyImmediate(targetObject);
|
||||
Object.DestroyImmediate(secondTargetObject);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+35
-15
@@ -39,7 +39,7 @@ namespace Eagle0.Tutorial {
|
||||
private MapController _mapController;
|
||||
private IGameModel _eagleModel;
|
||||
private ShardokGameController _shardokController;
|
||||
private GameObject _activeHighlight;
|
||||
private readonly List<GameObject> _activeHighlights = new List<GameObject>();
|
||||
private readonly List<Image> _highlightEdges = new List<Image>();
|
||||
private readonly List<int> _activeUnitHighlightCells = new List<int>();
|
||||
private DialogueUnitHighlight _activeUnitHighlightCriteria;
|
||||
@@ -242,7 +242,15 @@ namespace Eagle0.Tutorial {
|
||||
step.highlightProvince = province.Name;
|
||||
break;
|
||||
case TutorialFocus.TargetOneofCase.ButtonId:
|
||||
step.highlightTarget = source.Focus.ButtonId;
|
||||
step.highlightTargets.Add(source.Focus.ButtonId);
|
||||
step.persistHighlight = index == dialogue.Steps.Count - 1;
|
||||
break;
|
||||
case TutorialFocus.TargetOneofCase.UiElements:
|
||||
if (source.Focus.UiElements.ElementIds.Count == 0 ||
|
||||
source.Focus.UiElements.ElementIds.Any(string.IsNullOrEmpty)) {
|
||||
return false;
|
||||
}
|
||||
step.highlightTargets.AddRange(source.Focus.UiElements.ElementIds);
|
||||
step.persistHighlight = index == dialogue.Steps.Count - 1;
|
||||
break;
|
||||
case TutorialFocus.TargetOneofCase.UnitId:
|
||||
@@ -325,13 +333,15 @@ namespace Eagle0.Tutorial {
|
||||
ClearHighlight();
|
||||
ClearUnitHighlight();
|
||||
|
||||
if (!string.IsNullOrEmpty(step.highlightTarget)) {
|
||||
// Highlight a UI element
|
||||
var target = TutorialManager.Instance?.TargetRegistry?.GetTargetOrFind(
|
||||
step.highlightTarget);
|
||||
if (target != null && target.gameObject.activeInHierarchy) {
|
||||
HighlightRect(target.GetComponent<RectTransform>());
|
||||
}
|
||||
if (step.highlightTargets.Count > 0) {
|
||||
var registry = TutorialManager.Instance?.TargetRegistry;
|
||||
var targets =
|
||||
registry == null
|
||||
? Enumerable.Empty<RectTransform>()
|
||||
: step.highlightTargets.SelectMany(registry.GetTargetsOrFind)
|
||||
.Where(target => target.gameObject.activeInHierarchy)
|
||||
.Select(target => target.GetComponent<RectTransform>());
|
||||
HighlightRects(targets);
|
||||
} else if (!string.IsNullOrEmpty(step.highlightProvince) && _mapController != null) {
|
||||
// Highlight a province on the map
|
||||
var proxy = _mapController.GetProvinceHighlightProxy(step.highlightProvince);
|
||||
@@ -423,8 +433,7 @@ namespace Eagle0.Tutorial {
|
||||
bool keepHighlight = false;
|
||||
if (_activeScript != null && _activeScript.steps.Count > 0) {
|
||||
var lastStep = _activeScript.steps[_activeScript.steps.Count - 1];
|
||||
keepHighlight = lastStep.persistHighlight &&
|
||||
!string.IsNullOrEmpty(lastStep.highlightTarget);
|
||||
keepHighlight = lastStep.persistHighlight && lastStep.highlightTargets.Count > 0;
|
||||
}
|
||||
|
||||
if (markCompleted && _activeScript != null && !string.IsNullOrEmpty(_activeScript.id)) {
|
||||
@@ -482,6 +491,17 @@ namespace Eagle0.Tutorial {
|
||||
|
||||
ClearHighlight();
|
||||
|
||||
AddHighlightRect(target);
|
||||
}
|
||||
|
||||
private void HighlightRects(IEnumerable<RectTransform> targets) {
|
||||
ClearHighlight();
|
||||
foreach (var target in targets.Where(target => target != null).Distinct()) {
|
||||
AddHighlightRect(target);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddHighlightRect(RectTransform target) {
|
||||
var go = new GameObject("DialogueHighlight");
|
||||
go.transform.SetParent(target, false);
|
||||
|
||||
@@ -531,7 +551,7 @@ namespace Eagle0.Tutorial {
|
||||
new Vector2(-highlightThickness, 0),
|
||||
Vector2.zero);
|
||||
|
||||
_activeHighlight = go;
|
||||
_activeHighlights.Add(go);
|
||||
}
|
||||
|
||||
private void CreateBorderEdge(
|
||||
@@ -555,10 +575,10 @@ namespace Eagle0.Tutorial {
|
||||
}
|
||||
|
||||
private void ClearHighlight() {
|
||||
if (_activeHighlight != null) {
|
||||
Destroy(_activeHighlight);
|
||||
_activeHighlight = null;
|
||||
foreach (var highlight in _activeHighlights) {
|
||||
if (highlight != null) { Destroy(highlight); }
|
||||
}
|
||||
_activeHighlights.Clear();
|
||||
_highlightEdges.Clear();
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Eagle0.Tutorial {
|
||||
[Serializable]
|
||||
@@ -7,7 +8,7 @@ namespace Eagle0.Tutorial {
|
||||
public string speakerImagePath;
|
||||
public string dialogueText;
|
||||
public string instructionText;
|
||||
public string highlightTarget;
|
||||
public List<string> highlightTargets = new List<string>();
|
||||
public string highlightProvince;
|
||||
public DialogueUnitHighlight highlightUnit;
|
||||
public bool persistHighlight;
|
||||
|
||||
+22
-1
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Eagle0.Tutorial {
|
||||
@@ -43,6 +44,8 @@ namespace Eagle0.Tutorial {
|
||||
// Dynamic targets registered at runtime (e.g., command buttons from prefabs)
|
||||
private Dictionary<string, RectTransform> _dynamicTargets =
|
||||
new Dictionary<string, RectTransform>();
|
||||
private Dictionary<string, IReadOnlyList<RectTransform>> _dynamicTargetGroups =
|
||||
new Dictionary<string, IReadOnlyList<RectTransform>>();
|
||||
|
||||
/// <summary>
|
||||
/// Registers a target at runtime. Use for prefab-instantiated UI elements.
|
||||
@@ -50,13 +53,22 @@ namespace Eagle0.Tutorial {
|
||||
public void RegisterTarget(string targetId, RectTransform target) {
|
||||
if (string.IsNullOrEmpty(targetId) || target == null) return;
|
||||
_dynamicTargets[targetId] = target;
|
||||
_dynamicTargetGroups.Remove(targetId);
|
||||
}
|
||||
|
||||
public void RegisterTargets(string targetId, IEnumerable<RectTransform> targets) {
|
||||
if (string.IsNullOrEmpty(targetId) || targets == null) return;
|
||||
_dynamicTargets.Remove(targetId);
|
||||
_dynamicTargetGroups[targetId] = targets.Where(target => target != null).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a dynamically registered target.
|
||||
/// </summary>
|
||||
public void UnregisterTarget(string targetId) {
|
||||
if (!string.IsNullOrEmpty(targetId)) { _dynamicTargets.Remove(targetId); }
|
||||
if (string.IsNullOrEmpty(targetId)) return;
|
||||
_dynamicTargets.Remove(targetId);
|
||||
_dynamicTargetGroups.Remove(targetId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -105,5 +117,14 @@ namespace Eagle0.Tutorial {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IReadOnlyList<Transform> GetTargetsOrFind(string targetId) {
|
||||
if (_dynamicTargetGroups.TryGetValue(targetId, out var group)) {
|
||||
return group.Cast<Transform>().ToList();
|
||||
}
|
||||
|
||||
var target = GetTargetOrFind(targetId);
|
||||
return target == null ? new List<Transform>() : new List<Transform> { target };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,9 +39,14 @@ message TutorialFocus {
|
||||
string button_id = 2;
|
||||
TutorialHexTile hex_tile = 3;
|
||||
int32 unit_id = 4;
|
||||
TutorialUiElements ui_elements = 5;
|
||||
}
|
||||
}
|
||||
|
||||
message TutorialUiElements {
|
||||
repeated string element_ids = 1;
|
||||
}
|
||||
|
||||
message TutorialHexTile {
|
||||
int32 row = 1;
|
||||
int32 column = 2;
|
||||
|
||||
@@ -111,6 +111,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:game_state_view_differ",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/province",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/view_filters:game_state_view_filter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types",
|
||||
|
||||
@@ -2,6 +2,7 @@ package net.eagle0.eagle.library
|
||||
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultWithResultingState
|
||||
import net.eagle0.eagle.library.util.faction_utils.FactionUtils
|
||||
import net.eagle0.eagle.library.util.province.ProvinceUtils
|
||||
import net.eagle0.eagle.model.action_result.types.ActionResultType
|
||||
import net.eagle0.eagle.model.state.date.Date
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
@@ -110,7 +111,8 @@ object StrategicTutorialDialogueSelector {
|
||||
actionResult.provinceId.exists(provinceId =>
|
||||
isDevelopedNonKingFactionBorderProvince(after, provinceId) &&
|
||||
!hasTrainedAttackForce(startingState, provinceId) &&
|
||||
hasTrainedAttackForce(after, provinceId)
|
||||
hasTrainedAttackForce(after, provinceId) &&
|
||||
hasUpgradeableArmament(after, provinceId)
|
||||
)
|
||||
)(
|
||||
TutorialDialogueContent.prepareAttackArm(marekId)
|
||||
@@ -122,6 +124,11 @@ object StrategicTutorialDialogueSelector {
|
||||
TutorialDialogueContent.attackPrepareTrainDialogueId,
|
||||
TutorialDialogueContent.attackPrepareArmDialogueId
|
||||
) &&
|
||||
wasEmittedBeforeRound(
|
||||
startingState,
|
||||
TutorialDialogueContent.attackPrepareArmDialogueId,
|
||||
after.currentRoundId
|
||||
) &&
|
||||
!hasEmitted(startingState, TutorialDialogueContent.attackReadyDialogueId) &&
|
||||
hasPreparedAttackForce(after)
|
||||
)(
|
||||
@@ -143,6 +150,9 @@ object StrategicTutorialDialogueSelector {
|
||||
private def hasEmittedAll(state: GameState, dialogueIds: String*): Boolean =
|
||||
dialogueIds.forall(hasEmitted(state, _))
|
||||
|
||||
private def wasEmittedBeforeRound(state: GameState, dialogueId: String, roundId: Int): Boolean =
|
||||
state.tutorialState.flatMap(_.emittedRound(dialogueId)).exists(_ < roundId)
|
||||
|
||||
private def shouldOfferPostBattleAlms(before: GameState, after: GameState): Boolean =
|
||||
before.tutorialState
|
||||
.flatMap(_.emittedRound(TutorialDialogueContent.postBattleRebuildDialogueId))
|
||||
@@ -243,6 +253,14 @@ object StrategicTutorialDialogueSelector {
|
||||
.sum >= ReadyTroopCount
|
||||
}
|
||||
|
||||
private def hasUpgradeableArmament(state: GameState, provinceId: Int): Boolean =
|
||||
state.provinces.get(provinceId).exists { province =>
|
||||
val effectiveInfrastructure = ProvinceUtils.effectiveInfrastructure(province)
|
||||
province.battalionIds
|
||||
.flatMap(state.battalions.get)
|
||||
.exists(_.armament < effectiveInfrastructure)
|
||||
}
|
||||
|
||||
private def hasPreparedAttackForce(state: GameState): Boolean =
|
||||
state.provinces.valuesIterator
|
||||
.filter(province => isDevelopedNonKingFactionBorderProvince(state, province.id))
|
||||
|
||||
+10
-5
@@ -10,7 +10,8 @@ import net.eagle0.eagle.views.tutorial_dialogue.{
|
||||
TutorialDialogueStep as TutorialDialogueStepProto,
|
||||
TutorialFocus as TutorialFocusProto,
|
||||
TutorialHexTile as TutorialHexTileProto,
|
||||
TutorialTextReplacement as TutorialTextReplacementProto
|
||||
TutorialTextReplacement as TutorialTextReplacementProto,
|
||||
TutorialUiElements as TutorialUiElementsProto
|
||||
}
|
||||
|
||||
object ActionResultViewConverter {
|
||||
@@ -38,15 +39,19 @@ object ActionResultViewConverter {
|
||||
dialogueTextId = step.dialogueTextId,
|
||||
instructionTextId = step.instructionTextId,
|
||||
focus = step.focus.map {
|
||||
case net.eagle0.eagle.model.view.action_result.TutorialFocus.Province(provinceId) =>
|
||||
case net.eagle0.eagle.model.view.action_result.TutorialFocus.Province(provinceId) =>
|
||||
TutorialFocusProto(target = TutorialFocusProto.Target.ProvinceId(provinceId))
|
||||
case net.eagle0.eagle.model.view.action_result.TutorialFocus.Button(buttonId) =>
|
||||
case net.eagle0.eagle.model.view.action_result.TutorialFocus.Button(buttonId) =>
|
||||
TutorialFocusProto(target = TutorialFocusProto.Target.ButtonId(buttonId))
|
||||
case net.eagle0.eagle.model.view.action_result.TutorialFocus.HexTile(row, column) =>
|
||||
case net.eagle0.eagle.model.view.action_result.TutorialFocus.UiElements(elementIds) =>
|
||||
TutorialFocusProto(
|
||||
target = TutorialFocusProto.Target.UiElements(TutorialUiElementsProto(elementIds = elementIds))
|
||||
)
|
||||
case net.eagle0.eagle.model.view.action_result.TutorialFocus.HexTile(row, column) =>
|
||||
TutorialFocusProto(
|
||||
target = TutorialFocusProto.Target.HexTile(TutorialHexTileProto(row = row, column = column))
|
||||
)
|
||||
case net.eagle0.eagle.model.view.action_result.TutorialFocus.Unit(unitId) =>
|
||||
case net.eagle0.eagle.model.view.action_result.TutorialFocus.Unit(unitId) =>
|
||||
TutorialFocusProto(target = TutorialFocusProto.Target.UnitId(unitId))
|
||||
},
|
||||
textReplacements = step.textReplacements.map(replacement =>
|
||||
|
||||
@@ -2,10 +2,11 @@ package net.eagle0.eagle.model.view.action_result
|
||||
|
||||
sealed trait TutorialFocus
|
||||
object TutorialFocus {
|
||||
case class Province(provinceId: Int) extends TutorialFocus
|
||||
case class Button(buttonId: String) extends TutorialFocus
|
||||
case class HexTile(row: Int, column: Int) extends TutorialFocus
|
||||
case class Unit(unitId: Int) extends TutorialFocus
|
||||
case class Province(provinceId: Int) extends TutorialFocus
|
||||
case class Button(buttonId: String) extends TutorialFocus
|
||||
case class UiElements(elementIds: Vector[String]) extends TutorialFocus
|
||||
case class HexTile(row: Int, column: Int) extends TutorialFocus
|
||||
case class Unit(unitId: Int) extends TutorialFocus
|
||||
}
|
||||
|
||||
sealed trait TutorialTextReplacementValue
|
||||
@@ -74,11 +75,17 @@ object TutorialDialogueValidator {
|
||||
}
|
||||
}
|
||||
step.focus.foreach {
|
||||
case TutorialFocus.Province(provinceId) if !provinceIds.contains(provinceId) =>
|
||||
case TutorialFocus.Province(provinceId) if !provinceIds.contains(provinceId) =>
|
||||
invalid(s"$label references missing province $provinceId")
|
||||
case TutorialFocus.Button(buttonId) if isBlank(buttonId) =>
|
||||
case TutorialFocus.Button(buttonId) if isBlank(buttonId) =>
|
||||
invalid(s"$label has an empty button id")
|
||||
case TutorialFocus.Unit(unitId) =>
|
||||
case TutorialFocus.UiElements(elementIds) if elementIds.isEmpty =>
|
||||
invalid(s"$label has no UI element ids")
|
||||
case TutorialFocus.UiElements(elementIds) if elementIds.exists(isBlank) =>
|
||||
invalid(s"$label has an empty UI element id")
|
||||
case TutorialFocus.UiElements(elementIds) if elementIds.distinct.size != elementIds.size =>
|
||||
invalid(s"$label repeats a UI element id")
|
||||
case TutorialFocus.Unit(unitId) =>
|
||||
unitIds match {
|
||||
case None =>
|
||||
invalid(s"$label uses unit focus without tactical unit context")
|
||||
@@ -86,9 +93,9 @@ object TutorialDialogueValidator {
|
||||
invalid(s"$label references missing tactical unit $unitId")
|
||||
case _ =>
|
||||
}
|
||||
case _: TutorialFocus.HexTile =>
|
||||
case _: TutorialFocus.HexTile =>
|
||||
invalid(s"$label uses hex focus without tactical map context")
|
||||
case _ =>
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -323,7 +323,11 @@ object TutorialDialogueContent {
|
||||
marekId,
|
||||
"tutorial_attack_prepare_arm_dialogue",
|
||||
instructionTextId = Some("tutorial_attack_prepare_arm_instruction"),
|
||||
focus = Some(TutorialFocus.Button("VisitTownButton"))
|
||||
focus = Some(
|
||||
TutorialFocus.UiElements(
|
||||
Vector("VisitTownButton", "BattalionArmamentValues", "InfrastructureField")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
+51
-10
@@ -375,6 +375,8 @@ class StrategicTutorialDialogueSelectorTest extends AnyFlatSpec with Matchers {
|
||||
id = OnmaaId,
|
||||
rulingFactionId = Some(PlayerFactionId),
|
||||
support = 40,
|
||||
infrastructure = 50,
|
||||
infrastructureDevastation = 10,
|
||||
battalionIds = Vector(501, 502),
|
||||
neighbors = Vector(Neighbor(provinceId = 39, startingPositionIndex = 0))
|
||||
)
|
||||
@@ -446,13 +448,24 @@ class StrategicTutorialDialogueSelectorTest extends AnyFlatSpec with Matchers {
|
||||
actingFactionId = Some(PlayerFactionId),
|
||||
provinceId = Some(OnmaaId)
|
||||
) should not contain TutorialDialogueContent.attackPrepareArmDialogueId
|
||||
selectedIds(
|
||||
val armDialogues = selectedDialogues(
|
||||
almostTrained,
|
||||
trained,
|
||||
ActionResultType.Train,
|
||||
actingFactionId = Some(PlayerFactionId),
|
||||
provinceId = Some(OnmaaId)
|
||||
) should contain("tutorial_attack_prepare_arm")
|
||||
)
|
||||
armDialogues.map(_.id) should contain("tutorial_attack_prepare_arm")
|
||||
armDialogues
|
||||
.find(_.id == TutorialDialogueContent.attackPrepareArmDialogueId)
|
||||
.getOrElse(fail("Armament guidance was not emitted"))
|
||||
.steps
|
||||
.loneElement
|
||||
.focus shouldBe Some(
|
||||
TutorialFocus.UiElements(
|
||||
Vector("VisitTownButton", "BattalionArmamentValues", "InfrastructureField")
|
||||
)
|
||||
)
|
||||
selectedIds(
|
||||
trained,
|
||||
trained,
|
||||
@@ -460,6 +473,26 @@ class StrategicTutorialDialogueSelectorTest extends AnyFlatSpec with Matchers {
|
||||
actingFactionId = Some(PlayerFactionId),
|
||||
provinceId = Some(OnmaaId)
|
||||
) should not contain "tutorial_attack_prepare_arm"
|
||||
|
||||
val fullyArmedAlmostTrained = almostTrained.copy(
|
||||
battalions = Map(
|
||||
501 -> BattalionC(501, BattalionTypeId.LightInfantry, size = 750, training = 50, armament = 40),
|
||||
502 -> BattalionC(502, BattalionTypeId.Longbowmen, size = 750, training = 49, armament = 40)
|
||||
)
|
||||
)
|
||||
val fullyArmedTrained = fullyArmedAlmostTrained.copy(
|
||||
battalions = fullyArmedAlmostTrained.battalions.updated(
|
||||
502,
|
||||
fullyArmedAlmostTrained.battalions(502).withTraining(50)
|
||||
)
|
||||
)
|
||||
selectedIds(
|
||||
fullyArmedAlmostTrained,
|
||||
fullyArmedTrained,
|
||||
ActionResultType.Train,
|
||||
actingFactionId = Some(PlayerFactionId),
|
||||
provinceId = Some(OnmaaId)
|
||||
) should not contain TutorialDialogueContent.attackPrepareArmDialogueId
|
||||
}
|
||||
|
||||
it should "wait for 40 support before beginning attack preparation" in {
|
||||
@@ -581,14 +614,14 @@ class StrategicTutorialDialogueSelectorTest extends AnyFlatSpec with Matchers {
|
||||
}
|
||||
|
||||
it should "suggest attacking only after organizing, training, and arming a developed hostile-border force" in {
|
||||
val borderProvince = ProvinceC(
|
||||
val borderProvince = ProvinceC(
|
||||
id = OnmaaId,
|
||||
rulingFactionId = Some(PlayerFactionId),
|
||||
support = 40,
|
||||
battalionIds = Vector(501, 502),
|
||||
neighbors = Vector(Neighbor(provinceId = 39, startingPositionIndex = 0))
|
||||
)
|
||||
val factions = Map(
|
||||
val factions = Map(
|
||||
PlayerFactionId -> FactionC(
|
||||
id = PlayerFactionId,
|
||||
factionHeadId = marek.id,
|
||||
@@ -597,31 +630,32 @@ class StrategicTutorialDialogueSelectorTest extends AnyFlatSpec with Matchers {
|
||||
),
|
||||
5 -> FactionC(id = 5, factionHeadId = 500, name = "Kojarians", leaderIds = Vector(500))
|
||||
)
|
||||
val readyBattalions = Map(
|
||||
val readyBattalions = Map(
|
||||
501 -> BattalionC(501, BattalionTypeId.LightInfantry, size = 750, training = 50, armament = 30),
|
||||
502 -> BattalionC(502, BattalionTypeId.Longbowmen, size = 750, training = 50, armament = 30)
|
||||
)
|
||||
val readyForce = state(
|
||||
val readyForce = state(
|
||||
onmaa = borderProvince,
|
||||
additionalProvinces = Map(39 -> ProvinceC(id = 39, rulingFactionId = Some(5))),
|
||||
battalions = readyBattalions,
|
||||
factions = factions
|
||||
)
|
||||
val throughTrain = withEmittedDialogueIds(
|
||||
val throughTrain = withEmittedDialogueIds(
|
||||
readyForce,
|
||||
TutorialDialogueContent.attackPrepareOrganizeDialogueId,
|
||||
TutorialDialogueContent.attackPrepareTrainDialogueId
|
||||
)
|
||||
val throughArm = withEmittedDialogueIds(
|
||||
val throughArmSameRound = withEmittedDialogueIds(
|
||||
readyForce,
|
||||
TutorialDialogueContent.attackPrepareOrganizeDialogueId,
|
||||
TutorialDialogueContent.attackPrepareTrainDialogueId,
|
||||
TutorialDialogueContent.attackPrepareArmDialogueId
|
||||
)
|
||||
val undeveloped = throughArm.copy(
|
||||
val throughArm = throughArmSameRound.copy(currentRoundId = 2)
|
||||
val undeveloped = throughArm.copy(
|
||||
provinces = throughArm.provinces.updated(OnmaaId, borderProvince.copy(support = 39))
|
||||
)
|
||||
val alreadyReady = withEmittedDialogueIds(
|
||||
val alreadyReady = withEmittedDialogueIds(
|
||||
readyForce,
|
||||
TutorialDialogueContent.attackPrepareOrganizeDialogueId,
|
||||
TutorialDialogueContent.attackPrepareTrainDialogueId,
|
||||
@@ -635,6 +669,13 @@ class StrategicTutorialDialogueSelectorTest extends AnyFlatSpec with Matchers {
|
||||
TutorialDialogueContent.attackReadyDialogueId
|
||||
selectedIds(undeveloped, undeveloped, ActionResultType.ArmTroops) should not contain
|
||||
TutorialDialogueContent.attackReadyDialogueId
|
||||
selectedIds(
|
||||
throughArmSameRound,
|
||||
throughArmSameRound,
|
||||
ActionResultType.ArmTroops,
|
||||
actingFactionId = Some(PlayerFactionId),
|
||||
provinceId = Some(OnmaaId)
|
||||
) should not contain TutorialDialogueContent.attackReadyDialogueId
|
||||
selectedIds(
|
||||
throughArm,
|
||||
throughArm,
|
||||
|
||||
@@ -60,6 +60,23 @@ class TutorialDialogueValidatorTest extends AnyFlatSpec with Matchers {
|
||||
validate(dialogue = tacticalDialogue, unitIds = Some(Set(27))) shouldBe tacticalDialogue
|
||||
}
|
||||
|
||||
it should "accept multiple UI focus targets" in {
|
||||
val uiDialogue = validDialogue.copy(
|
||||
steps = validDialogue.steps.map(
|
||||
_.copy(focus = Some(TutorialFocus.UiElements(Vector("ArmamentValues", "InfrastructureField"))))
|
||||
)
|
||||
)
|
||||
validate(dialogue = uiDialogue) shouldBe uiDialogue
|
||||
}
|
||||
|
||||
it should "reject an empty collection of UI focus targets" in {
|
||||
val uiDialogue = validDialogue.copy(
|
||||
steps = validDialogue.steps.map(_.copy(focus = Some(TutorialFocus.UiElements(Vector.empty))))
|
||||
)
|
||||
val error = the[IllegalStateException] thrownBy validate(dialogue = uiDialogue)
|
||||
error.getMessage should include("no UI element ids")
|
||||
}
|
||||
|
||||
it should "reject a tactical unit focus missing from the snapshot" in {
|
||||
val tacticalDialogue = validDialogue.copy(
|
||||
steps = validDialogue.steps.map(_.copy(focus = Some(TutorialFocus.Unit(27))))
|
||||
|
||||
Reference in New Issue
Block a user