Delete LegacyRansomValidity and convert test to Scala types (#5236)

Remove the proto wrapper LegacyRansomValidity and its only caller (the
proto overload of AvailableResolveRansomOfferCommandFactory). Convert
the test from proto types to Scala types, replacing ScalaPB's .update()
lens syntax with helper functions.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-12 16:35:21 -08:00
committed by GitHub
co-authored by Claude Opus 4.5
parent 65a9cf1f97
commit ca2a13e17b
6 changed files with 294 additions and 409 deletions
@@ -1,37 +1,18 @@
package net.eagle0.eagle.library.actions.availability
import net.eagle0.eagle.api.available_command.ResolveRansomOfferAvailableCommand
import net.eagle0.eagle.common.diplomacy_offer.{
DiplomacyOffer as DiplomacyOfferProto,
HostageOfferedInExchange as HostageOfferedInExchangeProto,
InvitationDetails,
PrisonerOfferedInExchange as PrisonerOfferedInExchangeProto,
PrisonerToBeRansomed as PrisonerToBeRansomedProto,
RansomOfferDetails as RansomOfferDetailsProto
}
import net.eagle0.eagle.common.diplomacy_offer_status.DiplomacyOfferStatus.{
DIPLOMACY_OFFER_STATUS_REJECTED,
DIPLOMACY_OFFER_STATUS_UNRESOLVED
}
import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.library.util.ransom_validity.RansomValidity
import net.eagle0.eagle.library.util.LegacyRansomValidity
import net.eagle0.eagle.library.EagleInternalException
import net.eagle0.eagle.model.proto_converters.diplomacy_offer.status.StatusConverter
import net.eagle0.eagle.model.state.diplomacy_offer.{DiplomacyOffer, Invitation, RansomOffer}
import net.eagle0.eagle.model.state.diplomacy_offer.{Invitation, RansomOffer}
import net.eagle0.eagle.model.state.diplomacy_offer.status.{Rejected, Unresolved}
import net.eagle0.eagle.model.state.game_state.GameState as ScalaGameState
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.FactionId
object AvailableResolveRansomOfferCommandFactory {
// ==================== Scala overload ====================
// Don't show an offer if the same faction has an outstanding offer that trades away the prisoner to be ransomed,
// or either the offering or targeted faction has an incoming invitation that must be resolved first
private def hasOutstandingConflictScala(
private def hasOutstandingConflict(
offer: RansomOffer,
gameState: ScalaGameState
gameState: GameState
): Boolean = {
val prisonerHeroId = offer.prisonerToBeRansomed.prisonerHeroId
@@ -69,9 +50,8 @@ object AvailableResolveRansomOfferCommandFactory {
import net.eagle0.eagle.model.state.command.available.AvailableCommand
import net.eagle0.eagle.model.state.command.available.AvailableCommand.DiplomacyOfferInfo
/** Scala overload - returns pure Scala type */
def availableCommand(
gameState: ScalaGameState,
gameState: GameState,
factionId: FactionId
): Option[AvailableCommand] = {
val provinces = gameState.provinces.values.toVector
@@ -80,7 +60,7 @@ object AvailableResolveRansomOfferCommandFactory {
.incomingDiplomacyOffers
.collect { case ro: RansomOffer => ro }
.filter(offer => RansomValidity.isStillValid(offer, provinces))
.filter(offer => !hasOutstandingConflictScala(offer, gameState))
.filter(offer => !hasOutstandingConflict(offer, gameState))
.filter(_.status == Unresolved) match {
case items if items.isEmpty => None
case offers =>
@@ -98,86 +78,4 @@ object AvailableResolveRansomOfferCommandFactory {
)
}
}
// ==================== Proto overload ====================
// Don't show an offer if the same faction has an outstanding offer that trades away the prisoner to be ransomed,
// or either the offering or targeted faction has an incoming invitation that must be resolved first
private def hasOutstandingConflict(
offer: DiplomacyOfferProto,
gs: GameState
): Boolean = offer.offerDetails match {
case RansomOfferDetailsProto(
Some(prisonerToBeRansomed),
_,
_,
_,
_ /* unknownFieldSet */
) =>
gs.factions.values
.flatMap(_.incomingDiplomacyOffers)
.exists { diploOffer =>
diploOffer.offerDetails match {
case RansomOfferDetailsProto(
_,
prisonersOffered,
_,
_,
_ /* unknownFieldSet */
) =>
prisonersOffered
.exists(
_.heroId == prisonerToBeRansomed.prisonerHeroId
) && diploOffer.status != DIPLOMACY_OFFER_STATUS_REJECTED
case _ => false
}
} || gs
.factions(offer.originatingFactionId)
.incomingDiplomacyOffers
.filter(_.status != DIPLOMACY_OFFER_STATUS_REJECTED)
.exists { invitation =>
invitation.offerDetails match {
case InvitationDetails(_ /* unknownFieldSet */ ) => true
case _ => false
}
} || gs
.factions(offer.targetFactionId)
.incomingDiplomacyOffers
.filter(_.status != DIPLOMACY_OFFER_STATUS_REJECTED)
.exists {
_.offerDetails match {
case InvitationDetails(_ /* unknownFieldSet */ ) => true
case _ => false
}
}
case _ => throw new EagleInternalException("Not a ransom offer")
}
def availableCommand(
gameState: GameState,
factionId: FactionId
): Option[ResolveRansomOfferAvailableCommand] =
gameState
.factions(factionId)
.incomingDiplomacyOffers
.collect { diplomacyOffer =>
diplomacyOffer.offerDetails match {
case _: RansomOfferDetailsProto => diplomacyOffer
}
}
.filter(offer => LegacyRansomValidity.isStillValid(offer, gameState))
.filter(offer => !hasOutstandingConflict(offer, gameState))
.filter(_.status == DIPLOMACY_OFFER_STATUS_UNRESOLVED) match {
case items if items.isEmpty => None
case offers: Seq[DiplomacyOffer] =>
Some(
ResolveRansomOfferAvailableCommand(
offers = offers.map {
_.withEligibleStatuses(
EligibleDiplomacyStatuses.acceptRejectStatuses.map(StatusConverter.toProto)
)
}
)
)
}
}
@@ -697,15 +697,8 @@ scala_library(
],
deps = [
":eligible_diplomacy_statuses",
":pkg",
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/common:diplomacy_offer_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/library:eagle_internal_exception",
"//src/main/scala/net/eagle0/eagle/library/util:legacy_ransom_validity",
"//src/main/scala/net/eagle0/eagle/library/util/ransom_validity",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/diplomacy_offer/status",
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
"//src/main/scala/net/eagle0/eagle/model/state/diplomacy_offer",
"//src/main/scala/net/eagle0/eagle/model/state/diplomacy_offer/status",
@@ -237,25 +237,6 @@ scala_library(
],
)
scala_library(
name = "legacy_ransom_validity",
srcs = ["LegacyRansomValidity.scala"],
visibility = [
"//src/main/scala/net/eagle0/eagle/library:__subpackages__",
"//src/test/scala/net/eagle0/eagle/library:__subpackages__",
],
deps = [
"//src/main/protobuf/net/eagle0/eagle/common:diplomacy_offer_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/common:unaffiliated_hero_type_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/library/util/ransom_validity",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/diplomacy_offer",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/province",
"//src/main/scala/net/eagle0/eagle/model/state/diplomacy_offer",
],
)
scala_library(
name = "map_generator",
srcs = ["MapGenerator.scala"],
@@ -1,15 +0,0 @@
package net.eagle0.eagle.library.util
import net.eagle0.eagle.common.diplomacy_offer.DiplomacyOffer
import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.library.util.ransom_validity.RansomValidity
import net.eagle0.eagle.model.proto_converters.diplomacy_offer.DiplomacyOfferConverter
import net.eagle0.eagle.model.proto_converters.province.ProvinceConverter
object LegacyRansomValidity {
def isStillValid(offer: DiplomacyOffer, gs: GameState): Boolean =
RansomValidity.isStillValid(
DiplomacyOfferConverter.fromProto(offer),
gs.provinces.values.map(ProvinceConverter.fromProto).toVector
)
}
@@ -1,112 +1,155 @@
package net.eagle0.eagle.library.actions.availability
import net.eagle0.eagle.common.date.Date
import net.eagle0.eagle.common.diplomacy_offer.{
DiplomacyOffer,
import net.eagle0.eagle.model.state.command.available.AvailableCommand
import net.eagle0.eagle.model.state.command.available.AvailableCommand.DiplomacyOfferInfo
import net.eagle0.eagle.model.state.date.Date
import net.eagle0.eagle.model.state.diplomacy_offer.{DiplomacyOffer, Invitation, RansomOffer}
import net.eagle0.eagle.model.state.diplomacy_offer.status.{Accepted, Rejected, Unresolved}
import net.eagle0.eagle.model.state.diplomacy_offer.RansomOfferDetails.{
HostageOfferedInExchange,
InvitationDetails,
PrisonerOfferedInExchange,
PrisonerToBeRansomed,
RansomOfferDetails
PrisonerToBeRansomed
}
import net.eagle0.eagle.common.diplomacy_offer_status.DiplomacyOfferStatus.{
DIPLOMACY_OFFER_STATUS_ACCEPTED,
DIPLOMACY_OFFER_STATUS_REJECTED,
DIPLOMACY_OFFER_STATUS_UNRESOLVED
}
import net.eagle0.eagle.common.recruitment_info.RecruitmentInfo
import net.eagle0.eagle.common.recruitment_info.RecruitmentStatus.{
RECRUITMENT_STATUS_PRISONER,
RECRUITMENT_STATUS_TRAVELER
}
import net.eagle0.eagle.common.unaffiliated_hero_type.UnaffiliatedHeroType.{
UNAFFILIATED_HERO_PRISONER,
UNAFFILIATED_HERO_TRAVELER
}
import net.eagle0.eagle.internal.faction.Faction
import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.province.Province
import net.eagle0.eagle.internal.unaffiliated_hero.UnaffiliatedHero
import net.eagle0.eagle.library.util.IDable.{mapifyFactions, mapifyProvinces}
import net.eagle0.eagle.model.state.faction.concrete.FactionC
import net.eagle0.eagle.model.state.faction.FactionT
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.model.state.province.concrete.ProvinceC
import net.eagle0.eagle.model.state.province.ProvinceT
import net.eagle0.eagle.model.state.run_status.RunStatus
import net.eagle0.eagle.model.state.unaffiliated_hero.concrete.UnaffiliatedHeroC
import net.eagle0.eagle.model.state.unaffiliated_hero.RecruitmentInfo
import net.eagle0.eagle.model.state.unaffiliated_hero.UnaffiliatedHeroType.{Prisoner, Traveler}
import net.eagle0.eagle.model.state.RoundPhase.PlayerCommands
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import org.scalatest.Inside.inside
class AvailableResolveRansomOfferCommandFactoryTest extends AnyFlatSpec with Matchers {
private val actingFid = 19
private val firstOfferingFid = 22
private val secondOfferingFid = 6
private val firstOffer = DiplomacyOffer(
private val firstOffer = RansomOffer(
originatingFactionId = firstOfferingFid,
targetFactionId = actingFid,
messengerHeroId = 1,
messengerOriginProvinceId = 2,
offerDetails = RansomOfferDetails(
prisonerToBeRansomed = Some(
PrisonerToBeRansomed(
prisonerHeroId = 86,
provinceIdForPrisoner = 1
)
),
prisonersOffered = Vector(PrisonerOfferedInExchange(heroId = 105, provinceIdWithHero = 2)),
hostagesOffered = Vector(HostageOfferedInExchange(heroId = 101, provinceIdWithHero = 2)),
goldOffered = 150
offerTextId = "test_offer",
prisonerToBeRansomed = PrisonerToBeRansomed(
prisonerHeroId = 86,
provinceIdForPrisoner = 1
),
status = DIPLOMACY_OFFER_STATUS_UNRESOLVED
prisonersOffered =
Vector(PrisonerOfferedInExchange(heroId = 105, factionIdForPrisoner = -1, provinceIdWithHero = 2)),
hostagesOffered = Vector(HostageOfferedInExchange(heroId = 101, provinceIdWithHero = 2)),
goldOffered = 150,
status = Unresolved
)
private val gameState = GameState(
currentDate = Some(Date(year = 1501, month = 6)),
randomSeed = 1L,
factions = mapifyFactions(
Faction(
id = actingFid,
incomingDiplomacyOffers = Vector(firstOffer)
),
Faction(id = firstOfferingFid),
Faction(id = secondOfferingFid)
),
provinces = mapifyProvinces(
Province(
id = 1,
rulingFactionId = Some(actingFid),
unaffiliatedHeroes = Vector(
UnaffiliatedHero(
heroId = 86,
`type` = UNAFFILIATED_HERO_PRISONER,
recruitmentInfo = Some(RecruitmentInfo(status = RECRUITMENT_STATUS_PRISONER))
)
private def makeProvinces(
province1RulingFactionId: Option[Int] = Some(actingFid),
province1Prisoners: Vector[UnaffiliatedHeroC] = Vector(
UnaffiliatedHeroC(
heroId = 86,
unaffiliatedHeroType = Prisoner,
recruitmentInfo = RecruitmentInfo.Prisoner
)
),
Province(
id = 2,
rulingFactionId = Some(firstOfferingFid),
gold = 250,
rulingFactionHeroIds = Vector(101),
unaffiliatedHeroes = Vector(
UnaffiliatedHero(
heroId = 105,
`type` = UNAFFILIATED_HERO_PRISONER,
recruitmentInfo = Some(RecruitmentInfo(status = RECRUITMENT_STATUS_PRISONER))
)
province2Gold: Int = 250,
province2RulingFactionId: Option[Int] = Some(firstOfferingFid),
province2HeroIds: Vector[Int] = Vector(101),
province2Prisoners: Vector[UnaffiliatedHeroC] = Vector(
UnaffiliatedHeroC(
heroId = 105,
unaffiliatedHeroType = Prisoner,
recruitmentInfo = RecruitmentInfo.Prisoner
)
),
Province(id = 3, rulingFactionId = Some(secondOfferingFid))
)
)
"availableCommand" should "return a command with the available ransom offers" in {
val cmd = AvailableResolveRansomOfferCommandFactory
.availableCommand(gameState, actingFid)
.get
val expectedOffer = firstOffer.withEligibleStatuses(
Vector(
DIPLOMACY_OFFER_STATUS_ACCEPTED,
DIPLOMACY_OFFER_STATUS_REJECTED
)
): Map[Int, ProvinceT] = Map(
1 -> ProvinceC(
id = 1,
rulingFactionId = province1RulingFactionId,
unaffiliatedHeroes = province1Prisoners
),
2 -> ProvinceC(
id = 2,
rulingFactionId = province2RulingFactionId,
gold = province2Gold,
rulingFactionHeroIds = province2HeroIds,
unaffiliatedHeroes = province2Prisoners
),
3 -> ProvinceC(id = 3, rulingFactionId = Some(secondOfferingFid))
)
private def makeFactions(
actingFactionOffers: Vector[DiplomacyOffer] = Vector(firstOffer),
firstOfferingFactionOffers: Vector[DiplomacyOffer] = Vector(),
secondOfferingFactionOffers: Vector[DiplomacyOffer] = Vector()
): Map[Int, FactionT] = Map(
actingFid -> FactionC(
id = actingFid,
factionHeadId = 100,
name = "Acting Faction",
leaderIds = Vector(100),
incomingDiplomacyOffers = actingFactionOffers
),
firstOfferingFid -> FactionC(
id = firstOfferingFid,
factionHeadId = 200,
name = "First Offering Faction",
leaderIds = Vector(200),
incomingDiplomacyOffers = firstOfferingFactionOffers
),
secondOfferingFid -> FactionC(
id = secondOfferingFid,
factionHeadId = 300,
name = "Second Offering Faction",
leaderIds = Vector(300),
incomingDiplomacyOffers = secondOfferingFactionOffers
)
cmd.offers should contain theSameElementsAs Vector(expectedOffer)
}
)
private def makeGameState(
factions: Map[Int, FactionT] = makeFactions(),
provinces: Map[Int, ProvinceT] = makeProvinces()
): GameState = GameState(
gameId = 1,
currentRoundId = 1,
currentPhase = PlayerCommands,
currentDate = Some(Date(year = 1501, month = Date.Month.June)),
actionResultCount = 0,
provinces = provinces,
heroes = Map(),
battalions = Map(),
destroyedBattalions = Map(),
factions = factions,
factionCommandCounts = Map(),
killedHeroes = Map(),
destroyedFactions = Map(),
outstandingBattles = Vector(),
battleCounter = 0,
deferredNotifications = Vector(),
runStatus = RunStatus.Running,
victor = None,
battalionTypes = Vector(),
randomSeed = 1L,
chronicleEntries = Vector()
)
private val gameState = makeGameState()
"availableCommand" should "return a command with the available ransom offers" in
inside(AvailableResolveRansomOfferCommandFactory.availableCommand(gameState, actingFid)) {
case Some(AvailableCommand.ResolveRansomOfferAvailable(offers)) =>
offers should have size 1
val expectedOffer = DiplomacyOfferInfo(
offeringFactionId = firstOfferingFid,
targetFactionId = actingFid,
heroId = Some(1),
eligibleStatuses = Vector(Accepted, Rejected)
)
offers should contain theSameElementsAs Vector(expectedOffer)
}
it should "return nothing if there are no ransom offers" in {
AvailableResolveRansomOfferCommandFactory.availableCommand(
@@ -116,216 +159,195 @@ class AvailableResolveRansomOfferCommandFactoryTest extends AnyFlatSpec with Mat
}
it should "return nothing if the origin province doesn't have enough gold" in {
AvailableResolveRansomOfferCommandFactory.availableCommand(
gameState.update(_.provinces(2).gold := 149),
actingFid
) shouldBe empty
val gs = makeGameState(provinces = makeProvinces(province2Gold = 149))
AvailableResolveRansomOfferCommandFactory.availableCommand(gs, actingFid) shouldBe empty
}
it should "return nothing if the origin province no longer has the hostage" in {
AvailableResolveRansomOfferCommandFactory.availableCommand(
gameState.update(_.provinces(2).rulingFactionHeroIds := Vector(102)),
actingFid
) shouldBe empty
val gs = makeGameState(provinces = makeProvinces(province2HeroIds = Vector(102)))
AvailableResolveRansomOfferCommandFactory.availableCommand(gs, actingFid) shouldBe empty
}
it should "return nothing if the origin province no longer has the prisoner" in {
AvailableResolveRansomOfferCommandFactory.availableCommand(
gameState.update(
_.provinces(2).unaffiliatedHeroes := Vector(
UnaffiliatedHero(
val gs = makeGameState(
provinces = makeProvinces(
province2Prisoners = Vector(
UnaffiliatedHeroC(
heroId = 105,
`type` = UNAFFILIATED_HERO_TRAVELER,
recruitmentInfo = Some(RecruitmentInfo(status = RECRUITMENT_STATUS_TRAVELER))
unaffiliatedHeroType = Traveler,
recruitmentInfo = RecruitmentInfo.Traveler
)
)
),
actingFid
) shouldBe empty
)
)
AvailableResolveRansomOfferCommandFactory.availableCommand(gs, actingFid) shouldBe empty
}
it should "return nothing if the origin province no longer belongs to the offering faction" in {
AvailableResolveRansomOfferCommandFactory.availableCommand(
gameState.update(
_.provinces(2).optionalRulingFactionId := None
),
actingFid
) shouldBe empty
val gs = makeGameState(provinces = makeProvinces(province2RulingFactionId = None))
AvailableResolveRansomOfferCommandFactory.availableCommand(gs, actingFid) shouldBe empty
}
it should "return nothing if the target province no longer has the prisoner to be ransomed" in {
AvailableResolveRansomOfferCommandFactory.availableCommand(
gameState.update(
_.provinces(1).unaffiliatedHeroes := Vector(
UnaffiliatedHero(
val gs = makeGameState(
provinces = makeProvinces(
province1Prisoners = Vector(
UnaffiliatedHeroC(
heroId = 86,
`type` = UNAFFILIATED_HERO_TRAVELER,
recruitmentInfo = Some(RecruitmentInfo(status = RECRUITMENT_STATUS_TRAVELER))
unaffiliatedHeroType = Traveler,
recruitmentInfo = RecruitmentInfo.Traveler
)
)
),
actingFid
) shouldBe empty
)
)
AvailableResolveRansomOfferCommandFactory.availableCommand(gs, actingFid) shouldBe empty
}
it should "return nothing if the target province no longer belongs to the target faction" in {
AvailableResolveRansomOfferCommandFactory.availableCommand(
gameState.update(
_.provinces(1).rulingFactionId := secondOfferingFid
),
actingFid
) shouldBe empty
val gs = makeGameState(provinces = makeProvinces(province1RulingFactionId = Some(secondOfferingFid)))
AvailableResolveRansomOfferCommandFactory.availableCommand(gs, actingFid) shouldBe empty
}
it should "return nothing if there is an outstanding offer to trade away the prisoner to be ransomed" in {
AvailableResolveRansomOfferCommandFactory.availableCommand(
gameState.update(
_.factions(secondOfferingFid).incomingDiplomacyOffers := Vector(
DiplomacyOffer(
originatingFactionId = actingFid,
targetFactionId = secondOfferingFid,
messengerOriginProvinceId = 1,
offerDetails = RansomOfferDetails(
prisonerToBeRansomed = Some(PrisonerToBeRansomed()),
prisonersOffered = Vector(
PrisonerOfferedInExchange(
heroId = 86,
factionIdForPrisoner = firstOfferingFid,
provinceIdWithHero = 1
)
),
hostagesOffered = Vector(),
goldOffered = 0
),
status = DIPLOMACY_OFFER_STATUS_UNRESOLVED
)
val conflictingOffer = RansomOffer(
originatingFactionId = actingFid,
targetFactionId = secondOfferingFid,
messengerHeroId = 2,
messengerOriginProvinceId = 1,
offerTextId = "conflicting_offer",
prisonerToBeRansomed = PrisonerToBeRansomed(prisonerHeroId = 0, provinceIdForPrisoner = 0),
prisonersOffered = Vector(
PrisonerOfferedInExchange(
heroId = 86,
factionIdForPrisoner = firstOfferingFid,
provinceIdWithHero = 1
)
),
actingFid
) shouldBe empty
hostagesOffered = Vector(),
goldOffered = 0,
status = Unresolved
)
val gs = makeGameState(
factions = makeFactions(secondOfferingFactionOffers = Vector(conflictingOffer))
)
AvailableResolveRansomOfferCommandFactory.availableCommand(gs, actingFid) shouldBe empty
}
it should "return a command if the outstanding offer to trade away the prisoner to be ransomed was rejected" in {
AvailableResolveRansomOfferCommandFactory
.availableCommand(
gameState.update(
_.factions(secondOfferingFid).incomingDiplomacyOffers := Vector(
DiplomacyOffer(
originatingFactionId = actingFid,
targetFactionId = secondOfferingFid,
messengerOriginProvinceId = 1,
offerDetails = RansomOfferDetails(
prisonerToBeRansomed = Some(PrisonerToBeRansomed()),
prisonersOffered = Vector(
PrisonerOfferedInExchange(
heroId = 86,
factionIdForPrisoner = firstOfferingFid,
provinceIdWithHero = 1
)
),
hostagesOffered = Vector(),
goldOffered = 0
),
status = DIPLOMACY_OFFER_STATUS_REJECTED
)
)
),
actingFid
)
.get
.offers shouldBe Vector(
firstOffer.withEligibleStatuses(
Vector(
DIPLOMACY_OFFER_STATUS_ACCEPTED,
DIPLOMACY_OFFER_STATUS_REJECTED
val conflictingOffer = RansomOffer(
originatingFactionId = actingFid,
targetFactionId = secondOfferingFid,
messengerHeroId = 2,
messengerOriginProvinceId = 1,
offerTextId = "conflicting_offer",
prisonerToBeRansomed = PrisonerToBeRansomed(prisonerHeroId = 0, provinceIdForPrisoner = 0),
prisonersOffered = Vector(
PrisonerOfferedInExchange(
heroId = 86,
factionIdForPrisoner = firstOfferingFid,
provinceIdWithHero = 1
)
)
),
hostagesOffered = Vector(),
goldOffered = 0,
status = Rejected
)
val gs = makeGameState(
factions = makeFactions(secondOfferingFactionOffers = Vector(conflictingOffer))
)
inside(AvailableResolveRansomOfferCommandFactory.availableCommand(gs, actingFid)) {
case Some(AvailableCommand.ResolveRansomOfferAvailable(offers)) =>
offers shouldBe Vector(
DiplomacyOfferInfo(
offeringFactionId = firstOfferingFid,
targetFactionId = actingFid,
heroId = Some(1),
eligibleStatuses = Vector(Accepted, Rejected)
)
)
}
}
it should "return nothing if there is an outstanding invitation to the offering faction" in {
AvailableResolveRansomOfferCommandFactory.availableCommand(
gameState.update(
_.factions(firstOfferingFid).incomingDiplomacyOffers := Vector(
DiplomacyOffer(
originatingFactionId = actingFid,
targetFactionId = secondOfferingFid,
messengerOriginProvinceId = 1,
status = DIPLOMACY_OFFER_STATUS_UNRESOLVED,
offerDetails = InvitationDetails()
)
)
),
actingFid
) shouldBe empty
val invitation = Invitation(
originatingFactionId = actingFid,
targetFactionId = secondOfferingFid,
messengerHeroId = 3,
messengerOriginProvinceId = 1,
status = Unresolved,
offerTextId = "invitation"
)
val gs = makeGameState(
factions = makeFactions(firstOfferingFactionOffers = Vector(invitation))
)
AvailableResolveRansomOfferCommandFactory.availableCommand(gs, actingFid) shouldBe empty
}
it should "return an ransom offer if the outstanding invitation to the offering faction was rejected" in {
AvailableResolveRansomOfferCommandFactory
.availableCommand(
gameState.update(
_.factions(firstOfferingFid).incomingDiplomacyOffers := Vector(
DiplomacyOffer(
originatingFactionId = actingFid,
targetFactionId = secondOfferingFid,
messengerOriginProvinceId = 1,
status = DIPLOMACY_OFFER_STATUS_REJECTED,
offerDetails = InvitationDetails()
)
)
),
actingFid
)
.get
.offers shouldBe Vector(
firstOffer.withEligibleStatuses(
Vector(
DIPLOMACY_OFFER_STATUS_ACCEPTED,
DIPLOMACY_OFFER_STATUS_REJECTED
)
)
it should "return a ransom offer if the outstanding invitation to the offering faction was rejected" in {
val invitation = Invitation(
originatingFactionId = actingFid,
targetFactionId = secondOfferingFid,
messengerHeroId = 3,
messengerOriginProvinceId = 1,
status = Rejected,
offerTextId = "invitation"
)
val gs = makeGameState(
factions = makeFactions(firstOfferingFactionOffers = Vector(invitation))
)
inside(AvailableResolveRansomOfferCommandFactory.availableCommand(gs, actingFid)) {
case Some(AvailableCommand.ResolveRansomOfferAvailable(offers)) =>
offers shouldBe Vector(
DiplomacyOfferInfo(
offeringFactionId = firstOfferingFid,
targetFactionId = actingFid,
heroId = Some(1),
eligibleStatuses = Vector(Accepted, Rejected)
)
)
}
}
it should "return nothing if there is an outstanding invitation to the acting faction" in {
AvailableResolveRansomOfferCommandFactory.availableCommand(
gameState.update(
_.factions(actingFid).incomingDiplomacyOffers :+=
DiplomacyOffer(
originatingFactionId = actingFid,
targetFactionId = secondOfferingFid,
messengerOriginProvinceId = 1,
status = DIPLOMACY_OFFER_STATUS_UNRESOLVED,
offerDetails = InvitationDetails()
)
),
actingFid
) shouldBe empty
val invitation = Invitation(
originatingFactionId = actingFid,
targetFactionId = secondOfferingFid,
messengerHeroId = 3,
messengerOriginProvinceId = 1,
status = Unresolved,
offerTextId = "invitation"
)
val gs = makeGameState(
factions = makeFactions(actingFactionOffers = Vector(firstOffer, invitation))
)
AvailableResolveRansomOfferCommandFactory.availableCommand(gs, actingFid) shouldBe empty
}
it should "return an invitation if the outstanding invitation to the acting faction was rejected" in {
AvailableResolveRansomOfferCommandFactory
.availableCommand(
gameState.update(
_.factions(actingFid).incomingDiplomacyOffers :+=
DiplomacyOffer(
originatingFactionId = firstOfferingFid,
targetFactionId = actingFid,
messengerOriginProvinceId = 1,
status = DIPLOMACY_OFFER_STATUS_REJECTED,
offerDetails = InvitationDetails()
)
),
actingFid
)
.get
.offers shouldBe Vector(
firstOffer.withEligibleStatuses(
Vector(
DIPLOMACY_OFFER_STATUS_ACCEPTED,
DIPLOMACY_OFFER_STATUS_REJECTED
)
)
it should "return a ransom offer if the outstanding invitation to the acting faction was rejected" in {
val invitation = Invitation(
originatingFactionId = firstOfferingFid,
targetFactionId = actingFid,
messengerHeroId = 3,
messengerOriginProvinceId = 1,
status = Rejected,
offerTextId = "invitation"
)
val gs = makeGameState(
factions = makeFactions(actingFactionOffers = Vector(firstOffer, invitation))
)
inside(AvailableResolveRansomOfferCommandFactory.availableCommand(gs, actingFid)) {
case Some(AvailableCommand.ResolveRansomOfferAvailable(offers)) =>
offers shouldBe Vector(
DiplomacyOfferInfo(
offeringFactionId = firstOfferingFid,
targetFactionId = actingFid,
heroId = Some(1),
eligibleStatuses = Vector(Accepted, Rejected)
)
)
}
}
}
@@ -604,15 +604,21 @@ scala_test(
name = "available_resolve_ransom_offer_command_factory_test",
srcs = ["AvailableResolveRansomOfferCommandFactoryTest.scala"],
deps = [
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:faction_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:hero_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:province_scala_proto",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/library/actions/availability:available_resolve_ransom_offer_command_factory",
"//src/main/scala/net/eagle0/eagle/library/actions/availability:pkg",
"//src/main/scala/net/eagle0/eagle/library/util:idable",
"//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/date",
"//src/main/scala/net/eagle0/eagle/model/state/diplomacy_offer",
"//src/main/scala/net/eagle0/eagle/model/state/diplomacy_offer/status",
"//src/main/scala/net/eagle0/eagle/model/state/faction",
"//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/province",
"//src/main/scala/net/eagle0/eagle/model/state/province/concrete",
"//src/main/scala/net/eagle0/eagle/model/state/run_status",
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero",
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero/concrete",
],
)