mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 22:35:42 +00:00
Add server tutorial dialogue payload (#8456)
* Drive tutorial opening dialogue from server * Update tutorial dialogue contracts * Keep tutorial rendering client-driven * Make tutorial focus targets data-driven * Reference tutorial speakers and client text
This commit is contained in:
@@ -20,6 +20,7 @@ proto_library(
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":game_state_view_proto",
|
||||
":tutorial_dialogue_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:action_result_notification_details_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:action_result_type_proto",
|
||||
"@com_google_protobuf//:wrappers_proto",
|
||||
@@ -268,6 +269,7 @@ csharp_proto_compile(
|
||||
":province_view_proto",
|
||||
":shardok_battle_view_proto",
|
||||
":stat_with_condition_proto",
|
||||
":tutorial_dialogue_proto",
|
||||
],
|
||||
visibility = ["//src/main/csharp/net/eagle0/clients/unity/eagle0:__pkg__"],
|
||||
)
|
||||
@@ -287,6 +289,7 @@ go_proto_library(
|
||||
":province_view_proto",
|
||||
":shardok_battle_view_proto",
|
||||
":stat_with_condition_proto",
|
||||
":tutorial_dialogue_proto",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
@@ -294,3 +297,18 @@ go_proto_library(
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:common_go_proto",
|
||||
],
|
||||
)
|
||||
|
||||
proto_library(
|
||||
name = "tutorial_dialogue_proto",
|
||||
srcs = ["tutorial_dialogue.proto"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
scala_proto_library(
|
||||
name = "tutorial_dialogue_scala_proto",
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle:__subpackages__",
|
||||
],
|
||||
deps = [":tutorial_dialogue_proto"],
|
||||
)
|
||||
|
||||
@@ -10,6 +10,7 @@ import "google/protobuf/wrappers.proto";
|
||||
import "src/main/protobuf/net/eagle0/eagle/common/action_result_notification_details.proto";
|
||||
import "src/main/protobuf/net/eagle0/eagle/common/action_result_type.proto";
|
||||
import "src/main/protobuf/net/eagle0/eagle/views/game_state_view.proto";
|
||||
import "src/main/protobuf/net/eagle0/eagle/views/tutorial_dialogue.proto";
|
||||
|
||||
option java_multiple_files = true;
|
||||
option java_package = "net.eagle0.eagle.views";
|
||||
@@ -26,4 +27,6 @@ message ActionResultView {
|
||||
GameStateViewDiff game_state_diff = 5;
|
||||
|
||||
repeated .net.eagle0.eagle.common.Notification notifications = 6;
|
||||
|
||||
repeated TutorialDialogue tutorial_dialogues = 7;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package net.eagle0.eagle.views;
|
||||
|
||||
option java_multiple_files = true;
|
||||
option java_package = "net.eagle0.eagle.views";
|
||||
option java_outer_classname = "TutorialDialogueProto";
|
||||
option objc_class_prefix = "E0G";
|
||||
|
||||
// Tutorial copy is server-owned. Clients may interpret focus as a presentation
|
||||
// hint, but should render the speaker and text directly from this payload.
|
||||
message TutorialDialogue {
|
||||
string id = 1;
|
||||
repeated TutorialDialogueStep steps = 2;
|
||||
}
|
||||
|
||||
message TutorialDialogueStep {
|
||||
int32 speaker_hero_id = 1;
|
||||
string dialogue_text_id = 2;
|
||||
optional string instruction_text_id = 3;
|
||||
TutorialFocus focus = 5;
|
||||
}
|
||||
|
||||
message TutorialFocus {
|
||||
oneof target {
|
||||
int32 province_id = 1;
|
||||
string button_id = 2;
|
||||
TutorialHexTile hex_tile = 3;
|
||||
int32 unit_id = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message TutorialHexTile {
|
||||
int32 row = 1;
|
||||
int32 column = 2;
|
||||
}
|
||||
@@ -27,6 +27,15 @@ filegroup(
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "tutorial_dialogue_text",
|
||||
srcs = ["tutorial_dialogue_text.tsv"],
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle/service:__pkg__",
|
||||
"//src/test:__subpackages__",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "beasts",
|
||||
srcs = ["beasts.tsv"],
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
text_id text
|
||||
tutorial_opening_dialogue_1 Sadar Rakon. I had hoped we would never see this day.\n\nYou were once Ikhaan Tarn's most trusted lieutenant — the right hand of the Shardok himself. But over the past year, Tarn's behavior grew ever more erratic. The paranoia, the inscrutable orders, those strange training exercises no one could explain... and King Bregos would hear none of it.\n\nSo you did what had to be done. You and a handful of others raised the banner of the Reclamation — not against the Crown, but against the man who wields its sword and can no longer be trusted with it.
|
||||
tutorial_opening_dialogue_2 But Tarn still commands the royal army, and he is perhaps the most capable soldier this realm has ever seen. Your allies are scattered. You've been pushed back to Onmaa, your back to the sea. The rebellion teeters on the edge of defeat.\n\nThis is the last stand, Sadar. If we fall here, the Reclamation dies with us.
|
||||
tutorial_opening_dialogue_3 Tarn's forces are already at the gates. If we survive this day... well. Perhaps there is still a way to reach the King's ear. But first, we must survive.\n\nJoin the battle. Let us make our stand.
|
||||
tutorial_opening_instruction Click <b>Fight!</b> to enter tactical combat.
|
||||
|
@@ -8,11 +8,12 @@ import net.eagle0.eagle.library.util.faction_utils.FactionUtils
|
||||
import net.eagle0.eagle.library.util.view_filters.GameStateViewFilter
|
||||
import net.eagle0.eagle.library.util.GameStateViewDiffer
|
||||
import net.eagle0.eagle.model.action_result.types.ActionResultType
|
||||
import net.eagle0.eagle.model.state.{GameType, RoundPhase, TutorialPhase}
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.RoundPhase
|
||||
import net.eagle0.eagle.model.view.action_result.ActionResultView
|
||||
import net.eagle0.eagle.model.view.action_result.{ActionResultView, TutorialDialogue, TutorialDialogueContent}
|
||||
import net.eagle0.eagle.model.view.game_state.{GameStateView, GameStateViewDiff}
|
||||
import net.eagle0.eagle.FactionId
|
||||
import net.eagle0.util.hero_generation.LoadedHero
|
||||
|
||||
object ActionResultFilter {
|
||||
private val UNIVERSALLY_VISIBLE_TYPES: Set[ActionResultType] = Set(
|
||||
@@ -107,6 +108,8 @@ object ActionResultFilter {
|
||||
def shouldIncludeNotification(targetFactionIds: Vector[FactionId]): Boolean =
|
||||
targetFactionIds.isEmpty || factionId.exists(targetFactionIds.contains)
|
||||
|
||||
val tutorialDialogues = openingTutorialDialogue(actionResult.actionResultType, result.resultingState, factionId)
|
||||
|
||||
if gsDiff.isDefined &&
|
||||
actionResult.actingFactionId.isDefined &&
|
||||
factionId.exists { askingFid =>
|
||||
@@ -137,7 +140,8 @@ object ActionResultFilter {
|
||||
provinceId = actionResult.provinceId,
|
||||
actingHeroId = actionResult.actingHeroId,
|
||||
gameStateDiff = gsDiff,
|
||||
notifications = filteredNotifications
|
||||
notifications = filteredNotifications,
|
||||
tutorialDialogues = tutorialDialogues
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -195,6 +199,9 @@ object ActionResultFilter {
|
||||
def shouldIncludeNotification(targetFactionIds: Vector[FactionId]): Boolean =
|
||||
targetFactionIds.isEmpty || targetFactionIds.contains(factionId)
|
||||
|
||||
val tutorialDialogues =
|
||||
openingTutorialDialogue(actionResult.actionResultType, result.resultingState, Some(factionId))
|
||||
|
||||
if gsDiff.isDefined &&
|
||||
actionResult.actingFactionId.isDefined &&
|
||||
!actionResult.actingFactionId.exists(targetFid =>
|
||||
@@ -220,11 +227,31 @@ object ActionResultFilter {
|
||||
provinceId = actionResult.provinceId,
|
||||
actingHeroId = actionResult.actingHeroId,
|
||||
gameStateDiff = gsDiff,
|
||||
notifications = filteredNotifications
|
||||
notifications = filteredNotifications,
|
||||
tutorialDialogues = tutorialDialogues
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def openingTutorialDialogue(
|
||||
actionResultType: ActionResultType,
|
||||
state: GameState,
|
||||
factionId: Option[FactionId]
|
||||
): Vector[TutorialDialogue] =
|
||||
Option
|
||||
.when(
|
||||
factionId.contains(TutorialDialogueContent.playerFactionId) &&
|
||||
actionResultType == ActionResultType.GameStart &&
|
||||
state.gameType == GameType.Tutorial(TutorialPhase.OpeningBattle)
|
||||
) {
|
||||
val speakerHeroId = state.heroes.values
|
||||
.find(_.nameTextId == LoadedHero.nameTextId("Old Marek the Learned"))
|
||||
.map(_.id)
|
||||
.getOrElse(throw new IllegalStateException("Old Marek is missing from the opening tutorial state"))
|
||||
TutorialDialogueContent.opening(speakerHeroId)
|
||||
}
|
||||
.toVector
|
||||
|
||||
def filterForObserver(
|
||||
startingState: GameState,
|
||||
results: Vector[ActionResultWithResultingState]
|
||||
|
||||
@@ -115,6 +115,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/view/action_result:action_result_view",
|
||||
"//src/main/scala/net/eagle0/eagle/model/view/game_state:game_state_view_diff",
|
||||
"//src/main/scala/net/eagle0/util/hero_generation:loaded_hero",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
+31
-1
@@ -5,6 +5,12 @@ import net.eagle0.eagle.model.proto_converters.view.game_state.GameStateViewDiff
|
||||
import net.eagle0.eagle.model.proto_converters.NotificationConverter
|
||||
import net.eagle0.eagle.model.view.action_result.ActionResultView
|
||||
import net.eagle0.eagle.views.action_result_view.ActionResultView as ActionResultViewProto
|
||||
import net.eagle0.eagle.views.tutorial_dialogue.{
|
||||
TutorialDialogue as TutorialDialogueProto,
|
||||
TutorialDialogueStep as TutorialDialogueStepProto,
|
||||
TutorialFocus as TutorialFocusProto,
|
||||
TutorialHexTile as TutorialHexTileProto
|
||||
}
|
||||
|
||||
object ActionResultViewConverter {
|
||||
def toProto(view: ActionResultView): ActionResultViewProto =
|
||||
@@ -16,6 +22,30 @@ object ActionResultViewConverter {
|
||||
gameStateDiff = view.gameStateDiff.map(GameStateViewDiffConverter.toProto),
|
||||
notifications = view.notifications
|
||||
.map(NotificationConverter.toProto)
|
||||
.collect { case (proto, false) => proto }
|
||||
.collect { case (proto, false) => proto },
|
||||
tutorialDialogues = view.tutorialDialogues.map(dialogue =>
|
||||
TutorialDialogueProto(
|
||||
id = dialogue.id,
|
||||
steps = dialogue.steps.map(step =>
|
||||
TutorialDialogueStepProto(
|
||||
speakerHeroId = step.speakerHeroId,
|
||||
dialogueTextId = step.dialogueTextId,
|
||||
instructionTextId = step.instructionTextId,
|
||||
focus = step.focus.map {
|
||||
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) =>
|
||||
TutorialFocusProto(target = TutorialFocusProto.Target.ButtonId(buttonId))
|
||||
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) =>
|
||||
TutorialFocusProto(target = TutorialFocusProto.Target.UnitId(unitId))
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ scala_library(
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:action_result_type_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/views:action_result_view_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/views:tutorial_dialogue_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:notification_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types",
|
||||
|
||||
@@ -11,5 +11,6 @@ case class ActionResultView(
|
||||
provinceId: Option[ProvinceId] = None,
|
||||
actingHeroId: Option[HeroId] = None,
|
||||
gameStateDiff: Option[GameStateViewDiff] = None,
|
||||
notifications: Vector[NotificationT] = Vector.empty
|
||||
notifications: Vector[NotificationT] = Vector.empty,
|
||||
tutorialDialogues: Vector[TutorialDialogue] = Vector.empty
|
||||
)
|
||||
|
||||
@@ -2,10 +2,15 @@ load("@rules_scala//scala:scala.bzl", "scala_library")
|
||||
|
||||
scala_library(
|
||||
name = "action_result_view",
|
||||
srcs = ["ActionResultView.scala"],
|
||||
srcs = [
|
||||
"ActionResultView.scala",
|
||||
"TutorialDialogue.scala",
|
||||
"TutorialDialogueContent.scala",
|
||||
],
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle/library:__pkg__",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/view/action_result:__pkg__",
|
||||
"//src/main/scala/net/eagle0/eagle/service:__pkg__",
|
||||
"//src/main/scala/net/eagle0/eagle/service/controller:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
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 TutorialDialogueStep(
|
||||
speakerHeroId: Int,
|
||||
dialogueTextId: String,
|
||||
instructionTextId: Option[String] = None,
|
||||
focus: Option[TutorialFocus] = None
|
||||
)
|
||||
|
||||
case class TutorialDialogue(id: String, steps: Vector[TutorialDialogueStep])
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package net.eagle0.eagle.model.view.action_result
|
||||
|
||||
object TutorialDialogueContent {
|
||||
val playerFactionId: Int = 3
|
||||
|
||||
val openingFirstDialogueTextId: String = "tutorial_opening_dialogue_1"
|
||||
val openingSecondDialogueTextId: String = "tutorial_opening_dialogue_2"
|
||||
val openingThirdDialogueTextId: String = "tutorial_opening_dialogue_3"
|
||||
val openingInstructionTextId: String = "tutorial_opening_instruction"
|
||||
|
||||
val textIds: Vector[String] = Vector(
|
||||
openingFirstDialogueTextId,
|
||||
openingSecondDialogueTextId,
|
||||
openingThirdDialogueTextId,
|
||||
openingInstructionTextId
|
||||
)
|
||||
|
||||
def opening(speakerHeroId: Int): TutorialDialogue = TutorialDialogue(
|
||||
id = "tutorial_opening",
|
||||
steps = Vector(
|
||||
TutorialDialogueStep(
|
||||
speakerHeroId = speakerHeroId,
|
||||
dialogueTextId = openingFirstDialogueTextId
|
||||
),
|
||||
TutorialDialogueStep(
|
||||
speakerHeroId = speakerHeroId,
|
||||
dialogueTextId = openingSecondDialogueTextId,
|
||||
focus = Some(TutorialFocus.Province(provinceId = 14))
|
||||
),
|
||||
TutorialDialogueStep(
|
||||
speakerHeroId = speakerHeroId,
|
||||
dialogueTextId = openingThirdDialogueTextId,
|
||||
instructionTextId = Some(openingInstructionTextId),
|
||||
focus = Some(TutorialFocus.Button(buttonId = "FightButton"))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -289,6 +289,7 @@ scala_library(
|
||||
srcs = ["GamesManager.scala"],
|
||||
resources = [
|
||||
"//src/main/resources/net/eagle0/eagle:settings",
|
||||
"//src/main/resources/net/eagle0/eagle:tutorial_dialogue_text",
|
||||
"//src/main/resources/net/eagle0/shardok:battalion_types",
|
||||
],
|
||||
visibility = [
|
||||
@@ -335,6 +336,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/common:functional_random",
|
||||
"//src/main/scala/net/eagle0/common:proto_parser",
|
||||
"//src/main/scala/net/eagle0/common:simple_timed_logger",
|
||||
"//src/main/scala/net/eagle0/common:tsv_utils",
|
||||
"//src/main/scala/net/eagle0/common:zip_utils",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle:user_id",
|
||||
@@ -367,6 +369,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/shardok_battle",
|
||||
"//src/main/scala/net/eagle0/eagle/model/view/action_result:action_result_view",
|
||||
"//src/main/scala/net/eagle0/eagle/service/controller:game_controller",
|
||||
"//src/main/scala/net/eagle0/eagle/service/controller:human_player_client_connection_state",
|
||||
"//src/main/scala/net/eagle0/eagle/service/new_game_creation",
|
||||
|
||||
@@ -6,7 +6,7 @@ import scala.concurrent.{ExecutionContext, Future}
|
||||
import scala.util.{Failure, Random, Success}
|
||||
|
||||
import io.sentry.Sentry
|
||||
import net.eagle0.common.{ProtoParser, SeededRandom, SimpleTimedLogger, ZipUtils}
|
||||
import net.eagle0.common.{ProtoParser, SeededRandom, SimpleTimedLogger, TsvUtils, ZipUtils}
|
||||
import net.eagle0.common.shardok_internal_interface.ShardokInternalInterfaceGrpc.ShardokInternalInterfaceStub
|
||||
import net.eagle0.common.tutorial_battle_config.TutorialBattleConfig
|
||||
import net.eagle0.eagle.{ClientTextId, FactionId, GameId, ProvinceId, ShardokGameId, UserId}
|
||||
@@ -47,6 +47,7 @@ import net.eagle0.eagle.model.state.{BattalionType, TutorialPhase}
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.hero.HeroT
|
||||
import net.eagle0.eagle.model.state.shardok_battle.ShardokBattle
|
||||
import net.eagle0.eagle.model.view.action_result.TutorialDialogueContent
|
||||
import net.eagle0.eagle.service.controller.*
|
||||
import net.eagle0.eagle.service.new_game_creation.{
|
||||
ExpandedGameParameters,
|
||||
@@ -120,11 +121,19 @@ object GamesManager {
|
||||
private def isOpeningTutorialBattle(battle: ShardokBattle): Boolean =
|
||||
battle.battleIndex == 1 && battle.reinforcementHeroIds.nonEmpty
|
||||
|
||||
private val tutorialDialogueTexts: Map[ClientTextId, String] =
|
||||
TsvUtils
|
||||
.loadMaps(getClass.getResource("/net/eagle0/eagle/tutorial_dialogue_text.tsv"))
|
||||
.getOrElse(throw new EagleInternalException("Failed to load tutorial dialogue text"))
|
||||
.map(row => row("text_id") -> row("text").replace("\\n", "\n"))
|
||||
.toMap
|
||||
|
||||
private val pregeneratedTexts = PregeneratedClientTextStore(
|
||||
texts = GameParametersUtils.fixedHeroesNameMap ++
|
||||
GameParametersUtils.pregeneratedHeroesNameMap ++
|
||||
GameParametersUtils.fixedHeroBackstories ++
|
||||
GameParametersUtils.pregeneratedHeroBackstories +
|
||||
GameParametersUtils.pregeneratedHeroBackstories ++
|
||||
tutorialDialogueTexts +
|
||||
(NewGameCreation.initialChronicleGeneratedTextId -> NewGameCreation.initialChronicleText) +
|
||||
(TutorialGameCreation.tutorialChronicleGeneratedTextId -> TutorialGameCreation.tutorialChronicleText)
|
||||
)
|
||||
@@ -190,6 +199,19 @@ object GamesManager {
|
||||
factionIds
|
||||
)
|
||||
|
||||
private def withPopulatedTutorialTexts(
|
||||
clientTextStore: ClientTextStore,
|
||||
factionIds: Vector[FactionId]
|
||||
): ClientTextStore =
|
||||
clientTextStore.withAddedCompleteTexts(
|
||||
TutorialDialogueContent.textIds.map(textId =>
|
||||
textId -> pregeneratedTexts
|
||||
.getText(textId)
|
||||
.getOrElse(throw new EagleInternalException(s"Missing pregenerated tutorial dialogue text: $textId"))
|
||||
),
|
||||
factionIds
|
||||
)
|
||||
|
||||
private[service] def pregeneratedTextPairs(
|
||||
pregenerated: PregeneratedClientTextStore,
|
||||
heroes: Iterable[HeroT]
|
||||
@@ -1952,10 +1974,13 @@ class GamesManager(
|
||||
)
|
||||
|
||||
// Copy pregenerated texts (hero names, backstories) into the game's text store
|
||||
val populatedTextStore = GamesManager.withPopulatedPregeneratedTexts(
|
||||
clientTextStore = initialTextStore,
|
||||
heroes = engineAndResults.engine.currentState.heroes.values,
|
||||
factionIds = engineAndResults.engine.factionIds
|
||||
val populatedTextStore = GamesManager.withPopulatedTutorialTexts(
|
||||
clientTextStore = GamesManager.withPopulatedPregeneratedTexts(
|
||||
clientTextStore = initialTextStore,
|
||||
heroes = engineAndResults.engine.currentState.heroes.values,
|
||||
factionIds = engineAndResults.engine.factionIds
|
||||
),
|
||||
factionIds = Vector(TutorialDialogueContent.playerFactionId)
|
||||
)
|
||||
|
||||
// Player is Sadar Rakon (faction 3), AI factions are King (2), Bridget (4), and Hedrick (5)
|
||||
|
||||
@@ -27,6 +27,7 @@ import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.hero.backstory_version.BackstoryVersion
|
||||
import net.eagle0.eagle.model.state.hero.concrete.HeroC
|
||||
import net.eagle0.eagle.model.state.run_status.RunStatus
|
||||
import net.eagle0.eagle.model.view.action_result.TutorialDialogueContent
|
||||
import net.eagle0.eagle.service.controller.GameController
|
||||
import net.eagle0.eagle.service.controller.HumanPlayerClientConnectionState
|
||||
import net.eagle0.eagle.service.new_game_creation.{
|
||||
@@ -1179,6 +1180,16 @@ class GamesManagerTest extends AnyFlatSpec with MockFactory with Matchers with B
|
||||
val factionIds = controller.engine.currentState.factions.keys.toVector
|
||||
val chronicleId = "chronicle_update_371_11"
|
||||
|
||||
TutorialDialogueContent.textIds.foreach { textId =>
|
||||
controller.clientTextStore.completeTexts.keySet should contain(textId)
|
||||
}
|
||||
controller.clientTextStore
|
||||
.getCompleteTextsAccessibleTo(TutorialDialogueContent.playerFactionId)
|
||||
.map(_.id) should contain allElementsOf TutorialDialogueContent.textIds
|
||||
controller.clientTextStore.completeTexts(TutorialDialogueContent.openingFirstDialogueTextId).text should include(
|
||||
"\n\n"
|
||||
)
|
||||
|
||||
// A yearly chronicle text generated during the live tutorial session, then persisted.
|
||||
val _ = controller.clientTextStore
|
||||
.withAddedCompleteText(chronicleId, "A Realm in Ruin", factionIds)
|
||||
|
||||
+25
@@ -7,6 +7,7 @@ import scala.util.{Failure, Try}
|
||||
|
||||
import net.eagle0.eagle.library.settings.loaders.BattalionTypeLoader
|
||||
import net.eagle0.eagle.library.settings.HistoryBackend
|
||||
import net.eagle0.eagle.model.view.action_result.{TutorialDialogueContent, TutorialFocus}
|
||||
import net.eagle0.eagle.service.persistence.Persister
|
||||
import net.eagle0.util.hero_generation.LoadedHero
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
@@ -174,4 +175,28 @@ class TutorialGameCreationTest extends AnyFlatSpec with Matchers {
|
||||
gameState.heroes(100).nameTextId.shouldBe(LoadedHero.nameTextId("John Ranil"))
|
||||
gameState.heroes(101).nameTextId.shouldBe(LoadedHero.nameTextId("Elena Fyar"))
|
||||
}
|
||||
|
||||
it should "deliver the opening dialogue in the player's game-start result" in {
|
||||
val history = engineAndResults.engine.filteredHistoryWithUnfilteredCount(
|
||||
factionId = TutorialDialogueContent.playerFactionId,
|
||||
knownUnfilteredCount = 0
|
||||
)
|
||||
val dialogues = history.filteredResults.flatMap(_.tutorialDialogues)
|
||||
|
||||
dialogues.map(_.id).should(contain("tutorial_opening"))
|
||||
inside(dialogues.find(_.id == "tutorial_opening")) {
|
||||
case Some(opening) =>
|
||||
opening.steps.should(have.size(3))
|
||||
engineAndResults.engine.currentState.heroes(opening.steps.head.speakerHeroId).nameTextId shouldBe
|
||||
LoadedHero.nameTextId("Old Marek the Learned")
|
||||
opening.steps.map(_.dialogueTextId) shouldBe Vector(
|
||||
TutorialDialogueContent.openingFirstDialogueTextId,
|
||||
TutorialDialogueContent.openingSecondDialogueTextId,
|
||||
TutorialDialogueContent.openingThirdDialogueTextId
|
||||
)
|
||||
opening.steps(1).focus.shouldBe(Some(TutorialFocus.Province(provinceId = 14)))
|
||||
opening.steps(2).focus.shouldBe(Some(TutorialFocus.Button(buttonId = "FightButton")))
|
||||
opening.steps.last.instructionTextId.shouldBe(Some(TutorialDialogueContent.openingInstructionTextId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user