Add AI quest fulfillment for 7 more quest types

New quest command choosers:
- SpendOnFeastsQuestCommandChooser: Selects FeastCommand when quest active
- RestProvinceQuestCommandChooser: Rests in target province
- SendSuppliesQuestCommandChooser: Sends food to target province
- ExecutePrisonerQuestCommandChooser: Executes specific prisoner
- ReturnPrisonerQuestCommandChooser: Returns prisoner to faction
- StartEpidemicQuestCommandChooser: Starts epidemic in target province
- SwearBrotherhoodQuestCommandChooser: Swears brotherhood with target hero

Also adds Execute and Return helper methods to ManagePrisonersCommandSelector.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-08 16:37:46 -08:00
co-authored by Claude Opus 4.5
parent be1f9cc67c
commit 8a0b7d406b
11 changed files with 490 additions and 0 deletions
@@ -301,6 +301,7 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:alms_across_realm_quest_command_chooser",
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:alms_to_province_quest_command_chooser",
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:dismiss_specific_vassal_command_chooser",
"//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: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",
@@ -308,6 +309,12 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:quest_command_chooser",
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:release_all_prisoners_quest_command_chooser",
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:release_prisoner_quest_command_chooser",
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:rest_province_quest_command_chooser",
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:return_prisoner_quest_command_chooser",
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:send_supplies_quest_command_chooser",
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:spend_on_feasts_quest_command_chooser",
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:start_epidemic_quest_command_chooser",
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:swear_brotherhood_quest_command_chooser",
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:total_development_quest_command_chooser",
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:truce_count_quest_command_chooser",
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:truce_with_faction_quest_command_chooser",
@@ -6,6 +6,7 @@ import net.eagle0.eagle.library.util.command_choice_helpers.quest_command_select
AlmsAcrossRealmQuestCommandChooser,
AlmsToProvinceQuestCommandChooser,
DismissSpecificVassalCommandChooser,
ExecutePrisonerQuestCommandChooser,
ExilePrisonerQuestCommandChooser,
GiveToHeroesAcrossRealmQuestCommandChooser,
GiveToHeroesInProvinceQuestCommandChooser,
@@ -13,6 +14,12 @@ import net.eagle0.eagle.library.util.command_choice_helpers.quest_command_select
QuestCommandChooser,
ReleaseAllPrisonersQuestCommandChooser,
ReleasePrisonerQuestCommandChooser,
RestProvinceQuestCommandChooser,
ReturnPrisonerQuestCommandChooser,
SendSuppliesQuestCommandChooser,
SpendOnFeastsQuestCommandChooser,
StartEpidemicQuestCommandChooser,
SwearBrotherhoodQuestCommandChooser,
TotalDevelopmentQuestCommandChooser,
TruceCountQuestCommandChooser,
TruceWithFactionQuestCommandChooser,
@@ -37,7 +44,14 @@ object FulfillQuestsCommandSelector {
DismissSpecificVassalCommandChooser,
ReleasePrisonerQuestCommandChooser,
ExilePrisonerQuestCommandChooser,
ExecutePrisonerQuestCommandChooser,
ReturnPrisonerQuestCommandChooser,
ReleaseAllPrisonersQuestCommandChooser,
SpendOnFeastsQuestCommandChooser,
RestProvinceQuestCommandChooser,
SendSuppliesQuestCommandChooser,
StartEpidemicQuestCommandChooser,
SwearBrotherhoodQuestCommandChooser,
AllianceQuestCommandChooser
)
@@ -47,6 +47,28 @@ object ManagePrisonersCommandSelector {
): Option[CommandSelection] =
chosenCommandForPrisoner(actingFactionId, availableCommands, prisonerHeroId, PrisonerManagementOption.Exile)
/** Execute a specific prisoner. */
def chosenExecuteCommand(
actingFactionId: FactionId,
availableCommands: Vector[AvailableCommand],
prisonerHeroId: HeroId
): Option[CommandSelection] =
chosenCommandForPrisoner(actingFactionId, availableCommands, prisonerHeroId, PrisonerManagementOption.Execute)
/** Return a specific prisoner to a specific faction. */
def chosenReturnCommand(
actingFactionId: FactionId,
availableCommands: Vector[AvailableCommand],
prisonerHeroId: HeroId,
toFactionId: FactionId
): Option[CommandSelection] =
chosenCommandForPrisoner(
actingFactionId,
availableCommands,
prisonerHeroId,
PrisonerManagementOption.Return(toFactionId)
)
/** Find any prisoner that can be released and select to release them. */
def chosenReleaseAnyPrisonerCommand(
actingFactionId: FactionId,
@@ -330,6 +330,161 @@ scala_library(
],
)
scala_library(
name = "execute_prisoner_quest_command_chooser",
srcs = ["ExecutePrisonerQuestCommandChooser.scala"],
visibility = [
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers:__subpackages__",
"//src/test/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:__pkg__",
],
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/library/util/command_choice_helpers:manage_prisoners_command_selector",
"//src/main/scala/net/eagle0/eagle/library/util/hero:hero_utils",
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/quest",
],
)
scala_library(
name = "rest_province_quest_command_chooser",
srcs = ["RestProvinceQuestCommandChooser.scala"],
visibility = [
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers:__subpackages__",
"//src/test/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:__pkg__",
],
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/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/quest",
],
)
scala_library(
name = "return_prisoner_quest_command_chooser",
srcs = ["ReturnPrisonerQuestCommandChooser.scala"],
visibility = [
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers:__subpackages__",
"//src/test/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:__pkg__",
],
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/library/util/command_choice_helpers:manage_prisoners_command_selector",
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/quest",
],
)
scala_library(
name = "send_supplies_quest_command_chooser",
srcs = ["SendSuppliesQuestCommandChooser.scala"],
visibility = [
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers:__subpackages__",
"//src/test/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:__pkg__",
],
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/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/quest",
],
)
scala_library(
name = "spend_on_feasts_quest_command_chooser",
srcs = ["SpendOnFeastsQuestCommandChooser.scala"],
visibility = [
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers:__subpackages__",
"//src/test/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:__pkg__",
],
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/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/quest",
],
)
scala_library(
name = "start_epidemic_quest_command_chooser",
srcs = ["StartEpidemicQuestCommandChooser.scala"],
visibility = [
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers:__subpackages__",
"//src/test/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:__pkg__",
],
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/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/quest",
],
)
scala_library(
name = "swear_brotherhood_quest_command_chooser",
srcs = ["SwearBrotherhoodQuestCommandChooser.scala"],
visibility = [
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers:__subpackages__",
"//src/test/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:__pkg__",
],
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/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/quest",
],
)
scala_library(
name = "unaffiliated_hero_with_quest",
srcs = ["UnaffiliatedHeroWithQuest.scala"],
@@ -0,0 +1,48 @@
package net.eagle0.eagle.library.util.command_choice_helpers.quest_command_selectors
import net.eagle0.eagle.library.util.command_choice_helpers.ManagePrisonersCommandSelector
import net.eagle0.eagle.library.util.hero.HeroUtils
import net.eagle0.eagle.library.util.CommandSelection
import net.eagle0.eagle.model.state.command.available.AvailableCommand
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.model.state.quest.ExecutePrisonerQuest
import net.eagle0.eagle.FactionId
object ExecutePrisonerQuestCommandChooser extends DeterministicQuestCommandChooser {
override def chosenDeterministicCommand(
actingFactionId: FactionId,
gameState: GameState,
availableCommands: Vector[AvailableCommand],
uhsWithQuests: Vector[UnaffiliatedHeroWithQuest]
): Option[CommandSelection] = {
// Get all faction heads (first leader of each faction)
val factionHeadIds = gameState.factions.values.flatMap(_.leaderIds.headOption).toSet
uhsWithQuests.flatMap { uhWithQuest =>
uhWithQuest.quest match {
case quest: ExecutePrisonerQuest =>
// Look up both heroes
val questioner = gameState.heroes.get(uhWithQuest.heroId)
val prisoner = gameState.heroes.get(quest.prisonerHeroId)
(questioner, prisoner) match {
case (Some(q), Some(p)) =>
val prisonerIsFactionHead = factionHeadIds.contains(p.id)
val questionerIsBetter = HeroUtils.power(q) > HeroUtils.power(p)
// Only execute if questioner is better than prisoner, or prisoner is faction head
if prisonerIsFactionHead || questionerIsBetter then
ManagePrisonersCommandSelector.chosenExecuteCommand(
actingFactionId = actingFactionId,
availableCommands = availableCommands,
prisonerHeroId = quest.prisonerHeroId
)
else None
case _ => None
}
case _ => None
}
}.headOption
.map(_.withReason("fulfill ExecutePrisoner quest"))
}
}
@@ -0,0 +1,37 @@
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.RestAvailable
import net.eagle0.eagle.model.state.command.selected.SelectedCommand.RestSelected
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.model.state.quest.RestProvinceQuest
import net.eagle0.eagle.FactionId
object RestProvinceQuestCommandChooser 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: RestProvinceQuest => q.targetProvinceId }
.toSet
if targetProvinceIds.isEmpty then return None
availableCommands.collect { case ra: RestAvailable => ra }
.find(ra => targetProvinceIds.contains(ra.actingProvinceId))
.map { restAvailable =>
CommandSelection(
actingFactionId = actingFactionId,
actingProvinceId = restAvailable.actingProvinceId,
available = restAvailable,
selected = RestSelected,
reason = "fulfill RestProvince quest"
)
}
}
}
@@ -0,0 +1,30 @@
package net.eagle0.eagle.library.util.command_choice_helpers.quest_command_selectors
import net.eagle0.eagle.library.util.command_choice_helpers.ManagePrisonersCommandSelector
import net.eagle0.eagle.library.util.CommandSelection
import net.eagle0.eagle.model.state.command.available.AvailableCommand
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.model.state.quest.ReturnPrisonerQuest
import net.eagle0.eagle.FactionId
object ReturnPrisonerQuestCommandChooser extends DeterministicQuestCommandChooser {
override def chosenDeterministicCommand(
actingFactionId: FactionId,
gameState: GameState,
availableCommands: Vector[AvailableCommand],
uhsWithQuests: Vector[UnaffiliatedHeroWithQuest]
): Option[CommandSelection] =
uhsWithQuests
.map(_.quest)
.collect { case q: ReturnPrisonerQuest => q }
.flatMap { quest =>
ManagePrisonersCommandSelector.chosenReturnCommand(
actingFactionId = actingFactionId,
availableCommands = availableCommands,
prisonerHeroId = quest.prisonerHeroId,
toFactionId = quest.toFactionId
)
}
.headOption
.map(_.withReason("fulfill ReturnPrisoner quest"))
}
@@ -0,0 +1,60 @@
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.SendSuppliesAvailable
import net.eagle0.eagle.model.state.command.selected.SelectedCommand.SendSuppliesSelected
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.model.state.quest.SendSuppliesQuest
import net.eagle0.eagle.FactionId
object SendSuppliesQuestCommandChooser extends DeterministicQuestCommandChooser {
override def chosenDeterministicCommand(
actingFactionId: FactionId,
gameState: GameState,
availableCommands: Vector[AvailableCommand],
uhsWithQuests: Vector[UnaffiliatedHeroWithQuest]
): Option[CommandSelection] = {
val sendSuppliesQuests = uhsWithQuests
.map(_.quest)
.collect { case q: SendSuppliesQuest => q }
if sendSuppliesQuests.isEmpty then return None
// Group quests by target province and find the max food needed
val foodNeededByProvince = sendSuppliesQuests
.groupBy(_.targetProvinceId)
.view
.mapValues { quests =>
quests.map(q => q.componentCount - q.componentsFulfilled).max
}
.toMap
availableCommands.collect { case ssa: SendSuppliesAvailable => ssa }.flatMap { sendSuppliesAvailable =>
// Find a target province that matches a quest and is available
foodNeededByProvince.keys
.find(sendSuppliesAvailable.availableDestinationProvinceIds.contains)
.map { targetProvinceId =>
val foodNeeded = foodNeededByProvince(targetProvinceId)
val foodToSend = foodNeeded.min(sendSuppliesAvailable.foodAvailable)
val actingHero = sendSuppliesAvailable.availableHeroIds.head
(sendSuppliesAvailable, targetProvinceId, foodToSend, actingHero)
}
}.headOption.map {
case (sendSuppliesAvailable, targetProvinceId, foodToSend, actingHero) =>
CommandSelection(
actingFactionId = actingFactionId,
actingProvinceId = sendSuppliesAvailable.actingProvinceId,
available = sendSuppliesAvailable,
selected = SendSuppliesSelected(
gold = 0,
food = foodToSend,
actingHeroId = actingHero,
destinationProvinceId = targetProvinceId
),
reason = "fulfill SendSupplies quest"
)
}
}
}
@@ -0,0 +1,34 @@
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.FeastAvailable
import net.eagle0.eagle.model.state.command.selected.SelectedCommand.FeastSelected
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.model.state.quest.SpendOnFeastsQuest
import net.eagle0.eagle.FactionId
object SpendOnFeastsQuestCommandChooser extends DeterministicQuestCommandChooser {
override def chosenDeterministicCommand(
actingFactionId: FactionId,
gameState: GameState,
availableCommands: Vector[AvailableCommand],
uhsWithQuests: Vector[UnaffiliatedHeroWithQuest]
): Option[CommandSelection] = {
val hasSpendOnFeastsQuest = uhsWithQuests
.map(_.quest)
.exists(_.isInstanceOf[SpendOnFeastsQuest])
if !hasSpendOnFeastsQuest then return None
availableCommands.collectFirst { case fa: FeastAvailable => fa }.map { feastAvailable =>
CommandSelection(
actingFactionId = actingFactionId,
actingProvinceId = feastAvailable.actingProvinceId,
available = feastAvailable,
selected = FeastSelected,
reason = "fulfill SpendOnFeasts quest"
)
}
}
}
@@ -0,0 +1,43 @@
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.StartEpidemicAvailable
import net.eagle0.eagle.model.state.command.selected.SelectedCommand.StartEpidemicSelected
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.model.state.quest.StartEpidemicQuest
import net.eagle0.eagle.FactionId
object StartEpidemicQuestCommandChooser 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: StartEpidemicQuest => q.provinceId }
.toSet
if targetProvinceIds.isEmpty then return None
availableCommands.collect { case sea: StartEpidemicAvailable => sea }.flatMap { startEpidemicAvailable =>
// Find a target province that matches a quest and is in available options
startEpidemicAvailable.options
.find(opt => targetProvinceIds.contains(opt.targetProvinceId))
.map { matchingOption =>
CommandSelection(
actingFactionId = actingFactionId,
actingProvinceId = startEpidemicAvailable.actingProvinceId,
available = startEpidemicAvailable,
selected = StartEpidemicSelected(
selectedProvinceId = matchingOption.targetProvinceId,
actingHeroId = startEpidemicAvailable.availableHeroIds.head
),
reason = "fulfill StartEpidemic quest"
)
}
}.headOption
}
}
@@ -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.SwearBrotherhoodAvailable
import net.eagle0.eagle.model.state.command.selected.SelectedCommand.SwearBrotherhoodSelected
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.model.state.quest.SwearBrotherhoodWithHeroQuest
import net.eagle0.eagle.FactionId
object SwearBrotherhoodQuestCommandChooser extends DeterministicQuestCommandChooser {
override def chosenDeterministicCommand(
actingFactionId: FactionId,
gameState: GameState,
availableCommands: Vector[AvailableCommand],
uhsWithQuests: Vector[UnaffiliatedHeroWithQuest]
): Option[CommandSelection] = {
val targetHeroIds = uhsWithQuests
.map(_.quest)
.collect { case q: SwearBrotherhoodWithHeroQuest => q.targetHeroId }
.toSet
if targetHeroIds.isEmpty then return None
availableCommands.collect { case sba: SwearBrotherhoodAvailable => sba }.flatMap { swearBrotherhoodAvailable =>
// Find a hero that matches a quest and is in available heroes
swearBrotherhoodAvailable.availableHeroes
.find(hab => targetHeroIds.contains(hab.heroId))
.map { matchingHero =>
CommandSelection(
actingFactionId = actingFactionId,
actingProvinceId = swearBrotherhoodAvailable.actingProvinceId,
available = swearBrotherhoodAvailable,
selected = SwearBrotherhoodSelected(newBrotherHeroId = matchingHero.heroId),
reason = "fulfill SwearBrotherhood quest"
)
}
}.headOption
}
}