mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-29 00:35:41 +00:00
Add AI quest handlers for blizzard, drought, and epidemic filtering (#6649)
* Add AI quest handlers for blizzard, drought, and epidemic own-province filtering Create ControlWeatherQuestCommandChooser to handle StartBlizzardQuest and StartDroughtQuest via the ControlWeather command. Update StartEpidemicQuest CommandChooser to skip quests targeting the acting faction's own provinces. Both handlers verify the appropriate command is available before selecting. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Address review: use inside() pattern and extract questTargets method - Replace asInstanceOf with inside() pattern in ControlWeather and StartEpidemic test assertions - Extract questTargets into a private method in ControlWeatherQuestCommandChooser and match directly on its result Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -55,12 +55,19 @@ The AI processes quest handlers in priority order. The first handler that produc
|
||||
| `DevelopProvincesQuest` | `DevelopProvincesQuestCommandChooser` | Switches provinces to Develop order. Prefers provinces without hostile neighbors. |
|
||||
| `MobilizeProvincesQuest` | `MobilizeProvincesQuestCommandChooser` | Switches provinces to Mobilize order. Prefers provinces with hostile neighbors. Skipped if a DevelopProvincesQuest also exists (conflict avoidance). |
|
||||
|
||||
### Weather/Epidemic Quests
|
||||
|
||||
| Quest | Handler | Notes |
|
||||
|-------|---------|-------|
|
||||
| `StartEpidemicQuest` | `StartEpidemicQuestCommandChooser` | Start an epidemic in a province. Skipped if the target province belongs to the acting faction. |
|
||||
| `StartBlizzardQuest` | `ControlWeatherQuestCommandChooser` | Start a blizzard via ControlWeather command. Skipped if the target province belongs to the acting faction. |
|
||||
| `StartDroughtQuest` | `ControlWeatherQuestCommandChooser` | Start a drought via ControlWeather command. Skipped if the target province belongs to the acting faction. |
|
||||
|
||||
### Other Quests
|
||||
|
||||
| Quest | Handler | Conditions |
|
||||
|-------|---------|------------|
|
||||
| `RestProvinceQuest` | `RestProvinceQuestCommandChooser` | Use the Rest command in a specific province |
|
||||
| `StartEpidemicQuest` | `StartEpidemicQuestCommandChooser` | Start an epidemic in a province |
|
||||
| `SwearBrotherhoodWithHeroQuest` | `SwearBrotherhoodQuestCommandChooser` | Swear brotherhood with a specific hero |
|
||||
| `DismissSpecificVassalQuest` | `DismissSpecificVassalCommandChooser` | Only if province has more than 2 heroes AND the unaffiliated hero's power >= target hero's power * `RequiredPowerMultiplierForDismiss` |
|
||||
|
||||
@@ -92,8 +99,6 @@ The following quests have no handler in `FulfillQuestsCommandSelector` and must
|
||||
### Special Event Quests
|
||||
- `SuppressRiotByForceQuest` - Suppress a riot by force
|
||||
- `FightBeastsAloneQuest` - Fight beasts alone
|
||||
- `StartBlizzardQuest` - Start a blizzard in a province
|
||||
- `StartDroughtQuest` - Start a drought in a province
|
||||
- `ApprehendOutlawQuest` - Apprehend an outlaw hero
|
||||
|
||||
### Miscellaneous
|
||||
@@ -131,5 +136,6 @@ The AI prioritizes quests in the order they appear in `FulfillQuestsCommandSelec
|
||||
18. MobilizeProvinces
|
||||
19. SendSupplies
|
||||
20. StartEpidemic
|
||||
21. SwearBrotherhood
|
||||
22. Alliance
|
||||
21. ControlWeather (StartBlizzard/StartDrought)
|
||||
22. SwearBrotherhood
|
||||
23. Alliance
|
||||
|
||||
@@ -318,6 +318,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:alliance_quest_command_chooser",
|
||||
"//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:control_weather_quest_command_chooser",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:develop_provinces_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",
|
||||
|
||||
+2
@@ -5,6 +5,7 @@ import net.eagle0.eagle.library.util.command_choice_helpers.quest_command_select
|
||||
AllianceQuestCommandChooser,
|
||||
AlmsAcrossRealmQuestCommandChooser,
|
||||
AlmsToProvinceQuestCommandChooser,
|
||||
ControlWeatherQuestCommandChooser,
|
||||
DevelopProvincesQuestCommandChooser,
|
||||
DismissSpecificVassalCommandChooser,
|
||||
ExecutePrisonerQuestCommandChooser,
|
||||
@@ -55,6 +56,7 @@ object FulfillQuestsCommandSelector {
|
||||
MobilizeProvincesQuestCommandChooser,
|
||||
SendSuppliesQuestCommandChooser,
|
||||
StartEpidemicQuestCommandChooser,
|
||||
ControlWeatherQuestCommandChooser,
|
||||
SwearBrotherhoodQuestCommandChooser,
|
||||
AllianceQuestCommandChooser
|
||||
)
|
||||
|
||||
+23
@@ -73,6 +73,29 @@ scala_library(
|
||||
],
|
||||
)
|
||||
|
||||
scala_library(
|
||||
name = "control_weather_quest_command_chooser",
|
||||
srcs = ["ControlWeatherQuestCommandChooser.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/common",
|
||||
"//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 = "develop_provinces_quest_command_chooser",
|
||||
srcs = ["DevelopProvincesQuestCommandChooser.scala"],
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package net.eagle0.eagle.library.util.command_choice_helpers.quest_command_selectors
|
||||
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
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.ControlWeatherAvailable
|
||||
import net.eagle0.eagle.model.state.command.common.ControlWeatherType
|
||||
import net.eagle0.eagle.model.state.command.selected.SelectedCommand.ControlWeatherSelected
|
||||
import net.eagle0.eagle.model.state.game_state.GameState
|
||||
import net.eagle0.eagle.model.state.quest.{StartBlizzardQuest, StartDroughtQuest}
|
||||
|
||||
object ControlWeatherQuestCommandChooser extends DeterministicQuestCommandChooser {
|
||||
private def questTargets(
|
||||
actingFactionId: FactionId,
|
||||
gameState: GameState,
|
||||
uhsWithQuests: Vector[UnaffiliatedHeroWithQuest]
|
||||
): Vector[(ProvinceId, ControlWeatherType)] =
|
||||
uhsWithQuests
|
||||
.map(_.quest)
|
||||
.collect {
|
||||
case q: StartBlizzardQuest => (q.provinceId, ControlWeatherType.StartBlizzard)
|
||||
case q: StartDroughtQuest => (q.provinceId, ControlWeatherType.StartDrought)
|
||||
}
|
||||
.filterNot {
|
||||
case (pid, _) =>
|
||||
gameState.provinces.get(pid).exists(_.rulingFactionId.contains(actingFactionId))
|
||||
}
|
||||
|
||||
override def chosenDeterministicCommand(
|
||||
actingFactionId: FactionId,
|
||||
gameState: GameState,
|
||||
availableCommands: Vector[AvailableCommand],
|
||||
uhsWithQuests: Vector[UnaffiliatedHeroWithQuest]
|
||||
): Option[CommandSelection] =
|
||||
questTargets(actingFactionId, gameState, uhsWithQuests) match {
|
||||
case Vector() => None
|
||||
case targets =>
|
||||
availableCommands.collectFirst { case cwa: ControlWeatherAvailable => cwa }.flatMap { controlWeather =>
|
||||
targets.collectFirst {
|
||||
case (provinceId, weatherType)
|
||||
if controlWeather.options
|
||||
.exists(opt => opt.provinceId == provinceId && opt.controlWeatherTypes.contains(weatherType)) =>
|
||||
CommandSelection(
|
||||
actingFactionId = actingFactionId,
|
||||
actingProvinceId = controlWeather.actingProvinceId,
|
||||
available = controlWeather,
|
||||
selected = ControlWeatherSelected(
|
||||
selectedType = weatherType,
|
||||
selectedProvinceId = provinceId,
|
||||
actingHeroId = controlWeather.availableHeroIds.head
|
||||
),
|
||||
reason = s"fulfill $weatherType quest"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-3
@@ -18,12 +18,10 @@ object StartEpidemicQuestCommandChooser extends DeterministicQuestCommandChooser
|
||||
val targetProvinceIds = uhsWithQuests
|
||||
.map(_.quest)
|
||||
.collect { case q: StartEpidemicQuest => q.provinceId }
|
||||
.filterNot(pid => gameState.provinces.get(pid).exists(_.rulingFactionId.contains(actingFactionId)))
|
||||
.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 =>
|
||||
|
||||
+43
@@ -69,6 +69,28 @@ scala_test(
|
||||
],
|
||||
)
|
||||
|
||||
scala_test(
|
||||
name = "control_weather_quest_command_chooser_test",
|
||||
srcs = ["ControlWeatherQuestCommandChooserTest.scala"],
|
||||
deps = [
|
||||
"//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/quest_command_selectors:control_weather_quest_command_chooser",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:unaffiliated_hero_with_quest",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//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/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/quest",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/run_status",
|
||||
],
|
||||
)
|
||||
|
||||
scala_test(
|
||||
name = "develop_provinces_quest_command_chooser_test",
|
||||
srcs = ["DevelopProvincesQuestCommandChooserTest.scala"],
|
||||
@@ -177,3 +199,24 @@ scala_test(
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/run_status",
|
||||
],
|
||||
)
|
||||
|
||||
scala_test(
|
||||
name = "start_epidemic_quest_command_chooser_test",
|
||||
srcs = ["StartEpidemicQuestCommandChooserTest.scala"],
|
||||
deps = [
|
||||
"//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/quest_command_selectors:start_epidemic_quest_command_chooser",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors:unaffiliated_hero_with_quest",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
|
||||
"//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/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/faction/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/hero/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/quest",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/run_status",
|
||||
],
|
||||
)
|
||||
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
package net.eagle0.eagle.library.util.command_choice_helpers.quest_command_selectors
|
||||
|
||||
import net.eagle0.eagle.{FactionId, HeroId, ProvinceId}
|
||||
import net.eagle0.eagle.model.state.{GameType, RoundPhase}
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.{ControlWeatherAvailable, TargetProvinceOptions}
|
||||
import net.eagle0.eagle.model.state.command.common.ControlWeatherType
|
||||
import net.eagle0.eagle.model.state.command.selected.SelectedCommand.ControlWeatherSelected
|
||||
import net.eagle0.eagle.model.state.date.Date
|
||||
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.province.concrete.ProvinceC
|
||||
import net.eagle0.eagle.model.state.quest.{ImproveAgricultureQuest, StartBlizzardQuest, StartDroughtQuest}
|
||||
import net.eagle0.eagle.model.state.run_status.RunStatus
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.Inside.inside
|
||||
|
||||
class ControlWeatherQuestCommandChooserTest extends AnyFlatSpec with Matchers {
|
||||
|
||||
private val actingFactionId: FactionId = 1
|
||||
private val enemyFactionId: FactionId = 2
|
||||
private val factionLeaderId: HeroId = 10
|
||||
private val heroId: HeroId = 20
|
||||
private val ownProvinceId: ProvinceId = 100
|
||||
private val enemyProvinceId: ProvinceId = 200
|
||||
|
||||
private val gameState: GameState = {
|
||||
val factionLeader = HeroC(id = factionLeaderId, factionId = Some(actingFactionId))
|
||||
val hero = HeroC(id = heroId, factionId = None)
|
||||
val ownProvince = ProvinceC(
|
||||
id = ownProvinceId,
|
||||
rulingFactionId = Some(actingFactionId),
|
||||
rulingFactionHeroIds = Vector(factionLeaderId)
|
||||
)
|
||||
val enemyProvince = ProvinceC(
|
||||
id = enemyProvinceId,
|
||||
rulingFactionId = Some(enemyFactionId)
|
||||
)
|
||||
val faction = FactionC(
|
||||
id = actingFactionId,
|
||||
factionHeadId = factionLeaderId,
|
||||
name = "Test Faction",
|
||||
leaderIds = Vector(factionLeaderId)
|
||||
)
|
||||
val enemyFaction = FactionC(
|
||||
id = enemyFactionId,
|
||||
factionHeadId = 99,
|
||||
name = "Enemy Faction",
|
||||
leaderIds = Vector(99)
|
||||
)
|
||||
GameState(
|
||||
gameId = 1,
|
||||
currentRoundId = 1,
|
||||
currentPhase = RoundPhase.PlayerCommands,
|
||||
currentDate = Some(Date(year = 1501, month = June)),
|
||||
actionResultCount = 0,
|
||||
provinces = Map(
|
||||
ownProvinceId -> ownProvince,
|
||||
enemyProvinceId -> enemyProvince
|
||||
),
|
||||
heroes = Map(factionLeaderId -> factionLeader, heroId -> hero),
|
||||
battalions = Map.empty,
|
||||
destroyedBattalions = Map.empty,
|
||||
factions = Map(actingFactionId -> faction, enemyFactionId -> enemyFaction),
|
||||
factionCommandCounts = Map.empty,
|
||||
killedHeroes = Map.empty,
|
||||
destroyedFactions = Map.empty,
|
||||
outstandingBattles = Vector.empty,
|
||||
battleCounter = 0,
|
||||
deferredNotifications = Vector.empty,
|
||||
runStatus = RunStatus.Running,
|
||||
victor = None,
|
||||
battalionTypes = Vector.empty,
|
||||
randomSeed = 1L,
|
||||
chronicleEntries = Vector.empty,
|
||||
gameType = GameType.Normal
|
||||
)
|
||||
}
|
||||
|
||||
private val controlWeatherAvailable = ControlWeatherAvailable(
|
||||
options = Vector(
|
||||
TargetProvinceOptions(
|
||||
provinceId = enemyProvinceId,
|
||||
controlWeatherTypes = Vector(ControlWeatherType.StartBlizzard, ControlWeatherType.StartDrought)
|
||||
),
|
||||
TargetProvinceOptions(
|
||||
provinceId = ownProvinceId,
|
||||
controlWeatherTypes = Vector(ControlWeatherType.StartBlizzard)
|
||||
)
|
||||
),
|
||||
actingProvinceId = ownProvinceId,
|
||||
availableHeroIds = Vector(factionLeaderId)
|
||||
)
|
||||
|
||||
"ControlWeatherQuestCommandChooser" should "return a command for StartBlizzard quest on enemy province" in {
|
||||
val result = ControlWeatherQuestCommandChooser.chosenDeterministicCommand(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gameState,
|
||||
availableCommands = Vector(controlWeatherAvailable),
|
||||
uhsWithQuests = Vector(
|
||||
UnaffiliatedHeroWithQuest(
|
||||
pid = ownProvinceId,
|
||||
heroId = heroId,
|
||||
quest = StartBlizzardQuest(provinceId = enemyProvinceId)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
result shouldBe defined
|
||||
inside(result.get.selected) {
|
||||
case selected: ControlWeatherSelected =>
|
||||
selected.selectedType shouldBe ControlWeatherType.StartBlizzard
|
||||
selected.selectedProvinceId shouldBe enemyProvinceId
|
||||
}
|
||||
}
|
||||
|
||||
it should "return a command for StartDrought quest on enemy province" in {
|
||||
val result = ControlWeatherQuestCommandChooser.chosenDeterministicCommand(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gameState,
|
||||
availableCommands = Vector(controlWeatherAvailable),
|
||||
uhsWithQuests = Vector(
|
||||
UnaffiliatedHeroWithQuest(
|
||||
pid = ownProvinceId,
|
||||
heroId = heroId,
|
||||
quest = StartDroughtQuest(provinceId = enemyProvinceId)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
result shouldBe defined
|
||||
inside(result.get.selected) {
|
||||
case selected: ControlWeatherSelected =>
|
||||
selected.selectedType shouldBe ControlWeatherType.StartDrought
|
||||
selected.selectedProvinceId shouldBe enemyProvinceId
|
||||
}
|
||||
}
|
||||
|
||||
it should "return None when quest targets own province" in {
|
||||
val result = ControlWeatherQuestCommandChooser.chosenDeterministicCommand(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gameState,
|
||||
availableCommands = Vector(controlWeatherAvailable),
|
||||
uhsWithQuests = Vector(
|
||||
UnaffiliatedHeroWithQuest(
|
||||
pid = ownProvinceId,
|
||||
heroId = heroId,
|
||||
quest = StartBlizzardQuest(provinceId = ownProvinceId)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
result shouldBe None
|
||||
}
|
||||
|
||||
it should "return None when no weather quests exist" in {
|
||||
val result = ControlWeatherQuestCommandChooser.chosenDeterministicCommand(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gameState,
|
||||
availableCommands = Vector(controlWeatherAvailable),
|
||||
uhsWithQuests = Vector(
|
||||
UnaffiliatedHeroWithQuest(
|
||||
pid = ownProvinceId,
|
||||
heroId = heroId,
|
||||
quest = ImproveAgricultureQuest(provinceId = ownProvinceId, desiredValue = 50)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
result shouldBe None
|
||||
}
|
||||
|
||||
it should "return None when ControlWeatherAvailable is not in available commands" in {
|
||||
val result = ControlWeatherQuestCommandChooser.chosenDeterministicCommand(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gameState,
|
||||
availableCommands = Vector.empty,
|
||||
uhsWithQuests = Vector(
|
||||
UnaffiliatedHeroWithQuest(
|
||||
pid = ownProvinceId,
|
||||
heroId = heroId,
|
||||
quest = StartBlizzardQuest(provinceId = enemyProvinceId)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
result shouldBe None
|
||||
}
|
||||
|
||||
it should "return None when weather type is not available for the target province" in {
|
||||
val limitedOptions = ControlWeatherAvailable(
|
||||
options = Vector(
|
||||
TargetProvinceOptions(
|
||||
provinceId = enemyProvinceId,
|
||||
controlWeatherTypes = Vector(ControlWeatherType.EndBlizzard) // only EndBlizzard, not StartBlizzard
|
||||
)
|
||||
),
|
||||
actingProvinceId = ownProvinceId,
|
||||
availableHeroIds = Vector(factionLeaderId)
|
||||
)
|
||||
|
||||
val result = ControlWeatherQuestCommandChooser.chosenDeterministicCommand(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gameState,
|
||||
availableCommands = Vector(limitedOptions),
|
||||
uhsWithQuests = Vector(
|
||||
UnaffiliatedHeroWithQuest(
|
||||
pid = ownProvinceId,
|
||||
heroId = heroId,
|
||||
quest = StartBlizzardQuest(provinceId = enemyProvinceId)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
result shouldBe None
|
||||
}
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
package net.eagle0.eagle.library.util.command_choice_helpers.quest_command_selectors
|
||||
|
||||
import net.eagle0.eagle.{FactionId, HeroId, ProvinceId}
|
||||
import net.eagle0.eagle.model.state.{GameType, RoundPhase}
|
||||
import net.eagle0.eagle.model.state.command.available.AvailableCommand.{StartEpidemicAvailable, StartEpidemicOptions}
|
||||
import net.eagle0.eagle.model.state.command.selected.SelectedCommand.StartEpidemicSelected
|
||||
import net.eagle0.eagle.model.state.date.Date
|
||||
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.province.concrete.ProvinceC
|
||||
import net.eagle0.eagle.model.state.quest.{ImproveAgricultureQuest, StartEpidemicQuest}
|
||||
import net.eagle0.eagle.model.state.run_status.RunStatus
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.Inside.inside
|
||||
|
||||
class StartEpidemicQuestCommandChooserTest extends AnyFlatSpec with Matchers {
|
||||
|
||||
private val actingFactionId: FactionId = 1
|
||||
private val enemyFactionId: FactionId = 2
|
||||
private val factionLeaderId: HeroId = 10
|
||||
private val heroId: HeroId = 20
|
||||
private val ownProvinceId: ProvinceId = 100
|
||||
private val enemyProvinceId: ProvinceId = 200
|
||||
private val neutralProvinceId: ProvinceId = 300
|
||||
|
||||
private val gameState: GameState = {
|
||||
val factionLeader = HeroC(id = factionLeaderId, factionId = Some(actingFactionId))
|
||||
val hero = HeroC(id = heroId, factionId = None)
|
||||
val ownProvince = ProvinceC(
|
||||
id = ownProvinceId,
|
||||
rulingFactionId = Some(actingFactionId),
|
||||
rulingFactionHeroIds = Vector(factionLeaderId)
|
||||
)
|
||||
val enemyProvince = ProvinceC(
|
||||
id = enemyProvinceId,
|
||||
rulingFactionId = Some(enemyFactionId)
|
||||
)
|
||||
val neutralProvince = ProvinceC(
|
||||
id = neutralProvinceId
|
||||
)
|
||||
val faction = FactionC(
|
||||
id = actingFactionId,
|
||||
factionHeadId = factionLeaderId,
|
||||
name = "Test Faction",
|
||||
leaderIds = Vector(factionLeaderId)
|
||||
)
|
||||
val enemyFaction = FactionC(
|
||||
id = enemyFactionId,
|
||||
factionHeadId = 99,
|
||||
name = "Enemy Faction",
|
||||
leaderIds = Vector(99)
|
||||
)
|
||||
GameState(
|
||||
gameId = 1,
|
||||
currentRoundId = 1,
|
||||
currentPhase = RoundPhase.PlayerCommands,
|
||||
currentDate = Some(Date(year = 1501, month = June)),
|
||||
actionResultCount = 0,
|
||||
provinces = Map(
|
||||
ownProvinceId -> ownProvince,
|
||||
enemyProvinceId -> enemyProvince,
|
||||
neutralProvinceId -> neutralProvince
|
||||
),
|
||||
heroes = Map(factionLeaderId -> factionLeader, heroId -> hero),
|
||||
battalions = Map.empty,
|
||||
destroyedBattalions = Map.empty,
|
||||
factions = Map(actingFactionId -> faction, enemyFactionId -> enemyFaction),
|
||||
factionCommandCounts = Map.empty,
|
||||
killedHeroes = Map.empty,
|
||||
destroyedFactions = Map.empty,
|
||||
outstandingBattles = Vector.empty,
|
||||
battleCounter = 0,
|
||||
deferredNotifications = Vector.empty,
|
||||
runStatus = RunStatus.Running,
|
||||
victor = None,
|
||||
battalionTypes = Vector.empty,
|
||||
randomSeed = 1L,
|
||||
chronicleEntries = Vector.empty,
|
||||
gameType = GameType.Normal
|
||||
)
|
||||
}
|
||||
|
||||
private val startEpidemicAvailable = StartEpidemicAvailable(
|
||||
options = Vector(
|
||||
StartEpidemicOptions(targetProvinceId = enemyProvinceId),
|
||||
StartEpidemicOptions(targetProvinceId = ownProvinceId)
|
||||
),
|
||||
actingProvinceId = ownProvinceId,
|
||||
availableHeroIds = Vector(factionLeaderId)
|
||||
)
|
||||
|
||||
"StartEpidemicQuestCommandChooser" should "return a command when quest targets an enemy province" in {
|
||||
val result = StartEpidemicQuestCommandChooser.chosenDeterministicCommand(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gameState,
|
||||
availableCommands = Vector(startEpidemicAvailable),
|
||||
uhsWithQuests = Vector(
|
||||
UnaffiliatedHeroWithQuest(
|
||||
pid = ownProvinceId,
|
||||
heroId = heroId,
|
||||
quest = StartEpidemicQuest(provinceId = enemyProvinceId)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
result shouldBe defined
|
||||
inside(result.get.selected) {
|
||||
case selected: StartEpidemicSelected =>
|
||||
selected.selectedProvinceId shouldBe enemyProvinceId
|
||||
}
|
||||
}
|
||||
|
||||
it should "return None when quest targets own province" in {
|
||||
val result = StartEpidemicQuestCommandChooser.chosenDeterministicCommand(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gameState,
|
||||
availableCommands = Vector(startEpidemicAvailable),
|
||||
uhsWithQuests = Vector(
|
||||
UnaffiliatedHeroWithQuest(
|
||||
pid = ownProvinceId,
|
||||
heroId = heroId,
|
||||
quest = StartEpidemicQuest(provinceId = ownProvinceId)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
result shouldBe None
|
||||
}
|
||||
|
||||
it should "return None when no StartEpidemicQuest exists" in {
|
||||
val result = StartEpidemicQuestCommandChooser.chosenDeterministicCommand(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gameState,
|
||||
availableCommands = Vector(startEpidemicAvailable),
|
||||
uhsWithQuests = Vector(
|
||||
UnaffiliatedHeroWithQuest(
|
||||
pid = ownProvinceId,
|
||||
heroId = heroId,
|
||||
quest = ImproveAgricultureQuest(provinceId = ownProvinceId, desiredValue = 50)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
result shouldBe None
|
||||
}
|
||||
|
||||
it should "return None when StartEpidemicAvailable is not in available commands" in {
|
||||
val result = StartEpidemicQuestCommandChooser.chosenDeterministicCommand(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gameState,
|
||||
availableCommands = Vector.empty,
|
||||
uhsWithQuests = Vector(
|
||||
UnaffiliatedHeroWithQuest(
|
||||
pid = ownProvinceId,
|
||||
heroId = heroId,
|
||||
quest = StartEpidemicQuest(provinceId = enemyProvinceId)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
result shouldBe None
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user