Split feast spending quests by scope

This commit is contained in:
2026-05-29 19:50:37 -07:00
parent 0bc84928c7
commit 6b80cf4c0b
16 changed files with 355 additions and 39 deletions
@@ -185,6 +185,10 @@ namespace eagle {
case SealedValueOneofCase.StartDroughtQuest: return "Start Drought";
case SealedValueOneofCase.StartEpidemicQuest: return "Start Epidemic";
case SealedValueOneofCase.SpendOnFeastsQuest: return "Host Feasts";
case SealedValueOneofCase.SpendOnFeastsInProvinceQuest:
return "Host Feasts Locally";
case SealedValueOneofCase.SpendOnFeastsAcrossRealmQuest:
return "Host Feasts Across Realm";
case SealedValueOneofCase.SendSuppliesQuest: return "Send Supplies";
case SealedValueOneofCase.RestProvinceQuest: return "Rest Province";
case SealedValueOneofCase.ReconProvincesQuest: return "Recon Provinces";
@@ -327,4 +331,4 @@ namespace eagle {
return template;
}
}
}
}
@@ -433,6 +433,16 @@ namespace eagle {
return $"Host feasts costing {details.TotalGold} gold ({quest.ComponentsFulfilled}/{quest.ComponentCount})";
}
case SealedValueOneofCase.SpendOnFeastsInProvinceQuest: {
var details = quest.Details.SpendOnFeastsInProvinceQuest;
return $"Host feasts here costing {details.TotalGold} gold ({quest.ComponentsFulfilled}/{quest.ComponentCount})";
}
case SealedValueOneofCase.SpendOnFeastsAcrossRealmQuest: {
var details = quest.Details.SpendOnFeastsAcrossRealmQuest;
return $"Host feasts across the realm costing {details.TotalGold} gold ({quest.ComponentsFulfilled}/{quest.ComponentCount})";
}
case SealedValueOneofCase.SendSuppliesQuest: {
var details = quest.Details.SendSuppliesQuest;
var provinceName = model.Provinces[details.TargetProvinceId].Name;
@@ -476,4 +486,4 @@ namespace eagle {
}
}
}
}
}
@@ -62,6 +62,8 @@ message QuestDetails {
RepairDevastationQuest repair_devastation_quest = 45;
ReconSpecificProvincesQuest recon_specific_provinces_quest = 46;
StartDroughtQuest start_drought_quest = 47;
SpendOnFeastsInProvinceQuest spend_on_feasts_in_province_quest = 48;
SpendOnFeastsAcrossRealmQuest spend_on_feasts_across_realm_quest = 49;
}
}
@@ -230,6 +232,14 @@ message SpendOnFeastsQuest {
int32 total_gold = 1;
}
message SpendOnFeastsInProvinceQuest {
int32 total_gold = 1;
}
message SpendOnFeastsAcrossRealmQuest {
int32 total_gold = 1;
}
message SendSuppliesQuest {
int32 target_province_id = 1;
int32 total_food = 2;
@@ -249,4 +259,4 @@ message RepairDevastationQuest {
message ReconSpecificProvincesQuest {
repeated int32 target_province_ids = 1;
}
}
@@ -248,7 +248,7 @@ case class CheckForFulfilledQuestsAction(
case _: StartEpidemicQuest =>
false // action quest - fulfilled in ControlWeatherCommand
// Note: WinBattlesQuest is handled by ComponentQuest case above
// Note: SpendOnFeastsQuest, SendSuppliesQuest, RestProvinceQuest,
// Note: feast-spending quests, SendSuppliesQuest, RestProvinceQuest,
// ReconProvincesQuest, RepairDevastationQuest are ComponentQuests
// Note: ReconSpecificProvincesQuest has special handling above
case _: ApprehendOutlawQuest =>
@@ -216,6 +216,7 @@ class CommandFactory extends TCommandFactory {
FeastCommand.make(
actingFactionId = actingFactionId,
province = province,
factionProvinces = FactionUtils.provinces(actingFactionId, allProvinces(gameState)),
rulingFactionHeroes = province.rulingFactionHeroIds
.map(gameState.heroes),
factionLeaderIds = faction.leaderIds,
@@ -1,5 +1,7 @@
package net.eagle0.eagle.library.actions.impl.command
import scala.annotation.unused
import net.eagle0.eagle.{FactionId, HeroId}
import net.eagle0.eagle.library.actions.impl.common.ProtolessSimpleAction
import net.eagle0.eagle.library.settings.{LoyaltyGainFromFeast, VigorGainFromFeast}
@@ -9,14 +11,17 @@ import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedPro
import net.eagle0.eagle.model.action_result.concrete.{ActionResultC, ChangedHeroC, StatDelta, StatNoChange}
import net.eagle0.eagle.model.action_result.types.ActionResultType.Feast
import net.eagle0.eagle.model.action_result.ActionResultT
import net.eagle0.eagle.model.action_result.ActionResultT.ActionResultUpdater
import net.eagle0.eagle.model.state.hero.HeroT
import net.eagle0.eagle.model.state.province.ProvinceT
import net.eagle0.eagle.model.state.quest.SpendOnFeastsQuest
import net.eagle0.eagle.model.state.quest.{SpendOnFeastsAcrossRealmQuest, SpendOnFeastsInProvinceQuest}
import net.eagle0.eagle.model.state.unaffiliated_hero.UnaffiliatedHeroT
object FeastCommand {
def make(
actingFactionId: FactionId,
province: ProvinceT,
factionProvinces: Vector[ProvinceT],
rulingFactionHeroes: Vector[HeroT],
factionLeaderIds: Vector[HeroId],
goldCost: Int
@@ -24,6 +29,7 @@ object FeastCommand {
FeastCommand(
factionId = actingFactionId,
province = province,
factionProvinces = factionProvinces,
rulingFactionHeroes = rulingFactionHeroes,
factionLeaderIds = factionLeaderIds,
goldCost = goldCost
@@ -32,6 +38,7 @@ object FeastCommand {
private case class FeastCommand(
factionId: FactionId,
province: ProvinceT,
factionProvinces: Vector[ProvinceT],
rulingFactionHeroes: Vector[HeroT],
factionLeaderIds: Vector[HeroId],
goldCost: Int
@@ -58,16 +65,37 @@ object FeastCommand {
)
)
private def feastInThisProvinceQuestCheck(
uh: UnaffiliatedHeroT,
@unused province: ProvinceT
): Boolean =
uh.quest.exists {
case q: SpendOnFeastsInProvinceQuest => q.componentsFulfilled < q.componentCount
case _ => false
}
private def feastAcrossRealmQuestCheck(
uh: UnaffiliatedHeroT,
@unused province: ProvinceT
): Boolean =
uh.quest.exists {
case q: SpendOnFeastsAcrossRealmQuest => q.componentsFulfilled < q.componentCount
case _ => false
}
private def feastAcrossRealmCounterIncrementer: ActionResultUpdater =
QuestFulfillmentUtils.withCountersIncremented(
provinces = factionProvinces,
incrementAmount = goldCost,
check = feastAcrossRealmQuestCheck
)
private def changedProvinces: Vector[ChangedProvinceC] = {
// Increment SpendOnFeastsQuest progress by goldCost for unaffiliated hero in this province
// Increment in-province feast quest progress by goldCost for unaffiliated heroes in this province.
val questProgressChange = QuestFulfillmentUtils.counterIncrementedCP(
province = province,
incrementAmount = goldCost,
check = (uh, _) =>
uh.quest.exists {
case q: SpendOnFeastsQuest => q.componentsFulfilled < q.componentCount
case _ => false
}
check = feastInThisProvinceQuestCheck
)
questProgressChange match {
@@ -103,7 +131,7 @@ object FeastCommand {
provinceIdActed = Some(province.id),
changedHeroes = changedHeroes,
changedProvinces = changedProvinces
)
).update(feastAcrossRealmCounterIncrementer)
private def optionalVal(value: Double): Option[Double] =
if value == 0 then None
@@ -332,9 +332,14 @@ case class DivineMessagePromptGenerator(
s"$divinedHeroName wants to see a magical drought unleashed upon ${targetProvince.name}."
)
case SpendOnFeastsQuest(_, _, totalGold) =>
case SpendOnFeastsInProvinceQuest(_, _, totalGold) =>
TextGenerationSuccess(
s"$divinedHeroName wants $actingHeroName to host lavish feasts spending a total of $totalGold gold."
s"$divinedHeroName wants $actingHeroName to host lavish feasts in this province, spending a total of $totalGold gold."
)
case SpendOnFeastsAcrossRealmQuest(_, _, totalGold) =>
TextGenerationSuccess(
s"$divinedHeroName wants $actingHeroName to host lavish feasts across the realm, spending a total of $totalGold gold."
)
case SendSuppliesQuest(_, _, targetProvinceId, totalFood) =>
@@ -333,10 +333,15 @@ object QuestEndedGeneratorUtilities {
unaffiliatedHeroName <- unaffiliatedHeroNameResult
} yield s"$unaffiliatedHeroName wanted to see a magical drought unleashed upon ${targetProvince.name}."
case SpendOnFeastsQuest(_, _, totalGold) =>
case SpendOnFeastsInProvinceQuest(_, _, totalGold) =>
for {
unaffiliatedHeroName <- unaffiliatedHeroNameResult
} yield s"$unaffiliatedHeroName wanted ${faction.name} to host lavish feasts spending a total of $totalGold gold."
} yield s"$unaffiliatedHeroName wanted ${faction.name} to host lavish feasts in this province, spending a total of $totalGold gold."
case SpendOnFeastsAcrossRealmQuest(_, _, totalGold) =>
for {
unaffiliatedHeroName <- unaffiliatedHeroNameResult
} yield s"$unaffiliatedHeroName wanted ${faction.name} to host lavish feasts across the realm, spending a total of $totalGold gold."
case SendSuppliesQuest(_, _, targetProvinceId, totalFood) =>
val targetProvince = gameState.provinces(targetProvinceId)
@@ -5,7 +5,7 @@ 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.model.state.quest.{SpendOnFeastsAcrossRealmQuest, SpendOnFeastsInProvinceQuest}
import net.eagle0.eagle.FactionId
object SpendOnFeastsQuestCommandChooser extends DeterministicQuestCommandChooser {
@@ -15,20 +15,39 @@ object SpendOnFeastsQuestCommandChooser extends DeterministicQuestCommandChooser
availableCommands: Vector[AvailableCommand],
uhsWithQuests: Vector[UnaffiliatedHeroWithQuest]
): Option[CommandSelection] = {
val hasSpendOnFeastsQuest = uhsWithQuests
.map(_.quest)
.exists(_.isInstanceOf[SpendOnFeastsQuest])
val feastCommands = availableCommands.collect { case fa: FeastAvailable => fa }
if !hasSpendOnFeastsQuest then return None
val inProvinceSelection = uhsWithQuests.collect {
case UnaffiliatedHeroWithQuest(pid, _, _: SpendOnFeastsInProvinceQuest) => pid
}
.flatMap(pid => feastCommands.find(_.actingProvinceId == pid))
.headOption
.map { feastAvailable =>
CommandSelection(
actingFactionId = actingFactionId,
actingProvinceId = feastAvailable.actingProvinceId,
available = feastAvailable,
selected = FeastSelected,
reason = "fulfill SpendOnFeastsInProvince quest"
)
}
availableCommands.collectFirst { case fa: FeastAvailable => fa }.map { feastAvailable =>
CommandSelection(
actingFactionId = actingFactionId,
actingProvinceId = feastAvailable.actingProvinceId,
available = feastAvailable,
selected = FeastSelected,
reason = "fulfill SpendOnFeasts quest"
)
inProvinceSelection.orElse {
val hasAcrossRealmQuest = uhsWithQuests
.exists(_.quest.isInstanceOf[SpendOnFeastsAcrossRealmQuest])
Option
.when(hasAcrossRealmQuest)(feastCommands)
.flatMap(_.maxByOption(_.goldCost))
.map { feastAvailable =>
CommandSelection(
actingFactionId = actingFactionId,
actingProvinceId = feastAvailable.actingProvinceId,
available = feastAvailable,
selected = FeastSelected,
reason = "fulfill SpendOnFeastsAcrossRealm quest"
)
}
}
}
}
@@ -63,7 +63,8 @@ import net.eagle0.eagle.model.state.quest.{
ReturnPrisonerQuest,
SendSuppliesQuest,
SpecificExpansionQuest,
SpendOnFeastsQuest,
SpendOnFeastsAcrossRealmQuest,
SpendOnFeastsInProvinceQuest,
StartBlizzardQuest,
StartDroughtQuest,
StartEpidemicQuest,
@@ -166,7 +167,8 @@ object QuestCreationUtils {
restProvinceQuests,
reconProvincesQuests(heroes),
repairDevastationQuests,
spendOnFeastsQuests,
spendOnFeastsInProvinceQuests,
spendOnFeastsAcrossRealmQuests,
reconSpecificProvincesQuests(heroes)
)
) {
@@ -1135,16 +1137,16 @@ object QuestCreationUtils {
}
}
private def spendOnFeastsQuests(
private def spendOnFeastsInProvinceQuests(
@unused province: ProvinceT,
@unused allProvinces: Vector[ProvinceT],
@unused factions: Vector[FactionT],
@unused battalions: Vector[BattalionT],
functionalRandom: FunctionalRandom
): RandomState[Vector[Quest]] =
functionalRandom.nextIntInclusive(500, 1000).map { totalGold =>
functionalRandom.nextIntInclusive(333, 667).map { totalGold =>
Vector(
SpendOnFeastsQuest(
SpendOnFeastsInProvinceQuest(
componentCount = totalGold,
componentsFulfilled = 0,
totalGold = totalGold
@@ -1152,6 +1154,32 @@ object QuestCreationUtils {
)
}
private def spendOnFeastsAcrossRealmQuests(
province: ProvinceT,
allProvinces: Vector[ProvinceT],
@unused factions: Vector[FactionT],
@unused battalions: Vector[BattalionT],
functionalRandom: FunctionalRandom
): RandomState[Vector[Quest]] = FactionUtils
.provinces(province.getRulingFactionId, allProvinces)
.size match {
case 0 | 1 => RandomState(Vector(), functionalRandom)
case provinceCount =>
functionalRandom.nextIntInclusive(750, 1250).map { goldAmountPerProvince =>
val totalGold = (Math.pow(
provinceCount,
GiftProvinceCountExponent.doubleValue
) * goldAmountPerProvince).ceil.toInt
Vector(
SpendOnFeastsAcrossRealmQuest(
componentCount = totalGold,
componentsFulfilled = 0,
totalGold = totalGold
)
)
}
}
private def reconSpecificProvincesQuests(heroes: Vector[HeroT]): QuestCreator =
(
province: ProvinceT,
@@ -35,6 +35,8 @@ import net.eagle0.eagle.common.unaffiliated_hero_quest.{
ReturnPrisonerQuest as ReturnPrisonerQuestProto,
SendSuppliesQuest as SendSuppliesQuestProto,
SpecificExpansionQuest as SpecificExpansionQuestProto,
SpendOnFeastsAcrossRealmQuest as SpendOnFeastsAcrossRealmQuestProto,
SpendOnFeastsInProvinceQuest as SpendOnFeastsInProvinceQuestProto,
SpendOnFeastsQuest as SpendOnFeastsQuestProto,
StartBlizzardQuest as StartBlizzardQuestProto,
StartDroughtQuest as StartDroughtQuestProto,
@@ -221,9 +223,15 @@ object QuestConverter {
QuestProto(details = StartEpidemicQuestProto(q.provinceId))
case q: StartDroughtQuest =>
QuestProto(details = StartDroughtQuestProto(q.provinceId))
case q: SpendOnFeastsQuest =>
case q: SpendOnFeastsInProvinceQuest =>
QuestProto(
details = SpendOnFeastsQuestProto(q.totalGold),
details = SpendOnFeastsInProvinceQuestProto(q.totalGold),
componentCount = q.componentCount,
componentsFulfilled = q.componentsFulfilled
)
case q: SpendOnFeastsAcrossRealmQuest =>
QuestProto(
details = SpendOnFeastsAcrossRealmQuestProto(q.totalGold),
componentCount = q.componentCount,
componentsFulfilled = q.componentsFulfilled
)
@@ -456,7 +464,25 @@ object QuestConverter {
case StartDroughtQuestProto(provinceId, _ /* unknownFieldSet */ ) =>
StartDroughtQuest(provinceId)
case SpendOnFeastsQuestProto(totalGold, _ /* unknownFieldSet */ ) =>
SpendOnFeastsQuest(
SpendOnFeastsInProvinceQuest(
componentCount = questProto.componentCount,
componentsFulfilled = questProto.componentsFulfilled,
totalGold = totalGold
)
case SpendOnFeastsInProvinceQuestProto(
totalGold,
_ /* unknownFieldSet */
) =>
SpendOnFeastsInProvinceQuest(
componentCount = questProto.componentCount,
componentsFulfilled = questProto.componentsFulfilled,
totalGold = totalGold
)
case SpendOnFeastsAcrossRealmQuestProto(
totalGold,
_ /* unknownFieldSet */
) =>
SpendOnFeastsAcrossRealmQuest(
componentCount = questProto.componentCount,
componentsFulfilled = questProto.componentsFulfilled,
totalGold = totalGold
@@ -175,7 +175,16 @@ case class StartEpidemicQuest(provinceId: ProvinceId) extends Quest
case class StartDroughtQuest(provinceId: ProvinceId) extends Quest
case class SpendOnFeastsQuest(
case class SpendOnFeastsInProvinceQuest(
override val componentCount: Int,
override val componentsFulfilled: Int,
totalGold: Int
) extends ComponentQuest {
override def withComponentsFulfilled(componentsFulfilled: Int): Quest =
this.copy(componentsFulfilled = componentsFulfilled)
}
case class SpendOnFeastsAcrossRealmQuest(
override val componentCount: Int,
override val componentsFulfilled: Int,
totalGold: Int
@@ -63,7 +63,7 @@ class AvailableFeastCommandFactoryTest extends AnyFlatSpec with Matchers with Be
}
"availableCommand" should "be available even if everybody has maxed loyalty and vigor" in {
// Feast is available as long as there's enough gold, so players can complete SpendOnFeastsQuest
// Feast is available as long as there's enough gold, so players can complete feast-spending quests.
val gameState = GameState(
gameId = 0,
currentRoundId = 0,
@@ -279,6 +279,8 @@ scala_test(
"//src/main/scala/net/eagle0/eagle/model/action_result/types",
"//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/unaffiliated_hero/concrete",
],
)
@@ -6,6 +6,9 @@ import net.eagle0.eagle.model.action_result.concrete.{ChangedHeroC, StatDelta, S
import net.eagle0.eagle.model.action_result.types.ActionResultType.Feast
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.{SpendOnFeastsAcrossRealmQuest, SpendOnFeastsInProvinceQuest}
import net.eagle0.eagle.model.state.unaffiliated_hero.{RecruitmentInfo, UnaffiliatedHeroType}
import net.eagle0.eagle.model.state.unaffiliated_hero.concrete.UnaffiliatedHeroC
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import org.scalatest.BeforeAndAfterEach
@@ -66,6 +69,7 @@ class FeastCommandTest extends AnyFlatSpec with Matchers with BeforeAndAfterEach
.make(
actingFactionId = actingFactionId,
province = province,
factionProvinces = Vector(province),
rulingFactionHeroes = Vector(normalHero, fullVigorHero, fullLoyaltyHero),
factionLeaderIds = Vector(7),
goldCost = 44
@@ -83,6 +87,7 @@ class FeastCommandTest extends AnyFlatSpec with Matchers with BeforeAndAfterEach
.make(
actingFactionId = actingFactionId,
province = province,
factionProvinces = Vector(province),
rulingFactionHeroes = Vector(normalHero, fullVigorHero, fullLoyaltyHero),
factionLeaderIds = Vector(7),
goldCost = 44
@@ -101,6 +106,7 @@ class FeastCommandTest extends AnyFlatSpec with Matchers with BeforeAndAfterEach
.make(
actingFactionId = actingFactionId,
province = province,
factionProvinces = Vector(province),
rulingFactionHeroes = Vector(normalHero, fullVigorHero, fullLoyaltyHero),
factionLeaderIds = Vector(7),
goldCost = 100
@@ -119,6 +125,7 @@ class FeastCommandTest extends AnyFlatSpec with Matchers with BeforeAndAfterEach
.make(
actingFactionId = actingFactionId,
province = province,
factionProvinces = Vector(province),
rulingFactionHeroes = Vector(normalHero, fullVigorHero, fullLoyaltyHero),
factionLeaderIds = Vector(7),
goldCost = 44
@@ -140,6 +147,7 @@ class FeastCommandTest extends AnyFlatSpec with Matchers with BeforeAndAfterEach
.make(
actingFactionId = actingFactionId,
province = province,
factionProvinces = Vector(province),
rulingFactionHeroes = Vector(normalHero, fullVigorHero, fullLoyaltyHero),
factionLeaderIds = Vector(7),
goldCost = 44
@@ -157,6 +165,7 @@ class FeastCommandTest extends AnyFlatSpec with Matchers with BeforeAndAfterEach
.make(
actingFactionId = actingFactionId,
province = province,
factionProvinces = Vector(province),
rulingFactionHeroes = Vector(normalHero, fullVigorHero, fullLoyaltyHero),
factionLeaderIds = Vector(7),
goldCost = 44
@@ -178,6 +187,7 @@ class FeastCommandTest extends AnyFlatSpec with Matchers with BeforeAndAfterEach
.make(
actingFactionId = actingFactionId,
province = province,
factionProvinces = Vector(province),
rulingFactionHeroes = Vector(normalHero, fullVigorHero, fullLoyaltyHero),
factionLeaderIds = Vector(7),
goldCost = 44
@@ -195,6 +205,7 @@ class FeastCommandTest extends AnyFlatSpec with Matchers with BeforeAndAfterEach
.make(
actingFactionId = actingFactionId,
province = province,
factionProvinces = Vector(province),
rulingFactionHeroes = Vector(normalHero, fullVigorHero, fullLoyaltyHero),
factionLeaderIds = Vector(7),
goldCost = 44
@@ -203,4 +214,84 @@ class FeastCommandTest extends AnyFlatSpec with Matchers with BeforeAndAfterEach
result.changedHeroes.find(_.heroId == allFullHero.id) shouldBe None
}
it should "increment in-province feast quest progress only in the feast province" in {
val questHero = UnaffiliatedHeroC(
heroId = 90,
unaffiliatedHeroType = UnaffiliatedHeroType.Traveler,
recruitmentInfo = RecruitmentInfo.HasQuest(
SpendOnFeastsInProvinceQuest(
componentCount = 400,
componentsFulfilled = 120,
totalGold = 400
)
)
)
val provinceWithQuest = province.copy(unaffiliatedHeroes = Vector(questHero))
val result = FeastCommand
.make(
actingFactionId = actingFactionId,
province = provinceWithQuest,
factionProvinces = Vector(provinceWithQuest),
rulingFactionHeroes = Vector(normalHero, fullVigorHero, fullLoyaltyHero),
factionLeaderIds = Vector(7),
goldCost = 44
)
.immediateExecute
result.changedProvinces.collect { case cp: ChangedProvinceC => cp }
.flatMap(_.changedUnaffiliatedHeroes)
.find(_.heroId == questHero.heroId)
.flatMap(_.quest) shouldBe Some(
SpendOnFeastsInProvinceQuest(
componentCount = 400,
componentsFulfilled = 164,
totalGold = 400
)
)
}
it should "increment across-realm feast quest progress in any faction province" in {
val questHero = UnaffiliatedHeroC(
heroId = 91,
unaffiliatedHeroType = UnaffiliatedHeroType.Traveler,
recruitmentInfo = RecruitmentInfo.HasQuest(
SpendOnFeastsAcrossRealmQuest(
componentCount = 900,
componentsFulfilled = 250,
totalGold = 900
)
)
)
val otherProvince = ProvinceC(
id = 13,
rulingFactionId = Some(3),
rulingHeroId = Some(10),
rulingFactionHeroIds = Vector(10),
unaffiliatedHeroes = Vector(questHero)
)
val result = FeastCommand
.make(
actingFactionId = actingFactionId,
province = province,
factionProvinces = Vector(province, otherProvince),
rulingFactionHeroes = Vector(normalHero, fullVigorHero, fullLoyaltyHero),
factionLeaderIds = Vector(7),
goldCost = 44
)
.immediateExecute
result.changedProvinces.collect { case cp: ChangedProvinceC => cp }
.flatMap(_.changedUnaffiliatedHeroes)
.find(_.heroId == questHero.heroId)
.flatMap(_.quest) shouldBe Some(
SpendOnFeastsAcrossRealmQuest(
componentCount = 900,
componentsFulfilled = 294,
totalGold = 900
)
)
}
}
@@ -47,6 +47,8 @@ import net.eagle0.eagle.model.state.quest.{
ReleasePrisonerQuest,
ReturnPrisonerQuest,
SpecificExpansionQuest,
SpendOnFeastsAcrossRealmQuest,
SpendOnFeastsInProvinceQuest,
TruceCountQuest,
TruceWithFactionQuest,
UpgradeBattalionQuest,
@@ -1138,6 +1140,82 @@ class QuestCreationUtilsTest extends AnyFlatSpec with Matchers with BeforeAndAft
}
}
behavior of "spend on feasts in province quests"
it should "include a spend on feasts in province quest for about two thirds the old cost" in {
val quests = QuestCreationUtils
.availableQuests(
province = province,
allProvinces = allProvinces,
factions = factions,
battalions = Vector(),
heroes = Vector(),
functionalRandom = new JankyRandom(new Random())
)
.newValue
forExactly(1, quests) { quest =>
inside(quest) {
case SpendOnFeastsInProvinceQuest(count, fulfilled, totalGold) =>
count shouldBe totalGold
fulfilled shouldBe 0
totalGold should be >= 333
totalGold should be <= 667
}
}
}
behavior of "spend on feasts across realm quests"
it should "not include a spend on feasts across realm quest if the player has only one province" in {
val quests = QuestCreationUtils
.availableQuests(
province = province,
allProvinces = allProvinces,
factions = factions,
battalions = Vector(),
heroes = Vector(),
functionalRandom = new JankyRandom(new Random())
)
.newValue
forAll(quests) { quest =>
quest should not matchPattern {
case SpendOnFeastsAcrossRealmQuest(_, _, _) =>
}
}
}
it should "include a more expensive spend on feasts across realm quest if the player has 2 provinces" in {
val quests = QuestCreationUtils
.availableQuests(
province = province,
allProvinces = Vector(
province,
immediate1,
immediate2,
twoAway.withRulingFactionId(fid),
threeAway,
fourAway
),
factions = factions,
battalions = Vector(),
heroes = Vector(),
functionalRandom = new JankyRandom(new Random())
)
.newValue
forExactly(1, quests) { quest =>
inside(quest) {
case SpendOnFeastsAcrossRealmQuest(count, fulfilled, totalGold) =>
count shouldBe totalGold
fulfilled shouldBe 0
totalGold should be >= (750 * Math.pow(2, 0.5)).ceil.toInt
totalGold should be <= (1250 * Math.pow(2, 0.5)).ceil.toInt
}
}
}
behavior of "available manage prisoner quests"
it should "include execute prisoner quests for any prisoners" in {