Add SpendOnFeastsQuest generation (#5893)

* Add SpendOnFeastsQuest generation

- Add spendOnFeastsQuests to QuestCreationUtils that creates quests
  requiring 2-4 feasts spending 200-500 gold total
- Modify FeastCommand to track quest progress when feasts are held
- Pass factionLeaderProvinces to FeastCommand from CommandFactory
- Add quest_fulfillment_utils, quest, and unaffiliated_hero deps

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix FeastCommandTest to pass factionLeaderProvinces parameter

The FeastCommand.make method was updated to require a factionLeaderProvinces
parameter for quest progress tracking, but the test file wasn't updated.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Simplify SpendOnFeastsQuest to track total gold spent

- Remove factionLeaderProvinces parameter from FeastCommand
- Track gold spent (componentCount = totalGold, increment by goldCost per feast)
- Only check current province for quest progress
- Simplify quest creation (single random for totalGold target)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Change SpendOnFeastsQuest target gold range to 500-1000

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-06 13:26:46 -08:00
committed by GitHub
co-authored by Claude Opus 4.5
parent 7221ee48d6
commit 647ad25bb4
4 changed files with 62 additions and 12 deletions
@@ -421,6 +421,7 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library/settings:loyalty_gain_from_feast",
"//src/main/scala/net/eagle0/eagle/library/settings:vigor_gain_from_feast",
"//src/main/scala/net/eagle0/eagle/library/util:price_index_utils",
"//src/main/scala/net/eagle0/eagle/library/util/quest_fulfillment:quest_fulfillment_utils",
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
"//src/main/scala/net/eagle0/eagle/model/action_result:changed_battalion_trait",
"//src/main/scala/net/eagle0/eagle/model/action_result:notification_trait",
@@ -434,6 +435,8 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/model/state/hero",
"//src/main/scala/net/eagle0/eagle/model/state/hero:gender",
"//src/main/scala/net/eagle0/eagle/model/state/province",
"//src/main/scala/net/eagle0/eagle/model/state/quest",
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero",
],
)
@@ -226,12 +226,13 @@ class CommandFactory extends TCommandFactory {
)
case (ac: FeastAvailable, _: FeastSelected.type) =>
val province = gameState.provinces(ac.actingProvinceId)
val faction = gameState.factions(actingFactionId)
FeastCommand.make(
actingFactionId = actingFactionId,
province = province,
rulingFactionHeroes = province.rulingFactionHeroIds
.map(gameState.heroes),
factionLeaderIds = gameState.factions(actingFactionId).leaderIds,
factionLeaderIds = faction.leaderIds,
goldCost = ac.goldCost
)
case (ac: FreeForAllDecisionAvailable, sc: FreeForAllDecisionSelected) =>
@@ -3,6 +3,7 @@ package net.eagle0.eagle.library.actions.impl.command
import net.eagle0.eagle.{FactionId, HeroId}
import net.eagle0.eagle.library.actions.impl.common.ProtolessSimpleAction
import net.eagle0.eagle.library.settings.{LoyaltyGainFromFeast, VigorGainFromFeast}
import net.eagle0.eagle.library.util.quest_fulfillment.QuestFulfillmentUtils
import net.eagle0.eagle.library.util.PriceIndexUtils
import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedProvinceC
import net.eagle0.eagle.model.action_result.concrete.{ActionResultC, ChangedHeroC, StatDelta, StatNoChange}
@@ -10,6 +11,7 @@ import net.eagle0.eagle.model.action_result.types.ActionResultType.Feast
import net.eagle0.eagle.model.action_result.ActionResultT
import net.eagle0.eagle.model.state.hero.HeroT
import net.eagle0.eagle.model.state.province.ProvinceT
import net.eagle0.eagle.model.state.quest.SpendOnFeastsQuest
object FeastCommand {
def make(
@@ -56,18 +58,43 @@ object FeastCommand {
)
)
private def changedProvinces: Vector[ChangedProvinceC] =
Vector(
ChangedProvinceC(
provinceId = province.id,
goldDelta = Some(-goldCost),
newPriceIndex = PriceIndexUtils.maybeNewPriceIndexFromGoldSpend(
currentPriceIndex = province.priceIndex,
goldSpent = goldCost
)
)
private def changedProvinces: Vector[ChangedProvinceC] = {
// Increment SpendOnFeastsQuest progress by goldCost for unaffiliated hero 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
}
)
questProgressChange match {
case Some(cp) =>
Vector(
cp.copy(
goldDelta = Some(-goldCost),
newPriceIndex = PriceIndexUtils.maybeNewPriceIndexFromGoldSpend(
currentPriceIndex = province.priceIndex,
goldSpent = goldCost
)
)
)
case None =>
Vector(
ChangedProvinceC(
provinceId = province.id,
goldDelta = Some(-goldCost),
newPriceIndex = PriceIndexUtils.maybeNewPriceIndexFromGoldSpend(
currentPriceIndex = province.priceIndex,
goldSpent = goldCost
)
)
)
}
}
override def immediateExecute: ActionResultT =
ActionResultC(
actionResultType = Feast,
@@ -60,6 +60,7 @@ import net.eagle0.eagle.model.state.quest.{
ReturnPrisonerQuest,
SendSuppliesQuest,
SpecificExpansionQuest,
SpendOnFeastsQuest,
StartBlizzardQuest,
StartEpidemicQuest,
SuppressRiotByForceQuest,
@@ -158,7 +159,8 @@ object QuestCreationUtils {
sendSuppliesQuests,
restProvinceQuests,
reconProvincesQuests,
repairDevastationQuests
repairDevastationQuests,
spendOnFeastsQuests
)
) {
case (questCreator, fr) =>
@@ -1052,4 +1054,21 @@ object QuestCreationUtils {
)
}
}
private def spendOnFeastsQuests(
@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 =>
Vector(
SpendOnFeastsQuest(
componentCount = totalGold,
componentsFulfilled = 0,
totalGold = totalGold
)
)
}
}