Prioritize valuable safe upgrade quests (#8705)

This commit is contained in:
2026-07-18 14:28:01 -07:00
committed by GitHub
parent da0d1c04fd
commit f7d79639b5
2 changed files with 171 additions and 14 deletions
@@ -1,6 +1,6 @@
package net.eagle0.eagle.library.util.command_choice_helpers.quest_command_selectors
import net.eagle0.eagle.{FactionId, ProvinceId}
import net.eagle0.eagle.{FactionId, HeroId, ProvinceId}
import net.eagle0.eagle.library.settings.PortionOfCapacityForUpgradeBattalionQuest
import net.eagle0.eagle.library.util.command_choice_helpers.{
ImproveCommandSelector,
@@ -33,6 +33,7 @@ import net.eagle0.eagle.model.state.command.selected.SelectedCommand.{
VisitTownSelected
}
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.model.state.hero.Profession.Mage
import net.eagle0.eagle.model.state.province.ProvinceT
import net.eagle0.eagle.model.state.quest.UpgradeBattalionQuest
@@ -45,10 +46,10 @@ object UpgradeBattalionQuestCommandChooser extends DeterministicQuestCommandChoo
uhsWithQuests: Vector[UnaffiliatedHeroWithQuest]
): Option[CommandSelection] =
uhsWithQuests.collect {
case UnaffiliatedHeroWithQuest(pid, _, q: UpgradeBattalionQuest) => (pid, q)
case UnaffiliatedHeroWithQuest(pid, heroId, q: UpgradeBattalionQuest) => (pid, heroId, q)
}.iterator.flatMap {
case (questPid, quest) =>
commandForQuest(actingFactionId, gameState, availableCommands, questPid, quest).iterator
case (questPid, questHeroId, quest) =>
commandForQuest(actingFactionId, gameState, availableCommands, questPid, questHeroId, quest).iterator
}.nextOption()
private def commandForQuest(
@@ -56,6 +57,7 @@ object UpgradeBattalionQuestCommandChooser extends DeterministicQuestCommandChoo
gameState: GameState,
availableCommands: Vector[AvailableCommand],
questPid: ProvinceId,
questHeroId: HeroId,
quest: UpgradeBattalionQuest
): Option[CommandSelection] = {
val province = gameState.provinces(questPid)
@@ -83,15 +85,52 @@ object UpgradeBattalionQuestCommandChooser extends DeterministicQuestCommandChoo
)
.orElse(trainCompletes(actingFactionId, availableCommands, province, quest, full))
.orElse(marchPrequalified(actingFactionId, gameState, availableCommands, questPid, quest, bt, full))
.orElse(organizeInQuestProvince(actingFactionId, gameState, availableCommands, province, quest, matching))
.orElse(marchUnqualified(actingFactionId, gameState, availableCommands, questPid, quest, bt, full))
.orElse(developForBattalionType(actingFactionId, gameState, availableCommands, province, quest, bt, matching))
.orElse(trainProgress(actingFactionId, availableCommands, province, quest, best, threshold))
.orElse(developInfrastructure(actingFactionId, availableCommands, province, quest, best, threshold))
.orElse(armProgress(actingFactionId, availableCommands, province, quest, best, threshold))
.orElse {
Option
.when(
isHighValueQuestGiver(gameState, questHeroId) &&
isSafeForLongPreparation(actingFactionId, gameState, province)
) {
organizeInQuestProvince(actingFactionId, gameState, availableCommands, province, quest, matching)
.orElse(marchUnqualified(actingFactionId, gameState, availableCommands, questPid, quest, bt, full))
.orElse(
developForBattalionType(
actingFactionId,
gameState,
availableCommands,
province,
quest,
bt,
matching
)
)
.orElse(trainProgress(actingFactionId, availableCommands, province, quest, best, threshold))
.orElse(developInfrastructure(actingFactionId, availableCommands, province, quest, best, threshold))
.orElse(armProgress(actingFactionId, availableCommands, province, quest, best, threshold))
}
.flatten
.map(_.withOuterReason("safe province and high-value Mage quest giver"))
}
}
}
private def isHighValueQuestGiver(gameState: GameState, questHeroId: HeroId): Boolean =
gameState.heroes.get(questHeroId).exists(_.profession == Mage)
private def isSafeForLongPreparation(
actingFactionId: FactionId,
gameState: GameState,
province: ProvinceT
): Boolean = {
val factions = gameState.factions.values.toVector
FactionUtils
.hostileNeighbors(province, actingFactionId, gameState.provinces, factions)
.isEmpty &&
!province.incomingArmies.exists(incomingArmy =>
FactionUtils.factionsAreHostile(actingFactionId, incomingArmy.army.factionId, factions)
)
}
private def armCostFor(
costPerTroop: Double,
oldArmament: Double,
@@ -2,7 +2,7 @@ package net.eagle0.eagle.library.util.command_choice_helpers.quest_command_selec
import net.eagle0.eagle.{BattalionId, FactionId, HeroId, ProvinceId}
import net.eagle0.eagle.library.settings.{FoodIncreasePerAgriculture, GoldPerHeroHeldBack, MinSupportForTaxes}
import net.eagle0.eagle.model.state.{BattalionType, BattalionTypeId, GameType, RoundPhase}
import net.eagle0.eagle.model.state.{Army, BattalionType, BattalionTypeId, GameType, MovingArmy, RoundPhase, Supplies}
import net.eagle0.eagle.model.state.battalion.concrete.BattalionC
import net.eagle0.eagle.model.state.command.available.AvailableCommand.{
ArmTroopsAvailable,
@@ -36,6 +36,8 @@ import net.eagle0.eagle.model.state.date.Date.Month.June
import net.eagle0.eagle.model.state.faction.concrete.FactionC
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.model.state.hero.concrete.HeroC
import net.eagle0.eagle.model.state.hero.Profession
import net.eagle0.eagle.model.state.hero.Profession.{Mage, NoProfession}
import net.eagle0.eagle.model.state.province.concrete.ProvinceC
import net.eagle0.eagle.model.state.province.Neighbor
import net.eagle0.eagle.model.state.quest.{ImproveAgricultureQuest, UpgradeBattalionQuest}
@@ -116,10 +118,12 @@ class UpgradeBattalionQuestCommandChooserTest extends AnyFlatSpec with Matchers
province: ProvinceC,
battalions: Map[BattalionId, BattalionC] = Map.empty,
otherProvinces: Map[ProvinceId, ProvinceC] = Map.empty,
extraHeroes: Map[HeroId, HeroC] = Map.empty
extraHeroes: Map[HeroId, HeroC] = Map.empty,
extraFactions: Map[FactionId, FactionC] = Map.empty,
questHeroProfession: Profession = Mage
): GameState = {
val factionLeader = HeroC(id = factionLeaderId, factionId = Some(actingFactionId))
val questHero = HeroC(id = questHeroId, factionId = None)
val questHero = HeroC(id = questHeroId, factionId = None, profession = questHeroProfession)
val faction = FactionC(
id = actingFactionId,
factionHeadId = factionLeaderId,
@@ -136,7 +140,7 @@ class UpgradeBattalionQuestCommandChooserTest extends AnyFlatSpec with Matchers
heroes = Map(factionLeaderId -> factionLeader, questHeroId -> questHero) ++ extraHeroes,
battalions = battalions,
destroyedBattalions = Map.empty,
factions = Map(actingFactionId -> faction),
factions = Map(actingFactionId -> faction) ++ extraFactions,
factionCommandCounts = Map.empty,
killedHeroes = Map.empty,
destroyedFactions = Map.empty,
@@ -633,6 +637,120 @@ class UpgradeBattalionQuestCommandChooserTest extends AnyFlatSpec with Matchers
}
}
it should "not begin long preparation for an ordinary quest giver" in {
val gs = makeGameState(
province = baseProvince,
questHeroProfession = NoProfession
)
val result = UpgradeBattalionQuestCommandChooser.chosenDeterministicCommand(
actingFactionId = actingFactionId,
gameState = gs,
availableCommands = Vector(organizeAvailable),
uhsWithQuests = questUhs
)
result shouldBe None
}
it should "not begin long preparation for a Mage quest giver on a hostile border" in {
val enemyFactionId = 2
val enemyProvince = ProvinceC(
id = neighborProvinceId,
rulingFactionId = Some(enemyFactionId),
neighbors = Vector(Neighbor(provinceId = provinceId, startingPositionIndex = 0))
)
val questProvince = baseProvince.copy(
neighbors = Vector(Neighbor(provinceId = neighborProvinceId, startingPositionIndex = 0))
)
val enemyFaction = FactionC(
id = enemyFactionId,
factionHeadId = 0,
name = "Enemy Faction",
leaderIds = Vector.empty
)
val gs = makeGameState(
province = questProvince,
otherProvinces = Map(neighborProvinceId -> enemyProvince),
extraFactions = Map(enemyFactionId -> enemyFaction)
)
val result = UpgradeBattalionQuestCommandChooser.chosenDeterministicCommand(
actingFactionId = actingFactionId,
gameState = gs,
availableCommands = Vector(organizeAvailable),
uhsWithQuests = questUhs
)
result shouldBe None
}
it should "not begin long preparation for a Mage quest giver facing a hostile incoming army" in {
val enemyFactionId = 2
val enemyFaction = FactionC(
id = enemyFactionId,
factionHeadId = 0,
name = "Enemy Faction",
leaderIds = Vector.empty
)
val questProvince = baseProvince.copy(
incomingArmies = Vector(
MovingArmy(
id = 1,
army = Army(factionId = enemyFactionId, units = Vector.empty),
arrivalRound = 1,
destinationProvinceId = provinceId,
originProvinceId = neighborProvinceId,
supplies = Supplies(gold = 0, food = 0),
suppliesLoss = 0.0,
startingPositionIndex = None
)
)
)
val gs = makeGameState(
province = questProvince,
extraFactions = Map(enemyFactionId -> enemyFaction)
)
val result = UpgradeBattalionQuestCommandChooser.chosenDeterministicCommand(
actingFactionId = actingFactionId,
gameState = gs,
availableCommands = Vector(organizeAvailable),
uhsWithQuests = questUhs
)
result shouldBe None
}
it should "allow an ordinary quest giver's immediate completion step" in {
val province = baseProvince.copy(battalionIds = Vector(battalionId1))
val battalions = Map(
battalionId1 -> BattalionC(
id = battalionId1,
typeId = BattalionTypeId.LightInfantry,
size = 800,
armament = 0.0,
training = 0.5
)
)
val gs = makeGameState(
province = province,
battalions = battalions,
questHeroProfession = NoProfession
)
val result = UpgradeBattalionQuestCommandChooser.chosenDeterministicCommand(
actingFactionId = actingFactionId,
gameState = gs,
availableCommands = Vector(armTroopsAvailable),
uhsWithQuests = questUhs
)
inside(result) {
case Some(command) => command.selected shouldBe a[ArmTroopsSelected]
}
}
it should "develop Infrastructure when the armament target exceeds infrastructure (Step G)" in {
val province = baseProvince.copy(
infrastructure = 1.0,