mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 20:55:44 +00:00
Generate focus province quests (#8704)
* Add focus province quest type * Generate focus province quests
This commit is contained in:
@@ -353,6 +353,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:execute_prisoner_quest_command_chooser",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:exile_prisoner_quest_command_chooser",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:fight_beasts_alone_quest_command_chooser",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:focus_province_quest_command_chooser",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:give_to_heroes_across_realm_quest_command_chooser",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:give_to_heroes_in_province_quest_command_chooser",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:grand_army_quest_command_chooser",
|
||||
|
||||
+4
@@ -14,6 +14,7 @@ import net.eagle0.eagle.library.util.command_choice_helpers.quest_command_select
|
||||
ExecutePrisonerQuestCommandChooser,
|
||||
ExilePrisonerQuestCommandChooser,
|
||||
FightBeastsAloneQuestCommandChooser,
|
||||
FocusProvinceQuestCommandChooser,
|
||||
GiveToHeroesAcrossRealmQuestCommandChooser,
|
||||
GiveToHeroesInProvinceQuestCommandChooser,
|
||||
GrandArmyQuestCommandChooser,
|
||||
@@ -43,6 +44,7 @@ import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.quest.{
|
||||
AlmsAcrossRealmQuest,
|
||||
DevelopProvincesQuest,
|
||||
FocusProvinceQuest,
|
||||
GiveToHeroesAcrossRealmQuest,
|
||||
MobilizeProvincesQuest,
|
||||
Quest,
|
||||
@@ -77,6 +79,7 @@ object FulfillQuestsCommandSelector {
|
||||
ApprehendOutlawQuestCommandChooser,
|
||||
SpendOnFeastsQuestCommandChooser,
|
||||
RestProvinceQuestCommandChooser,
|
||||
FocusProvinceQuestCommandChooser,
|
||||
DevelopProvincesQuestCommandChooser,
|
||||
MobilizeProvincesQuestCommandChooser,
|
||||
SendSuppliesQuestCommandChooser,
|
||||
@@ -128,6 +131,7 @@ object FulfillQuestsCommandSelector {
|
||||
case _: ReconSpecificProvincesQuest => true
|
||||
case _: DevelopProvincesQuest => true
|
||||
case _: MobilizeProvincesQuest => true
|
||||
case _: FocusProvinceQuest => true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
|
||||
+23
@@ -384,6 +384,28 @@ scala_library(
|
||||
],
|
||||
)
|
||||
|
||||
scala_library(
|
||||
name = "focus_province_quest_command_chooser",
|
||||
srcs = ["FocusProvinceQuestCommandChooser.scala"],
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers:__subpackages__",
|
||||
],
|
||||
exports = [
|
||||
":quest_command_chooser",
|
||||
],
|
||||
deps = [
|
||||
":quest_command_chooser",
|
||||
":unaffiliated_hero_with_quest",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:command_selection",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/selected",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/quest",
|
||||
],
|
||||
)
|
||||
|
||||
scala_library(
|
||||
name = "release_all_prisoners_quest_command_chooser",
|
||||
srcs = ["ReleaseAllPrisonersQuestCommandChooser.scala"],
|
||||
@@ -494,6 +516,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/common",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/command/selected",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
],
|
||||
)
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package net.eagle0.eagle.library.util.command_choice_helpers.quest_command_selectors
|
||||
|
||||
import net.eagle0.eagle.library.util.CommandSelection
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.IssueOrdersAvailable
|
||||
import net.eagle0.eagle.model.state.command.selected.ProvinceOrder
|
||||
import net.eagle0.eagle.model.state.command.selected.SelectedCommand.IssueOrdersSelected
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.quest.FocusProvinceQuest
|
||||
import net.eagle0.eagle.FactionId
|
||||
|
||||
object FocusProvinceQuestCommandChooser extends DeterministicQuestCommandChooser {
|
||||
override def chosenDeterministicCommand(
|
||||
actingFactionId: FactionId,
|
||||
gameState: GameState,
|
||||
availableCommands: Vector[AvailableCommand],
|
||||
uhsWithQuests: Vector[UnaffiliatedHeroWithQuest]
|
||||
): Option[CommandSelection] = {
|
||||
val targetProvinceIds = uhsWithQuests.map(_.quest).collect { case q: FocusProvinceQuest => q.targetProvinceId }
|
||||
val currentFocus = gameState.factions.get(actingFactionId).flatMap(_.focusProvinceId)
|
||||
|
||||
if currentFocus.exists(targetProvinceIds.contains) then None
|
||||
else
|
||||
availableCommands.collectFirst { case command: IssueOrdersAvailable => command }.flatMap { issueOrders =>
|
||||
targetProvinceIds.find(issueOrders.availableFocusProvinces.contains).map { targetProvinceId =>
|
||||
CommandSelection(
|
||||
actingFactionId = actingFactionId,
|
||||
actingProvinceId = issueOrders.actingProvinceId,
|
||||
available = issueOrders,
|
||||
selected = IssueOrdersSelected(
|
||||
newOrders = issueOrders.currentOrders.map(order => ProvinceOrder(order.provinceId, order.orderType)),
|
||||
newFocusProvince = Some(targetProvinceId)
|
||||
),
|
||||
reason = "fulfill FocusProvince quest"
|
||||
)
|
||||
}
|
||||
}
|
||||
end if
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -60,7 +60,7 @@ object ProvinceOrderQuestHelper {
|
||||
available = issueOrders,
|
||||
selected = IssueOrdersSelected(
|
||||
newOrders = newOrders,
|
||||
newFocusProvince = issueOrders.availableFocusProvinces.headOption
|
||||
newFocusProvince = gameState.factions.get(actingFactionId).flatMap(_.focusProvinceId)
|
||||
),
|
||||
reason = reason
|
||||
)
|
||||
|
||||
+34
-3
@@ -45,6 +45,7 @@ import net.eagle0.eagle.model.state.quest.{
|
||||
ExilePrisonerQuest,
|
||||
ExpandToProvincesQuest,
|
||||
FightBeastsAloneQuest,
|
||||
FocusProvinceQuest,
|
||||
GiveToHeroesAcrossRealmQuest,
|
||||
GiveToHeroesInProvinceQuest,
|
||||
GrandArmyQuest,
|
||||
@@ -157,6 +158,7 @@ object QuestCreationUtils {
|
||||
swearBrotherhoodWithHeroQuests,
|
||||
QuestCreatorFrom(betrayAllyQuests),
|
||||
QuestCreatorFrom(borderSecurityQuests),
|
||||
focusProvinceQuests,
|
||||
QuestCreatorFrom(winBattleOutnumberedQuests),
|
||||
winBattlesQuests,
|
||||
QuestCreatorFrom(apprehendOutlawQuests),
|
||||
@@ -889,8 +891,38 @@ object QuestCreationUtils {
|
||||
factions: Vector[FactionT],
|
||||
@unused battalions: Vector[BattalionT]
|
||||
): Vector[Quest] =
|
||||
borderProvinces(province, allProvinces, factions)
|
||||
.map(borderProvince => BorderSecurityQuest(targetProvinceId = borderProvince.id))
|
||||
end borderSecurityQuests
|
||||
|
||||
private def focusProvinceQuests(
|
||||
province: ProvinceT,
|
||||
allProvinces: Vector[ProvinceT],
|
||||
factions: Vector[FactionT],
|
||||
@unused battalions: Vector[BattalionT],
|
||||
functionalRandom: FunctionalRandom
|
||||
): RandomState[Vector[Quest]] = {
|
||||
val candidates = borderProvinces(province, allProvinces, factions)
|
||||
|
||||
if candidates.isEmpty then RandomState(Vector.empty, functionalRandom)
|
||||
else
|
||||
functionalRandom.nextIntInclusive(3, 6).map { targetMonths =>
|
||||
candidates.map { borderProvince =>
|
||||
FocusProvinceQuest(
|
||||
componentCount = targetMonths,
|
||||
componentsFulfilled = 0,
|
||||
targetProvinceId = borderProvince.id
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def borderProvinces(
|
||||
province: ProvinceT,
|
||||
allProvinces: Vector[ProvinceT],
|
||||
factions: Vector[FactionT]
|
||||
): Vector[ProvinceT] = {
|
||||
val factionId = province.getRulingFactionId
|
||||
// Create a quest for each province that has neighbors controlled by non-allied factions
|
||||
FactionUtils
|
||||
.provinces(factionId, allProvinces)
|
||||
.filter { factionProvince =>
|
||||
@@ -903,8 +935,7 @@ object QuestCreationUtils {
|
||||
}
|
||||
}
|
||||
}
|
||||
.map(borderProvince => BorderSecurityQuest(targetProvinceId = borderProvince.id))
|
||||
end borderSecurityQuests
|
||||
}
|
||||
|
||||
private def winBattleOutnumberedQuests(
|
||||
@unused province: ProvinceT,
|
||||
|
||||
+75
-2
@@ -7,8 +7,14 @@ import net.eagle0.eagle.library.util.CommandSelection
|
||||
import net.eagle0.eagle.model.state.{GameType, RoundPhase}
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.*
|
||||
import net.eagle0.eagle.model.state.command.common.{DiplomacyOptionType, ImprovementType}
|
||||
import net.eagle0.eagle.model.state.command.selected.SelectedCommand.{AlmsSelected, DiplomacySelected, ImproveSelected}
|
||||
import net.eagle0.eagle.model.state.command.common.{DiplomacyOptionType, ImprovementType, ProvinceOrderType}
|
||||
import net.eagle0.eagle.model.state.command.selected.ProvinceOrder
|
||||
import net.eagle0.eagle.model.state.command.selected.SelectedCommand.{
|
||||
AlmsSelected,
|
||||
DiplomacySelected,
|
||||
ImproveSelected,
|
||||
IssueOrdersSelected
|
||||
}
|
||||
import net.eagle0.eagle.model.state.faction.concrete.FactionC
|
||||
import net.eagle0.eagle.model.state.faction.FactionRelationship
|
||||
import net.eagle0.eagle.model.state.faction.FactionRelationship.RelationshipLevel.{Hostile, Truce}
|
||||
@@ -21,6 +27,7 @@ import net.eagle0.eagle.model.state.province.ProvinceT
|
||||
import net.eagle0.eagle.model.state.quest.{
|
||||
AlmsAcrossRealmQuest,
|
||||
AlmsToProvinceQuest,
|
||||
FocusProvinceQuest,
|
||||
ImproveAgricultureQuest,
|
||||
TruceCountQuest,
|
||||
TruceWithFactionQuest,
|
||||
@@ -264,6 +271,72 @@ class FulfillQuestsCommandSelectorTest extends AnyFlatSpec with Matchers with Be
|
||||
)
|
||||
}
|
||||
|
||||
it should "set the target focus for a focus province quest in a vassal-ruled province" in {
|
||||
val focusQuestUhs = Vector(
|
||||
UnaffiliatedHeroC(
|
||||
heroId = 96,
|
||||
unaffiliatedHeroType = UnaffiliatedHeroType.Resident,
|
||||
recruitmentInfo = RecruitmentInfo.HasQuest(
|
||||
FocusProvinceQuest(
|
||||
componentCount = 4,
|
||||
componentsFulfilled = 0,
|
||||
targetProvinceId = 22
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val gameStateWithFocusQuest = gameState.copy(
|
||||
provinces = gameState.provinces.updated(
|
||||
22,
|
||||
gameState
|
||||
.provinces(22)
|
||||
.updateWith(
|
||||
rulingHeroId = Some(11),
|
||||
rulingFactionHeroIds = Vector(11),
|
||||
unaffiliatedHeroes = focusQuestUhs
|
||||
)
|
||||
)
|
||||
)
|
||||
val issueOrders = IssueOrdersAvailable(
|
||||
currentOrders = Vector(
|
||||
ProvinceOrders(16, ProvinceOrderType.Entrust),
|
||||
ProvinceOrders(18, ProvinceOrderType.Develop),
|
||||
ProvinceOrders(22, ProvinceOrderType.Mobilize)
|
||||
),
|
||||
availableOrders = Vector(
|
||||
ProvinceOrderType.Develop,
|
||||
ProvinceOrderType.Entrust,
|
||||
ProvinceOrderType.Mobilize
|
||||
),
|
||||
availableFocusProvinces = Vector(16, 18, 22),
|
||||
actingProvinceId = 16
|
||||
)
|
||||
|
||||
FulfillQuestsCommandSelector
|
||||
.chosenFulfillEasyQuestsCommand(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gameStateWithFocusQuest,
|
||||
availableCommands = Vector(issueOrders),
|
||||
functionalRandom = SeededRandom(0xcafe)
|
||||
)
|
||||
.newValue should contain(
|
||||
CommandSelection(
|
||||
actingFactionId = actingFactionId,
|
||||
actingProvinceId = 16,
|
||||
available = issueOrders,
|
||||
selected = IssueOrdersSelected(
|
||||
newOrders = Vector(
|
||||
ProvinceOrder(16, ProvinceOrderType.Entrust),
|
||||
ProvinceOrder(18, ProvinceOrderType.Develop),
|
||||
ProvinceOrder(22, ProvinceOrderType.Mobilize)
|
||||
),
|
||||
newFocusProvince = Some(22)
|
||||
),
|
||||
reason = "fulfill FocusProvince quest"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
it should "ignore local province quests from vassal-ruled provinces" in {
|
||||
val almsCommand = AlmsAvailable(
|
||||
actingProvinceId = 22,
|
||||
|
||||
+23
-2
@@ -37,7 +37,8 @@ class DevelopProvincesQuestCommandChooserTest extends AnyFlatSpec with Matchers
|
||||
|
||||
private def createGameState(
|
||||
province2Neighbors: Vector[Neighbor] = Vector.empty,
|
||||
province3Neighbors: Vector[Neighbor] = Vector.empty
|
||||
province3Neighbors: Vector[Neighbor] = Vector.empty,
|
||||
focusProvinceId: Option[ProvinceId] = None
|
||||
): GameState = {
|
||||
val factionLeader = HeroC(id = factionLeaderId, factionId = Some(actingFactionId))
|
||||
val hero = HeroC(id = heroId, factionId = None)
|
||||
@@ -66,7 +67,8 @@ class DevelopProvincesQuestCommandChooserTest extends AnyFlatSpec with Matchers
|
||||
id = actingFactionId,
|
||||
factionHeadId = factionLeaderId,
|
||||
name = "Test Faction",
|
||||
leaderIds = Vector(factionLeaderId)
|
||||
leaderIds = Vector(factionLeaderId),
|
||||
focusProvinceId = focusProvinceId
|
||||
)
|
||||
val enemyFaction = FactionC(
|
||||
id = enemyFactionId,
|
||||
@@ -233,6 +235,25 @@ class DevelopProvincesQuestCommandChooserTest extends AnyFlatSpec with Matchers
|
||||
}
|
||||
}
|
||||
|
||||
it should "preserve the faction focus when changing province orders" in {
|
||||
val result = DevelopProvincesQuestCommandChooser.chosenDeterministicCommand(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = createGameState(focusProvinceId = Some(province2Id)),
|
||||
availableCommands = Vector(issueOrdersAvailable),
|
||||
uhsWithQuests = Vector(
|
||||
UnaffiliatedHeroWithQuest(pid = province1Id, heroId = heroId, quest = developQuest)
|
||||
)
|
||||
)
|
||||
|
||||
inside(result) {
|
||||
case Some(command) =>
|
||||
inside(command.selected) {
|
||||
case selected: IssueOrdersSelected =>
|
||||
selected.newFocusProvince shouldBe Some(province2Id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "return None when IssueOrders is not available" in {
|
||||
val result = DevelopProvincesQuestCommandChooser.chosenDeterministicCommand(
|
||||
actingFactionId = actingFactionId,
|
||||
|
||||
+42
@@ -37,6 +37,7 @@ import net.eagle0.eagle.model.state.quest.{
|
||||
DismissSpecificVassalQuest,
|
||||
ExecutePrisonerQuest,
|
||||
ExilePrisonerQuest,
|
||||
FocusProvinceQuest,
|
||||
GiveToHeroesAcrossRealmQuest,
|
||||
GiveToHeroesInProvinceQuest,
|
||||
GrandArmyQuest,
|
||||
@@ -140,6 +141,47 @@ class QuestCreationUtilsTest extends AnyFlatSpec with Matchers with BeforeAndAft
|
||||
)
|
||||
)
|
||||
|
||||
"availableQuests" should "include a focus quest for an owned border province" in {
|
||||
val focusQuests = QuestCreationUtils
|
||||
.availableQuests(
|
||||
province = province,
|
||||
allProvinces = allProvinces,
|
||||
factions = factions,
|
||||
battalions = Vector.empty,
|
||||
heroes = Vector.empty,
|
||||
functionalRandom = new JankyRandom(new Random(1L))
|
||||
)
|
||||
.newValue
|
||||
.collect { case q: FocusProvinceQuest => q }
|
||||
|
||||
focusQuests should have size 1
|
||||
inside(focusQuests.head) {
|
||||
case quest: FocusProvinceQuest =>
|
||||
quest.targetProvinceId shouldBe province.id
|
||||
quest.componentCount should be >= 3
|
||||
quest.componentCount should be <= 6
|
||||
quest.componentsFulfilled shouldBe 0
|
||||
}
|
||||
}
|
||||
|
||||
it should "not include a focus quest when the faction has no border province" in {
|
||||
val interiorProvince = province.copy(neighbors = Vector(Neighbor(immediate1.id, 1)))
|
||||
val ownedNeighbor = immediate1.copy(rulingFactionId = Some(fid))
|
||||
|
||||
QuestCreationUtils
|
||||
.availableQuests(
|
||||
province = interiorProvince,
|
||||
allProvinces = Vector(interiorProvince, ownedNeighbor),
|
||||
factions = factions,
|
||||
battalions = Vector.empty,
|
||||
heroes = Vector.empty,
|
||||
functionalRandom = new JankyRandom(new Random(1L))
|
||||
)
|
||||
.newValue
|
||||
.collect { case q: FocusProvinceQuest => q }
|
||||
.shouldBe(empty)
|
||||
}
|
||||
|
||||
"availableQuests" should "include an agriculture improvement event if it is possible to raise enough" in
|
||||
forAtLeast(
|
||||
1,
|
||||
|
||||
Reference in New Issue
Block a user