mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 21:55:42 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e13a5e209f | ||
|
|
5d8aefb6c8 | ||
|
|
54bfe56cc4 | ||
|
|
24252d1243 | ||
|
|
d6b9edde4c | ||
|
|
3ec3d59740 |
@@ -11,8 +11,8 @@ This document analyzes all actions and commands in `src/main/scala/net/eagle0/ea
|
||||
|
||||
Based on BUILD.bazel dependency analysis (2025-09-04):
|
||||
- **Total Commands Analyzed:** 40
|
||||
- **Commands Fully Migrated (No Protobuf):** 34 (85%)
|
||||
- **Commands Still Using Protobuf:** 6 (15%)
|
||||
- **Commands Fully Migrated (No Protobuf):** 36 (90%)
|
||||
- **Commands Still Using Protobuf:** 4 (10%)
|
||||
- **Actions:** Most still have protobuf dependencies
|
||||
- **Base Classes:** 8 protoless variants available, 6 still use protobuf
|
||||
|
||||
@@ -182,8 +182,10 @@ These commands have **NO protobuf dependencies** in their BUILD.bazel targets:
|
||||
| MarchCommand.scala | `march_command` | **NEWLY MIGRATED** - Uses ProtolessSimpleAction, Army domain model |
|
||||
| ResolveTruceOfferCommand.scala | `resolve_truce_offer_command` | **NEWLY MIGRATED** - Uses ProtolessSimpleAction, TruceOffer domain model, LLM integration |
|
||||
| ResolveInvitationCommand.scala | `resolve_invitation_command` | **NEWLY MIGRATED** - Uses ProtolessSimpleAction, Invitation domain model, LLM integration |
|
||||
| ResolveRansomOfferCommand.scala | `resolve_ransom_offer_command` | **NEWLY MIGRATED** - Uses ProtolessSimpleAction, RansomOffer domain model, LLM integration |
|
||||
| ResolveTributeCommand.scala | `resolve_tribute_command` | **NEWLY MIGRATED** - Uses ProtolessSimpleAction, TributeAmount domain model |
|
||||
|
||||
**Total: 34 commands (85% of all commands)**
|
||||
**Total: 36 commands (90% of all commands)**
|
||||
|
||||
### ⚠️ Commands Still Using Protobuf Dependencies
|
||||
|
||||
@@ -194,10 +196,10 @@ These commands have protobuf dependencies in their BUILD.bazel files:
|
||||
| DiplomacyCommand.scala | `diplomacy_command` | `diplomacy_option`, `diplomacy_offer`, `available_command` | High - Complex diplomacy |
|
||||
| ResolveAllianceOfferCommand.scala | `resolve_alliance_offer_command` | `diplomacy_offer`, `available_command`, `action_result` | High - Diplomacy |
|
||||
| ResolveBreakAllianceCommand.scala | `resolve_break_alliance_command` | `diplomacy_offer`, `available_command`, `action_result` | High - Diplomacy |
|
||||
| ResolveRansomOfferCommand.scala | `resolve_ransom_offer_command` | `diplomacy_offer`, `available_command`, `game_state` | High - Diplomacy |
|
||||
| ResolveTributeCommand.scala | `resolve_tribute_command` | `diplomacy_offer`, `available_command`, `game_state` | High - Diplomacy |
|
||||
| ~~ResolveRansomOfferCommand.scala~~ | ~~`resolve_ransom_offer_command`~~ | ~~**MIGRATED**~~ | ~~High - Diplomacy~~ |
|
||||
| ~~ResolveTributeCommand.scala~~ | ~~`resolve_tribute_command`~~ | ~~**MIGRATED**~~ | ~~High - Diplomacy~~ |
|
||||
|
||||
**Total: 6 commands (15% of all commands)**
|
||||
**Total: 4 commands (10% of all commands)**
|
||||
|
||||
### Migration Complexity Analysis
|
||||
|
||||
|
||||
@@ -1201,17 +1201,24 @@ scala_library(
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:action_result_type_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:diplomacy_offer_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:action_result_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library:eagle_client_exception",
|
||||
"//src/main/scala/net/eagle0/eagle/library:eagle_internal_exception",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:simple_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/llm_request_generators/diplomacy_llm_helpers:diplomacy_resolution_llm_request_generator",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_simple_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:eagle_require",
|
||||
"//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:changed_hero_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:notification_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:action_result_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:changed_battalion_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:changed_faction_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:notification_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:offer_resolved_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/diplomacy_offer",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/diplomacy_offer/status",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1223,17 +1230,23 @@ scala_library(
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:action_result_type_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:diplomacy_offer_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:action_result_scala_proto",
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library:eagle_client_exception",
|
||||
"//src/main/scala/net/eagle0/eagle/library:eagle_internal_exception",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:simple_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/llm_request_generators/diplomacy_llm_helpers:diplomacy_resolution_llm_request_generator",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_simple_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:eagle_require",
|
||||
"//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:changed_hero_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:notification_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:action_result_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:changed_battalion_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:changed_faction_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:notification_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:offer_resolved_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/diplomacy_offer",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/diplomacy_offer/status",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1333,26 +1346,22 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions:__subpackages__",
|
||||
],
|
||||
exports = [
|
||||
":command_builder",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:deterministic_single_result_action",
|
||||
],
|
||||
deps = [
|
||||
":command_builder",
|
||||
":deterministic_single_result_command",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:action_result_type_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:diplomacy_offer_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/internal:action_result_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/actions/impl/common:action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:deterministic_single_result_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:date_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_simple_action",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:eagle_require",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils:legacy_faction_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:changed_hero_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:notification_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:action_result_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:tribute_paid_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:tribute_refused_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:army",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:tribute_amount",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -677,7 +677,32 @@ class CommandFactory {
|
||||
ac: ResolveTributeAvailableCommand,
|
||||
sc: ResolveTributeSelectedCommand
|
||||
) =>
|
||||
ResolveTributeCommand.make(actingFactionId, gameState, ac, sc)
|
||||
import net.eagle0.eagle.model.proto_converters.TributeAmountConverter
|
||||
|
||||
val demandingFid = sc.demandingFactionId
|
||||
val selectedDemand = ac.demands
|
||||
.find(_.demandingFactionId == demandingFid)
|
||||
.getOrElse(
|
||||
throw new IllegalArgumentException(
|
||||
s"Selected faction ID $demandingFid was not among options ${ac.demands.map(_.demandingFactionId)}"
|
||||
)
|
||||
)
|
||||
val tributeAmount =
|
||||
TributeAmountConverter.fromProto(selectedDemand.tributeDemanded.get)
|
||||
|
||||
ResolveTributeCommand.make(
|
||||
actingFactionId = actingFactionId,
|
||||
demandingFactionId = demandingFid,
|
||||
tributeAmount = tributeAmount,
|
||||
paid = sc.paid,
|
||||
availableDemandingFactionIds =
|
||||
ac.demands.map(_.demandingFactionId).toVector,
|
||||
actingProvinceId = ac.actingProvinceId,
|
||||
availableGold = ac.availableGold,
|
||||
availableFood = ac.availableFood,
|
||||
gameId = gameState.gameId,
|
||||
currentRoundId = gameState.currentRoundId
|
||||
)
|
||||
|
||||
case (ac: RestAvailableCommand, sc: RestSelectedCommand) =>
|
||||
RestCommand.make(
|
||||
|
||||
+175
-114
@@ -1,135 +1,196 @@
|
||||
package net.eagle0.eagle.library.actions.impl.command
|
||||
import net.eagle0.eagle.api.available_command.ResolveAllianceOfferAvailableCommand
|
||||
import net.eagle0.eagle.api.selected_command.ResolveAllianceOfferSelectedCommand
|
||||
import net.eagle0.eagle.common.action_result_notification_details.Notification.Llm
|
||||
import net.eagle0.eagle.common.action_result_notification_details.{
|
||||
AllianceAcceptedDetails,
|
||||
AllianceAmbassadorImprisonedDetails,
|
||||
AllianceRejectedDetails,
|
||||
Notification
|
||||
}
|
||||
import net.eagle0.eagle.common.action_result_type.ActionResultType
|
||||
import net.eagle0.eagle.common.diplomacy_offer.DiplomacyOffer
|
||||
import net.eagle0.eagle.common.diplomacy_offer_status.DiplomacyOfferStatus
|
||||
import net.eagle0.eagle.common.diplomacy_offer_status.DiplomacyOfferStatus.{
|
||||
DIPLOMACY_OFFER_STATUS_ACCEPTED,
|
||||
DIPLOMACY_OFFER_STATUS_IMPRISONED,
|
||||
DIPLOMACY_OFFER_STATUS_INVALIDATED,
|
||||
DIPLOMACY_OFFER_STATUS_REJECTED,
|
||||
DIPLOMACY_OFFER_STATUS_UNKNOWN,
|
||||
DIPLOMACY_OFFER_STATUS_UNRESOLVED,
|
||||
Unrecognized
|
||||
}
|
||||
import net.eagle0.eagle.internal.action_result.ActionResult
|
||||
import net.eagle0.eagle.internal.changed_faction.ChangedFaction
|
||||
import net.eagle0.eagle.library.actions.impl.common.SimpleAction
|
||||
import net.eagle0.eagle.library.actions.generated_text_request_generators.diplomacy_llm_helpers.DiplomacyResolutionLlmRequestGenerator
|
||||
|
||||
import net.eagle0.eagle.library.actions.impl.common.ProtolessSimpleAction
|
||||
import net.eagle0.eagle.library.util.EagleRequire.commandRequire
|
||||
import net.eagle0.eagle.library.{EagleCommandException, EagleInternalException}
|
||||
import net.eagle0.eagle.{FactionId, GameId, RoundId}
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.action_result.concrete.{
|
||||
ActionResultC,
|
||||
ChangedFactionC,
|
||||
NotificationC
|
||||
}
|
||||
import net.eagle0.eagle.model.action_result.generated_text_request.LlmRequestT.AllianceOfferResolutionMessage
|
||||
import net.eagle0.eagle.model.action_result.{NotificationDetails, NotificationT}
|
||||
import net.eagle0.eagle.model.action_result.types.OfferResolvedResultType
|
||||
import net.eagle0.eagle.model.state.diplomacy_offer.status.{
|
||||
Accepted,
|
||||
Imprisoned,
|
||||
Rejected,
|
||||
Status
|
||||
}
|
||||
import net.eagle0.eagle.model.state.diplomacy_offer.AllianceOffer
|
||||
import net.eagle0.eagle.library.EagleCommandException
|
||||
import net.eagle0.eagle.{FactionId, GameId, HeroId, ProvinceId, RoundId}
|
||||
|
||||
object ResolveAllianceOfferCommand {
|
||||
private case class ResolveAllianceOfferCommand(
|
||||
sc: ResolveAllianceOfferSelectedCommand,
|
||||
allianceOffer: DiplomacyOffer,
|
||||
resolution: DiplomacyOfferStatus,
|
||||
gameId: GameId,
|
||||
currentRoundId: RoundId
|
||||
) extends SimpleAction {
|
||||
override def immediateExecute: ActionResult = {
|
||||
val llmRequest =
|
||||
DiplomacyResolutionLlmRequestGenerator.allianceResolutionRequest(
|
||||
gameId = gameId,
|
||||
currentRoundId = currentRoundId,
|
||||
originatingFactionId = allianceOffer.originatingFactionId,
|
||||
targetFactionId = allianceOffer.targetFactionId,
|
||||
messengerHeroId = allianceOffer.messengerHeroId,
|
||||
resolution = resolution
|
||||
)
|
||||
|
||||
ActionResult(
|
||||
`type` = ActionResultType.OFFER_RESOLVED,
|
||||
player = Some(allianceOffer.targetFactionId),
|
||||
changedFactions = Vector(
|
||||
ChangedFaction(
|
||||
id = allianceOffer.targetFactionId,
|
||||
removedIncomingDiplomacyOfferFactionIds =
|
||||
Vector(allianceOffer.originatingFactionId),
|
||||
addedIncomingDiplomacyOffers =
|
||||
Vector(allianceOffer.withStatus(resolution))
|
||||
)
|
||||
),
|
||||
newDeferredNotification = Some(
|
||||
Notification(
|
||||
details = resolution match {
|
||||
case DIPLOMACY_OFFER_STATUS_ACCEPTED =>
|
||||
AllianceAcceptedDetails(
|
||||
offeringFactionId = allianceOffer.originatingFactionId,
|
||||
targetFactionId = allianceOffer.targetFactionId,
|
||||
ambassadorHeroId = allianceOffer.messengerHeroId
|
||||
)
|
||||
|
||||
case DIPLOMACY_OFFER_STATUS_REJECTED =>
|
||||
AllianceRejectedDetails(
|
||||
offeringFactionId = allianceOffer.originatingFactionId,
|
||||
targetFactionId = allianceOffer.targetFactionId,
|
||||
ambassadorHeroId = allianceOffer.messengerHeroId
|
||||
)
|
||||
|
||||
case DIPLOMACY_OFFER_STATUS_IMPRISONED =>
|
||||
AllianceAmbassadorImprisonedDetails(
|
||||
offeringFactionId = allianceOffer.originatingFactionId,
|
||||
targetFactionId = allianceOffer.targetFactionId,
|
||||
ambassadorHeroId = allianceOffer.messengerHeroId
|
||||
)
|
||||
|
||||
case DIPLOMACY_OFFER_STATUS_INVALIDATED =>
|
||||
throw new EagleCommandException(
|
||||
"Invalid diplomacy offers status for command"
|
||||
)
|
||||
|
||||
case DIPLOMACY_OFFER_STATUS_UNKNOWN |
|
||||
DIPLOMACY_OFFER_STATUS_UNRESOLVED | Unrecognized(_) =>
|
||||
throw new EagleInternalException(
|
||||
s"Unknown diplomacy offer status"
|
||||
)
|
||||
},
|
||||
llm = Llm.LlmId(llmRequest.id)
|
||||
)
|
||||
),
|
||||
newGeneratedTextRequests = Vector(llmRequest)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def make(
|
||||
actingFactionId: FactionId,
|
||||
availableCommand: ResolveAllianceOfferAvailableCommand,
|
||||
selectedCommand: ResolveAllianceOfferSelectedCommand,
|
||||
originatingFactionId: FactionId,
|
||||
messengerHeroId: HeroId,
|
||||
messengerOriginProvinceId: ProvinceId,
|
||||
allianceOffer: AllianceOffer,
|
||||
resolution: Status,
|
||||
availableOriginatingFactionIds: Vector[FactionId],
|
||||
availableResolutions: Vector[Status],
|
||||
gameId: GameId,
|
||||
currentRoundId: RoundId
|
||||
): SimpleAction = {
|
||||
// check requirements
|
||||
val offeringFid = selectedCommand.originatingFactionId
|
||||
val offers = availableCommand.offers
|
||||
val selectedOffer = offers.find(_.originatingFactionId == offeringFid)
|
||||
|
||||
): ProtolessSimpleAction = {
|
||||
// Validation
|
||||
commandRequire(
|
||||
selectedOffer.nonEmpty,
|
||||
s"Selected faction ID $offeringFid was not among options ${offers.map(_.originatingFactionId)}"
|
||||
availableOriginatingFactionIds.contains(originatingFactionId),
|
||||
s"Selected faction ID $originatingFactionId was not among options $availableOriginatingFactionIds"
|
||||
)
|
||||
|
||||
commandRequire(
|
||||
selectedOffer.get.eligibleStatuses.contains(selectedCommand.resolution),
|
||||
s"Selected alliance resolution ${selectedCommand.resolution} was not among options ${selectedOffer.get.eligibleStatuses}"
|
||||
availableResolutions.contains(resolution),
|
||||
s"Selected alliance resolution $resolution was not among options $availableResolutions"
|
||||
)
|
||||
|
||||
ResolveAllianceOfferCommand(
|
||||
sc = selectedCommand,
|
||||
allianceOffer = selectedOffer.get,
|
||||
resolution = selectedCommand.resolution,
|
||||
actingFactionId = actingFactionId,
|
||||
originatingFactionId = originatingFactionId,
|
||||
messengerHeroId = messengerHeroId,
|
||||
messengerOriginProvinceId = messengerOriginProvinceId,
|
||||
allianceOffer = allianceOffer,
|
||||
resolution = resolution,
|
||||
gameId = gameId,
|
||||
currentRoundId = currentRoundId
|
||||
)
|
||||
}
|
||||
|
||||
private case class ResolveAllianceOfferCommand(
|
||||
actingFactionId: FactionId,
|
||||
originatingFactionId: FactionId,
|
||||
messengerHeroId: HeroId,
|
||||
messengerOriginProvinceId: ProvinceId,
|
||||
allianceOffer: AllianceOffer,
|
||||
resolution: Status,
|
||||
gameId: GameId,
|
||||
currentRoundId: RoundId
|
||||
) extends ProtolessSimpleAction {
|
||||
override def immediateExecute: ActionResultT = {
|
||||
val llmRequestId =
|
||||
s"${currentRoundId} resolve alliance offer ${messengerHeroId}"
|
||||
|
||||
resolution match {
|
||||
case Accepted => acceptAllianceOffer(llmRequestId)
|
||||
case Rejected => rejectAllianceOffer(llmRequestId)
|
||||
case Imprisoned => imprisonAmbassador(llmRequestId)
|
||||
case _ =>
|
||||
throw new EagleCommandException(
|
||||
s"Invalid diplomacy offer status for command: $resolution"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def acceptAllianceOffer(llmRequestId: String): ActionResultT = {
|
||||
ActionResultC(
|
||||
actionResultType = OfferResolvedResultType,
|
||||
actingFactionId = Some(actingFactionId),
|
||||
changedFactions = Vector(
|
||||
ChangedFactionC(
|
||||
factionId = actingFactionId,
|
||||
removedIncomingDiplomacyOfferFactionIds =
|
||||
Vector(originatingFactionId),
|
||||
newIncomingDiplomacyOffers =
|
||||
Vector(allianceOffer.copy(status = Accepted))
|
||||
)
|
||||
),
|
||||
newNotifications = Vector(createNotification(llmRequestId, Accepted)),
|
||||
newGeneratedTextRequests =
|
||||
Vector(createLlmRequest(llmRequestId, Accepted))
|
||||
)
|
||||
}
|
||||
|
||||
private def rejectAllianceOffer(llmRequestId: String): ActionResultT = {
|
||||
ActionResultC(
|
||||
actionResultType = OfferResolvedResultType,
|
||||
actingFactionId = Some(actingFactionId),
|
||||
changedFactions = Vector(
|
||||
ChangedFactionC(
|
||||
factionId = actingFactionId,
|
||||
removedIncomingDiplomacyOfferFactionIds =
|
||||
Vector(originatingFactionId),
|
||||
newIncomingDiplomacyOffers =
|
||||
Vector(allianceOffer.copy(status = Rejected))
|
||||
)
|
||||
),
|
||||
newNotifications = Vector(createNotification(llmRequestId, Rejected)),
|
||||
newGeneratedTextRequests =
|
||||
Vector(createLlmRequest(llmRequestId, Rejected))
|
||||
)
|
||||
}
|
||||
|
||||
private def imprisonAmbassador(llmRequestId: String): ActionResultT = {
|
||||
ActionResultC(
|
||||
actionResultType = OfferResolvedResultType,
|
||||
actingFactionId = Some(actingFactionId),
|
||||
changedFactions = Vector(
|
||||
ChangedFactionC(
|
||||
factionId = actingFactionId,
|
||||
removedIncomingDiplomacyOfferFactionIds =
|
||||
Vector(originatingFactionId),
|
||||
newIncomingDiplomacyOffers =
|
||||
Vector(allianceOffer.copy(status = Imprisoned))
|
||||
)
|
||||
),
|
||||
newNotifications = Vector(createNotification(llmRequestId, Imprisoned)),
|
||||
newGeneratedTextRequests =
|
||||
Vector(createLlmRequest(llmRequestId, Imprisoned))
|
||||
)
|
||||
}
|
||||
|
||||
private def createNotification(
|
||||
llmRequestId: String,
|
||||
status: Status
|
||||
): NotificationC = {
|
||||
NotificationC(
|
||||
details = status match {
|
||||
case Accepted =>
|
||||
NotificationDetails.AllianceAccepted(
|
||||
offeringFactionId = originatingFactionId,
|
||||
targetFactionId = actingFactionId,
|
||||
ambassadorHeroId = messengerHeroId
|
||||
)
|
||||
|
||||
case Rejected =>
|
||||
NotificationDetails.AllianceRejected(
|
||||
offeringFactionId = originatingFactionId,
|
||||
targetFactionId = actingFactionId,
|
||||
ambassadorHeroId = messengerHeroId
|
||||
)
|
||||
|
||||
case Imprisoned =>
|
||||
NotificationDetails.AllianceAmbassadorImprisoned(
|
||||
offeringFactionId = originatingFactionId,
|
||||
targetFactionId = actingFactionId,
|
||||
ambassadorHeroId = messengerHeroId
|
||||
)
|
||||
|
||||
case _ =>
|
||||
throw new EagleCommandException(s"Unexpected status: $status")
|
||||
},
|
||||
affectedProvinceIds = Vector(),
|
||||
affectedHeroIds = Vector(messengerHeroId),
|
||||
llm = NotificationT.Llm.Id(llmRequestId),
|
||||
deferred = false
|
||||
)
|
||||
}
|
||||
|
||||
private def createLlmRequest(
|
||||
llmRequestId: String,
|
||||
status: Status
|
||||
): AllianceOfferResolutionMessage = {
|
||||
AllianceOfferResolutionMessage(
|
||||
requestId = llmRequestId,
|
||||
eagleGameId = gameId,
|
||||
recipientFactionIds = Vector.empty,
|
||||
alwaysGenerate = false,
|
||||
offeringFactionId = originatingFactionId,
|
||||
targetFactionId = actingFactionId,
|
||||
messengerHeroId = messengerHeroId,
|
||||
resolution = status
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+103
-177
@@ -1,195 +1,121 @@
|
||||
package net.eagle0.eagle.library.actions.impl.command
|
||||
import net.eagle0.eagle.api.available_command.{
|
||||
ResolveTributeAvailableCommand,
|
||||
TributeAndFaction
|
||||
|
||||
import net.eagle0.eagle.library.actions.impl.common.ProtolessSimpleAction
|
||||
import net.eagle0.eagle.library.util.EagleRequire.commandRequire
|
||||
import net.eagle0.eagle.model.action_result.ActionResultT
|
||||
import net.eagle0.eagle.model.action_result.concrete.ActionResultC
|
||||
import net.eagle0.eagle.model.action_result.changed_province.concrete.{
|
||||
ChangedProvinceC,
|
||||
HostileArmyStatusChange
|
||||
}
|
||||
import net.eagle0.eagle.api.selected_command.ResolveTributeSelectedCommand
|
||||
import net.eagle0.eagle.common.action_result_type.ActionResultType
|
||||
import net.eagle0.eagle.common.tribute_amount.TributeAmount
|
||||
import net.eagle0.eagle.internal.faction_relationship.FactionRelationship
|
||||
import net.eagle0.eagle.internal.action_result.ActionResult
|
||||
import net.eagle0.eagle.internal.army.{
|
||||
import net.eagle0.eagle.model.action_result.types.{
|
||||
TributePaidResultType,
|
||||
TributeRefusedResultType
|
||||
}
|
||||
import net.eagle0.eagle.model.state.HostileArmyGroupStatus.{
|
||||
Attacking,
|
||||
HostileArmyGroup,
|
||||
TributeDemanded,
|
||||
TributePaid
|
||||
}
|
||||
import net.eagle0.eagle.internal.changed_faction.ChangedFaction
|
||||
import net.eagle0.eagle.internal.changed_province.ChangedProvince
|
||||
import net.eagle0.eagle.internal.changed_province.ChangedProvince.HostileArmyGroupStatusChange
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.library.util.DateProtoUtils.*
|
||||
import net.eagle0.eagle.library.util.EagleRequire.commandRequire
|
||||
import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils
|
||||
import net.eagle0.eagle.{FactionId, ProvinceId}
|
||||
import net.eagle0.eagle.model.state.TributeAmount
|
||||
import net.eagle0.eagle.{FactionId, GameId, ProvinceId, RoundId}
|
||||
|
||||
object ResolveTributeCommand
|
||||
extends CommandBuilder[
|
||||
ResolveTributeAvailableCommand,
|
||||
ResolveTributeSelectedCommand
|
||||
] {
|
||||
|
||||
private case class PayTributeCommand(
|
||||
sc: ResolveTributeSelectedCommand,
|
||||
gs: GameState,
|
||||
pid: ProvinceId,
|
||||
demandingArmy: HostileArmyGroup,
|
||||
payingFid: FactionId
|
||||
) extends DeterministicSingleResultCommand(gs, sc) {
|
||||
lazy val otherTributeDemandingCps: Vector[ChangedProvince] =
|
||||
gs.provinces.values
|
||||
.filter(_.rulingFactionId.contains(payingFid))
|
||||
.flatMap { province =>
|
||||
province.hostileArmies
|
||||
.find(_.factionId == demandingArmy.factionId)
|
||||
.filter(_.status.asMessage.sealedValue.isTributeDemanded)
|
||||
.map { ha =>
|
||||
ChangedProvince(
|
||||
id = province.id,
|
||||
hostileArmyStatusChanges = Vector(
|
||||
HostileArmyGroupStatusChange(
|
||||
factionId = ha.factionId,
|
||||
newStatus = TributePaid(Some(TributeAmount(0, 0)))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
.toVector
|
||||
|
||||
override def immediateExecute: ActionResult = {
|
||||
val tributeAmount = demandingArmy.status match {
|
||||
case TributeDemanded(tributeAmount, _ /* unknownFieldSet */ ) =>
|
||||
tributeAmount
|
||||
case _ => throw new IllegalStateException("Tribute not paid")
|
||||
}
|
||||
ActionResult(
|
||||
`type` = ActionResultType.TRIBUTE_PAID,
|
||||
player = Some(payingFid),
|
||||
changedProvinces = ChangedProvince(
|
||||
id = pid,
|
||||
goldDelta = Some(-tributeAmount.get.gold),
|
||||
foodDelta = Some(-tributeAmount.get.food),
|
||||
hostileArmyStatusChanges = Vector(
|
||||
HostileArmyGroupStatusChange(
|
||||
factionId = demandingArmy.factionId,
|
||||
newStatus = TributePaid(tributeAmount = tributeAmount)
|
||||
)
|
||||
)
|
||||
) +: otherTributeDemandingCps,
|
||||
changedFactions = Vector(
|
||||
ChangedFaction(
|
||||
id = payingFid,
|
||||
changedFactionRelationships = Vector(
|
||||
LegacyFactionUtils
|
||||
.factionRelationship(payingFid, demandingArmy.factionId, gs)
|
||||
.update(
|
||||
_.relationshipLevel := FactionRelationship.RelationshipLevel.TRUCE,
|
||||
_.resetDate := gs.currentDate.get.addMonths(12)
|
||||
)
|
||||
)
|
||||
),
|
||||
ChangedFaction(
|
||||
id = demandingArmy.factionId,
|
||||
changedFactionRelationships = Vector(
|
||||
LegacyFactionUtils
|
||||
.factionRelationship(demandingArmy.factionId, payingFid, gs)
|
||||
.update(
|
||||
_.relationshipLevel := FactionRelationship.RelationshipLevel.TRUCE,
|
||||
_.resetDate := gs.currentDate.get.addMonths(12)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private case class RejectDemandCommand(
|
||||
sc: ResolveTributeSelectedCommand,
|
||||
gs: GameState,
|
||||
pid: ProvinceId,
|
||||
demandingArmy: HostileArmyGroup,
|
||||
rejectingFid: FactionId
|
||||
) extends DeterministicSingleResultCommand(gs, sc) {
|
||||
override def immediateExecute: ActionResult =
|
||||
ActionResult(
|
||||
`type` = ActionResultType.TRIBUTE_REFUSED,
|
||||
player = Some(rejectingFid),
|
||||
changedProvinces = Vector(
|
||||
ChangedProvince(
|
||||
id = pid,
|
||||
hostileArmyStatusChanges = Vector(
|
||||
HostileArmyGroupStatusChange(
|
||||
factionId = demandingArmy.factionId,
|
||||
newStatus = Attacking()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override def make(
|
||||
object ResolveTributeCommand {
|
||||
def make(
|
||||
actingFactionId: FactionId,
|
||||
gameState: GameState,
|
||||
availableCommand: ResolveTributeAvailableCommand,
|
||||
selectedCommand: ResolveTributeSelectedCommand
|
||||
): Command = {
|
||||
val demandingFid = selectedCommand.demandingFactionId
|
||||
|
||||
val demands = availableCommand.demands
|
||||
val selectedDemand = demands.find(_.demandingFactionId == demandingFid)
|
||||
|
||||
demandingFactionId: FactionId,
|
||||
tributeAmount: TributeAmount,
|
||||
paid: Boolean,
|
||||
availableDemandingFactionIds: Vector[FactionId],
|
||||
actingProvinceId: ProvinceId,
|
||||
availableGold: Int,
|
||||
availableFood: Int,
|
||||
gameId: GameId,
|
||||
currentRoundId: RoundId
|
||||
): ProtolessSimpleAction = {
|
||||
// Validate the demanding faction ID
|
||||
commandRequire(
|
||||
selectedDemand.nonEmpty,
|
||||
s"Selected faction ID $demandingFid was not among options ${demands.map(_.demandingFactionId).toList}"
|
||||
availableDemandingFactionIds.contains(demandingFactionId),
|
||||
s"Selected demanding faction ID $demandingFactionId was not among options $availableDemandingFactionIds"
|
||||
)
|
||||
|
||||
val demandingArmy = gameState
|
||||
.provinces(availableCommand.actingProvinceId)
|
||||
.hostileArmies
|
||||
.filter(_.status.asMessage.sealedValue.isTributeDemanded)
|
||||
.find(_.factionId == demandingFid)
|
||||
.get
|
||||
|
||||
val tributeDemanded = selectedDemand.get match {
|
||||
case TributeAndFaction(
|
||||
_,
|
||||
tributeDemanded,
|
||||
_,
|
||||
_,
|
||||
_ /* unknownFieldSet */
|
||||
) =>
|
||||
tributeDemanded
|
||||
// Validate that we have enough resources if paying
|
||||
if paid then {
|
||||
commandRequire(
|
||||
availableGold >= tributeAmount.gold,
|
||||
s"Not enough gold to pay tribute: have $availableGold, need ${tributeAmount.gold}"
|
||||
)
|
||||
commandRequire(
|
||||
availableFood >= tributeAmount.food,
|
||||
s"Not enough food to pay tribute: have $availableFood, need ${tributeAmount.food}"
|
||||
)
|
||||
}
|
||||
|
||||
if selectedCommand.paid then {
|
||||
commandRequire(
|
||||
availableCommand.availableFood >= tributeDemanded.get.food,
|
||||
s"Trying to pay ${tributeDemanded.get.food} food, but only ${availableCommand.availableFood} available"
|
||||
)
|
||||
commandRequire(
|
||||
availableCommand.availableGold >= tributeDemanded.get.gold,
|
||||
s"Trying to pay ${tributeDemanded.get.gold} gold, but only ${availableCommand.availableGold} available"
|
||||
)
|
||||
ResolveTributeCommand(
|
||||
actingFactionId = actingFactionId,
|
||||
demandingFactionId = demandingFactionId,
|
||||
tributeAmount = tributeAmount,
|
||||
paid = paid,
|
||||
actingProvinceId = actingProvinceId,
|
||||
gameId = gameId,
|
||||
currentRoundId = currentRoundId
|
||||
)
|
||||
}
|
||||
|
||||
PayTributeCommand(
|
||||
sc = selectedCommand,
|
||||
gs = gameState,
|
||||
pid = availableCommand.actingProvinceId,
|
||||
demandingArmy = demandingArmy,
|
||||
payingFid = actingFactionId
|
||||
private case class ResolveTributeCommand(
|
||||
actingFactionId: FactionId,
|
||||
demandingFactionId: FactionId,
|
||||
tributeAmount: TributeAmount,
|
||||
paid: Boolean,
|
||||
actingProvinceId: ProvinceId,
|
||||
gameId: GameId,
|
||||
currentRoundId: RoundId
|
||||
) extends ProtolessSimpleAction {
|
||||
override def immediateExecute: ActionResultT = {
|
||||
if paid then {
|
||||
payTribute()
|
||||
} else {
|
||||
rejectTribute()
|
||||
}
|
||||
}
|
||||
|
||||
private def payTribute(): ActionResultT = {
|
||||
ActionResultC(
|
||||
actionResultType = TributePaidResultType,
|
||||
actingFactionId = Some(actingFactionId),
|
||||
changedProvinces = Vector(
|
||||
ChangedProvinceC(
|
||||
provinceId = actingProvinceId,
|
||||
goldDelta = Some(-tributeAmount.gold),
|
||||
foodDelta = Some(-tributeAmount.food),
|
||||
hostileArmyStatusChanges = Vector(
|
||||
HostileArmyStatusChange(
|
||||
factionId = demandingFactionId,
|
||||
newStatus = TributePaid(tributeAmount)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
// TODO: Add faction relationship changes for truce if needed
|
||||
)
|
||||
} else
|
||||
RejectDemandCommand(
|
||||
sc = selectedCommand,
|
||||
gs = gameState,
|
||||
pid = availableCommand.actingProvinceId,
|
||||
demandingArmy = demandingArmy,
|
||||
rejectingFid = actingFactionId
|
||||
}
|
||||
|
||||
private def rejectTribute(): ActionResultT = {
|
||||
ActionResultC(
|
||||
actionResultType = TributeRefusedResultType,
|
||||
actingFactionId = Some(actingFactionId),
|
||||
changedProvinces = Vector(
|
||||
ChangedProvinceC(
|
||||
provinceId = actingProvinceId,
|
||||
hostileArmyStatusChanges = Vector(
|
||||
HostileArmyStatusChange(
|
||||
factionId = demandingFactionId,
|
||||
newStatus = Attacking
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
end if
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -902,6 +902,29 @@ scala_test(
|
||||
],
|
||||
)
|
||||
|
||||
scala_test(
|
||||
name = "resolve_tribute_command_test",
|
||||
srcs = ["ResolveTributeCommandTest.scala"],
|
||||
deps = [
|
||||
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
|
||||
"//src/main/scala/net/eagle0/eagle/library:eagle_client_exception",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:resolve_tribute_command",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:protoless_simple_action",
|
||||
"//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:changed_hero_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result:notification_trait",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province/concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:action_result_concrete",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:tribute_paid_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/action_result/types:tribute_refused_result_type",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:army",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state:tribute_amount",
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/impl:action_impl_pkg",
|
||||
],
|
||||
)
|
||||
|
||||
scala_test(
|
||||
name = "resolve_truce_offer_command_test",
|
||||
srcs = ["ResolveTruceOfferCommandTest.scala"],
|
||||
@@ -928,25 +951,6 @@ scala_test(
|
||||
],
|
||||
)
|
||||
|
||||
scala_test(
|
||||
name = "resolve_tribute_command_test",
|
||||
srcs = ["ResolveTributeCommandTest.scala"],
|
||||
deps = [
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:available_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/api:selected_command_scala_proto",
|
||||
"//src/main/protobuf/net/eagle0/eagle/common:action_result_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:eagle_client_exception",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_proto_applier_impl",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:deterministic_single_result_command",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/command:resolve_tribute_command",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/impl:action_impl_pkg",
|
||||
"//src/test/scala/net/eagle0/eagle/library/util/validations:testing_noop_validator",
|
||||
],
|
||||
)
|
||||
|
||||
scala_test(
|
||||
name = "rest_command_test",
|
||||
srcs = ["RestCommandTest.scala"],
|
||||
|
||||
+236
-329
@@ -1,377 +1,284 @@
|
||||
package net.eagle0.eagle.library.actions.impl.command
|
||||
|
||||
import net.eagle0.eagle.api.available_command.{
|
||||
ResolveTributeAvailableCommand,
|
||||
TributeAndFaction
|
||||
import net.eagle0.eagle.model.action_result.concrete.ActionResultC
|
||||
import net.eagle0.eagle.model.action_result.changed_province.concrete.{
|
||||
ChangedProvinceC,
|
||||
HostileArmyStatusChange
|
||||
}
|
||||
import net.eagle0.eagle.api.selected_command.ResolveTributeSelectedCommand
|
||||
import net.eagle0.eagle.common.action_result_type.ActionResultType.{
|
||||
TRIBUTE_PAID,
|
||||
TRIBUTE_REFUSED
|
||||
import net.eagle0.eagle.model.action_result.types.{
|
||||
TributePaidResultType,
|
||||
TributeRefusedResultType
|
||||
}
|
||||
import net.eagle0.eagle.common.date.Date
|
||||
import net.eagle0.eagle.internal.faction_relationship.FactionRelationship
|
||||
import net.eagle0.eagle.internal.faction_relationship.FactionRelationship.RelationshipLevel.TRUCE
|
||||
import net.eagle0.eagle.common.tribute_amount.TributeAmount
|
||||
import net.eagle0.eagle.internal.army.{
|
||||
Army,
|
||||
import net.eagle0.eagle.model.state.TributeAmount
|
||||
import net.eagle0.eagle.model.state.HostileArmyGroupStatus.{
|
||||
Attacking,
|
||||
HostileArmyGroup,
|
||||
MovingArmy,
|
||||
TributeDemanded,
|
||||
TributePaid
|
||||
}
|
||||
import net.eagle0.eagle.internal.changed_faction.ChangedFaction
|
||||
import net.eagle0.eagle.internal.changed_province.ChangedProvince
|
||||
import net.eagle0.eagle.internal.changed_province.ChangedProvince.HostileArmyGroupStatusChange
|
||||
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.supplies.Supplies
|
||||
import net.eagle0.eagle.library.EagleCommandException
|
||||
import net.eagle0.eagle.library.actions.applier.ActionResultProtoApplierImpl
|
||||
import net.eagle0.eagle.library.util.validations.TestingNoopValidator
|
||||
import net.eagle0.eagle.{FactionId, GameId, ProvinceId, RoundId}
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.Inside.inside
|
||||
import org.scalatest.LoneElement.convertToCollectionLoneElementWrapper
|
||||
|
||||
class ResolveTributeCommandTest extends AnyFlatSpec with Matchers {
|
||||
val actingFactionId = 3
|
||||
val actingProvinceId = 7
|
||||
val attackingFactionId = 9
|
||||
private val actingFactionId: FactionId = 12
|
||||
private val demandingFactionId: FactionId = 7
|
||||
private val actingProvinceId: ProvinceId = 22
|
||||
private val gameId: GameId = 0xfeed
|
||||
private val currentRoundId: RoundId = 873
|
||||
|
||||
val actionResultProtoApplier = new ActionResultProtoApplierImpl(
|
||||
validator = TestingNoopValidator
|
||||
)
|
||||
private val tributeAmount = TributeAmount(gold = 500, food = 300)
|
||||
private val availableDemandingFactionIds = Vector(demandingFactionId, 15, 20)
|
||||
|
||||
private val demandedGold = 400
|
||||
private val demandedFood = 700
|
||||
|
||||
private val currentDate = Date(year = 499, month = 7)
|
||||
|
||||
private val tributeAmount =
|
||||
TributeAmount(gold = demandedGold, food = demandedFood)
|
||||
|
||||
private val availableCommand = ResolveTributeAvailableCommand(
|
||||
actingProvinceId = actingProvinceId,
|
||||
availableGold = 500,
|
||||
availableFood = 1000,
|
||||
demands = Vector(
|
||||
TributeAndFaction(
|
||||
demandingFactionId = attackingFactionId,
|
||||
tributeDemanded = Some(tributeAmount)
|
||||
)
|
||||
"make" should "successfully create command when paying tribute with sufficient resources" in {
|
||||
val command = ResolveTributeCommand.make(
|
||||
actingFactionId = actingFactionId,
|
||||
demandingFactionId = demandingFactionId,
|
||||
tributeAmount = tributeAmount,
|
||||
paid = true,
|
||||
availableDemandingFactionIds = availableDemandingFactionIds,
|
||||
actingProvinceId = actingProvinceId,
|
||||
availableGold = 1000,
|
||||
availableFood = 500,
|
||||
gameId = gameId,
|
||||
currentRoundId = currentRoundId
|
||||
)
|
||||
)
|
||||
|
||||
private val paidCommand = ResolveTributeSelectedCommand(
|
||||
demandingFactionId = attackingFactionId,
|
||||
paid = true
|
||||
)
|
||||
command should not be null
|
||||
}
|
||||
|
||||
private val refusedCommand = ResolveTributeSelectedCommand(
|
||||
demandingFactionId = attackingFactionId,
|
||||
paid = false
|
||||
)
|
||||
|
||||
val movingArmyId = 67
|
||||
val originatingProvinceId = 99
|
||||
|
||||
private val tributeDemandingArmy = HostileArmyGroup(
|
||||
factionId = attackingFactionId,
|
||||
armies = Vector(
|
||||
MovingArmy(
|
||||
id = movingArmyId,
|
||||
army = Some(
|
||||
Army(
|
||||
factionId = attackingFactionId
|
||||
)
|
||||
),
|
||||
originProvince = originatingProvinceId,
|
||||
supplies = Some(Supplies(food = 2000, gold = 1000))
|
||||
)
|
||||
),
|
||||
status = TributeDemanded(Some(tributeAmount))
|
||||
)
|
||||
|
||||
private val actingProvince = Province(
|
||||
id = actingProvinceId,
|
||||
rulingFactionId = Some(actingFactionId),
|
||||
gold = 500,
|
||||
food = 1000,
|
||||
hostileArmies = Vector(tributeDemandingArmy)
|
||||
)
|
||||
|
||||
private val gameState = GameState(
|
||||
randomSeed = 1L,
|
||||
provinces = Map(
|
||||
actingProvinceId -> actingProvince,
|
||||
originatingProvinceId -> Province(id = originatingProvinceId)
|
||||
),
|
||||
factions = Map(
|
||||
actingFactionId -> Faction(id = actingFactionId),
|
||||
attackingFactionId -> Faction(id = attackingFactionId)
|
||||
),
|
||||
currentDate = Some(currentDate)
|
||||
)
|
||||
|
||||
"responding to a nonexistent demand" should "throw with appropriate message" in {
|
||||
val ac = availableCommand
|
||||
val sc = ResolveTributeSelectedCommand(
|
||||
demandingFactionId = 11,
|
||||
paid = true
|
||||
it should "successfully create command when rejecting tribute" in {
|
||||
val command = ResolveTributeCommand.make(
|
||||
actingFactionId = actingFactionId,
|
||||
demandingFactionId = demandingFactionId,
|
||||
tributeAmount = tributeAmount,
|
||||
paid = false,
|
||||
availableDemandingFactionIds = availableDemandingFactionIds,
|
||||
actingProvinceId = actingProvinceId,
|
||||
availableGold = 0, // Resources don't matter when rejecting
|
||||
availableFood = 0,
|
||||
gameId = gameId,
|
||||
currentRoundId = currentRoundId
|
||||
)
|
||||
|
||||
command should not be null
|
||||
}
|
||||
|
||||
it should "throw when demanding faction ID is not in available list" in {
|
||||
val invalidDemandingFactionId = 999
|
||||
|
||||
val ex = the[EagleCommandException] thrownBy {
|
||||
ResolveTributeCommand.make(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gameState,
|
||||
availableCommand = ac,
|
||||
selectedCommand = sc
|
||||
demandingFactionId = invalidDemandingFactionId,
|
||||
tributeAmount = tributeAmount,
|
||||
paid = true,
|
||||
availableDemandingFactionIds = availableDemandingFactionIds,
|
||||
actingProvinceId = actingProvinceId,
|
||||
availableGold = 1000,
|
||||
availableFood = 500,
|
||||
gameId = gameId,
|
||||
currentRoundId = currentRoundId
|
||||
)
|
||||
}
|
||||
|
||||
ex.getMessage shouldBe "requirement failed: Selected faction ID 11 was not among options List(9)"
|
||||
ex.getMessage should include(
|
||||
s"Selected demanding faction ID $invalidDemandingFactionId was not among options $availableDemandingFactionIds"
|
||||
)
|
||||
}
|
||||
|
||||
"accepting without enough food" should "throw with appropriate message" in {
|
||||
val ac = availableCommand.withAvailableFood(200)
|
||||
val sc = paidCommand
|
||||
|
||||
it should "throw when paying tribute but insufficient gold" in {
|
||||
val ex = the[EagleCommandException] thrownBy {
|
||||
ResolveTributeCommand.make(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gameState,
|
||||
availableCommand = ac,
|
||||
selectedCommand = sc
|
||||
demandingFactionId = demandingFactionId,
|
||||
tributeAmount = tributeAmount,
|
||||
paid = true,
|
||||
availableDemandingFactionIds = availableDemandingFactionIds,
|
||||
actingProvinceId = actingProvinceId,
|
||||
availableGold = 400, // Less than tributeAmount.gold (500)
|
||||
availableFood = 500,
|
||||
gameId = gameId,
|
||||
currentRoundId = currentRoundId
|
||||
)
|
||||
}
|
||||
|
||||
ex.getMessage shouldBe "requirement failed: Trying to pay 700 food, but only 200 available"
|
||||
ex.getMessage should include(
|
||||
s"Not enough gold to pay tribute: have 400, need ${tributeAmount.gold}"
|
||||
)
|
||||
}
|
||||
|
||||
"accepting without enough gold" should "throw with appropriate message" in {
|
||||
val ac = availableCommand.withAvailableGold(123)
|
||||
val sc = paidCommand
|
||||
|
||||
it should "throw when paying tribute but insufficient food" in {
|
||||
val ex = the[EagleCommandException] thrownBy {
|
||||
ResolveTributeCommand.make(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gameState,
|
||||
availableCommand = ac,
|
||||
selectedCommand = sc
|
||||
demandingFactionId = demandingFactionId,
|
||||
tributeAmount = tributeAmount,
|
||||
paid = true,
|
||||
availableDemandingFactionIds = availableDemandingFactionIds,
|
||||
actingProvinceId = actingProvinceId,
|
||||
availableGold = 1000,
|
||||
availableFood = 200, // Less than tributeAmount.food (300)
|
||||
gameId = gameId,
|
||||
currentRoundId = currentRoundId
|
||||
)
|
||||
}
|
||||
|
||||
ex.getMessage shouldBe "requirement failed: Trying to pay 400 gold, but only 123 available"
|
||||
}
|
||||
|
||||
"paying a tribute demand" should "set the basics" in {
|
||||
val results = ResolveTributeCommand
|
||||
.make(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gameState,
|
||||
availableCommand = availableCommand,
|
||||
selectedCommand = paidCommand
|
||||
)
|
||||
.execute(actionResultProtoApplier)
|
||||
|
||||
results should have size 1
|
||||
val result = results.head.actionResult
|
||||
result.`type` shouldBe TRIBUTE_PAID
|
||||
result.player shouldBe Some(actingFactionId)
|
||||
}
|
||||
|
||||
it should "set the hostile army to status TributePaid" in {
|
||||
val result = ResolveTributeCommand
|
||||
.make(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gameState,
|
||||
availableCommand = availableCommand,
|
||||
selectedCommand = paidCommand
|
||||
)
|
||||
.execute(actionResultProtoApplier)
|
||||
.head
|
||||
.actionResult
|
||||
|
||||
result.changedProvinces.exists { cp =>
|
||||
cp.id == actingProvinceId && cp.hostileArmyStatusChanges
|
||||
.contains(
|
||||
HostileArmyGroupStatusChange(
|
||||
factionId = attackingFactionId,
|
||||
newStatus = TributePaid(tributeAmount = Some(tributeAmount))
|
||||
)
|
||||
)
|
||||
} shouldBe true
|
||||
}
|
||||
|
||||
it should "set another tribute demanding army for same attacker & defender to TributePaid" in {
|
||||
val gameStateWithAnotherTributeDemandingArmy = gameState.update(
|
||||
_.provinces :+= (91 -> Province(
|
||||
id = 91,
|
||||
rulingFactionId = Some(actingFactionId),
|
||||
hostileArmies = Vector(
|
||||
HostileArmyGroup(
|
||||
factionId = attackingFactionId,
|
||||
armies = Vector(),
|
||||
status = TributeDemanded(Some(TributeAmount(food = 10, gold = 100)))
|
||||
)
|
||||
)
|
||||
))
|
||||
)
|
||||
|
||||
val result = ResolveTributeCommand
|
||||
.make(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gameStateWithAnotherTributeDemandingArmy,
|
||||
availableCommand = availableCommand,
|
||||
selectedCommand = paidCommand
|
||||
)
|
||||
.execute(actionResultProtoApplier)
|
||||
.head
|
||||
.actionResult
|
||||
|
||||
result.changedProvinces.exists { cp =>
|
||||
cp.id == 91 && cp.hostileArmyStatusChanges
|
||||
.contains(
|
||||
HostileArmyGroupStatusChange(
|
||||
factionId = attackingFactionId,
|
||||
newStatus = TributePaid(tributeAmount = Some(TributeAmount()))
|
||||
)
|
||||
)
|
||||
} shouldBe true
|
||||
}
|
||||
|
||||
it should "does not change a tribute demanding army from another faction" in {
|
||||
val gameStateWithAnotherTributeDemandingArmy = gameState.update(
|
||||
_.provinces :+= (91 -> Province(
|
||||
id = 91,
|
||||
rulingFactionId = Some(actingFactionId),
|
||||
hostileArmies = Vector(
|
||||
HostileArmyGroup(
|
||||
factionId = 111,
|
||||
armies = Vector(),
|
||||
status = TributeDemanded(Some(TributeAmount(food = 10, gold = 100)))
|
||||
)
|
||||
)
|
||||
)),
|
||||
_.factions :+= (111 -> Faction(id = 111))
|
||||
)
|
||||
|
||||
val result = ResolveTributeCommand
|
||||
.make(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gameStateWithAnotherTributeDemandingArmy,
|
||||
availableCommand = availableCommand,
|
||||
selectedCommand = paidCommand
|
||||
)
|
||||
.execute(actionResultProtoApplier)
|
||||
.head
|
||||
.actionResult
|
||||
|
||||
result.changedProvinces.exists(_.id == 91) shouldBe false
|
||||
}
|
||||
|
||||
it should "remove resources from the defender" in {
|
||||
val result = ResolveTributeCommand
|
||||
.make(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gameState,
|
||||
availableCommand = availableCommand,
|
||||
selectedCommand = paidCommand
|
||||
)
|
||||
.execute(actionResultProtoApplier)
|
||||
.head
|
||||
.actionResult
|
||||
|
||||
result.changedProvinces.exists { cp =>
|
||||
cp.id == actingProvinceId && cp.goldDelta.contains(
|
||||
-demandedGold
|
||||
) && cp.foodDelta
|
||||
.contains(-demandedFood)
|
||||
} shouldBe true
|
||||
}
|
||||
|
||||
it should "create a new truce" in {
|
||||
val result = ResolveTributeCommand
|
||||
.make(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gameState,
|
||||
availableCommand = availableCommand,
|
||||
selectedCommand = paidCommand
|
||||
)
|
||||
.execute(actionResultProtoApplier)
|
||||
.head
|
||||
.actionResult
|
||||
|
||||
result.changedFactions should have size 2
|
||||
result.changedFactions should contain(
|
||||
ChangedFaction(
|
||||
id = actingFactionId,
|
||||
changedFactionRelationships = Vector(
|
||||
FactionRelationship(
|
||||
targetFactionId = attackingFactionId,
|
||||
relationshipLevel = TRUCE,
|
||||
resetDate = Some(Date(year = 500, month = 7))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
result.changedFactions should contain(
|
||||
ChangedFaction(
|
||||
id = attackingFactionId,
|
||||
changedFactionRelationships = Vector(
|
||||
FactionRelationship(
|
||||
targetFactionId = actingFactionId,
|
||||
relationshipLevel = TRUCE,
|
||||
resetDate = Some(Date(year = 500, month = 7))
|
||||
)
|
||||
)
|
||||
)
|
||||
ex.getMessage should include(
|
||||
s"Not enough food to pay tribute: have 200, need ${tributeAmount.food}"
|
||||
)
|
||||
}
|
||||
|
||||
it should "create the appropriate resolution message" in {}
|
||||
|
||||
"refusing a tribute demand" should "set the basics" in {
|
||||
val results = ResolveTributeCommand
|
||||
.make(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gameState,
|
||||
availableCommand = availableCommand,
|
||||
selectedCommand = refusedCommand
|
||||
)
|
||||
.execute(actionResultProtoApplier)
|
||||
|
||||
results should have size 1
|
||||
val result = results.head.actionResult
|
||||
result.`type` shouldBe TRIBUTE_REFUSED
|
||||
result.player shouldBe Some(actingFactionId)
|
||||
}
|
||||
|
||||
it should "set the army to Attacking()" in {
|
||||
val result = ResolveTributeCommand
|
||||
.make(
|
||||
actingFactionId = actingFactionId,
|
||||
gameState = gameState,
|
||||
availableCommand = availableCommand,
|
||||
selectedCommand = refusedCommand
|
||||
)
|
||||
.execute(actionResultProtoApplier)
|
||||
.head
|
||||
.actionResult
|
||||
|
||||
result.changedProvinces should contain(
|
||||
ChangedProvince(
|
||||
id = actingProvinceId,
|
||||
hostileArmyStatusChanges = Vector(
|
||||
HostileArmyGroupStatusChange(
|
||||
factionId = tributeDemandingArmy.factionId,
|
||||
newStatus = Attacking()
|
||||
)
|
||||
)
|
||||
)
|
||||
"execute with paid = true" should "return TributePaidResultType" in {
|
||||
val command = ResolveTributeCommand.make(
|
||||
actingFactionId = actingFactionId,
|
||||
demandingFactionId = demandingFactionId,
|
||||
tributeAmount = tributeAmount,
|
||||
paid = true,
|
||||
availableDemandingFactionIds = availableDemandingFactionIds,
|
||||
actingProvinceId = actingProvinceId,
|
||||
availableGold = 1000,
|
||||
availableFood = 500,
|
||||
gameId = gameId,
|
||||
currentRoundId = currentRoundId
|
||||
)
|
||||
|
||||
val result = command.immediateExecute
|
||||
|
||||
inside(result) { case ar: ActionResultC =>
|
||||
ar.actionResultType shouldBe TributePaidResultType
|
||||
ar.actingFactionId shouldBe Some(actingFactionId)
|
||||
}
|
||||
}
|
||||
|
||||
it should "create the appropriate resolution message" in {}
|
||||
it should "deduct gold and food from the acting province" in {
|
||||
val command = ResolveTributeCommand.make(
|
||||
actingFactionId = actingFactionId,
|
||||
demandingFactionId = demandingFactionId,
|
||||
tributeAmount = tributeAmount,
|
||||
paid = true,
|
||||
availableDemandingFactionIds = availableDemandingFactionIds,
|
||||
actingProvinceId = actingProvinceId,
|
||||
availableGold = 1000,
|
||||
availableFood = 500,
|
||||
gameId = gameId,
|
||||
currentRoundId = currentRoundId
|
||||
)
|
||||
|
||||
val result = command.immediateExecute
|
||||
|
||||
inside(result) { case ar: ActionResultC =>
|
||||
inside(ar.changedProvinces.loneElement) {
|
||||
case province: ChangedProvinceC =>
|
||||
province.provinceId shouldBe actingProvinceId
|
||||
province.goldDelta shouldBe Some(-tributeAmount.gold)
|
||||
province.foodDelta shouldBe Some(-tributeAmount.food)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"execute with paid = false" should "return TributeRefusedResultType" in {
|
||||
val command = ResolveTributeCommand.make(
|
||||
actingFactionId = actingFactionId,
|
||||
demandingFactionId = demandingFactionId,
|
||||
tributeAmount = tributeAmount,
|
||||
paid = false,
|
||||
availableDemandingFactionIds = availableDemandingFactionIds,
|
||||
actingProvinceId = actingProvinceId,
|
||||
availableGold = 0, // Resources don't matter when rejecting
|
||||
availableFood = 0,
|
||||
gameId = gameId,
|
||||
currentRoundId = currentRoundId
|
||||
)
|
||||
|
||||
val result = command.immediateExecute
|
||||
|
||||
inside(result) { case ar: ActionResultC =>
|
||||
ar.actionResultType shouldBe TributeRefusedResultType
|
||||
ar.actingFactionId shouldBe Some(actingFactionId)
|
||||
}
|
||||
}
|
||||
|
||||
it should "not change gold or food in the acting province" in {
|
||||
val command = ResolveTributeCommand.make(
|
||||
actingFactionId = actingFactionId,
|
||||
demandingFactionId = demandingFactionId,
|
||||
tributeAmount = tributeAmount,
|
||||
paid = false,
|
||||
availableDemandingFactionIds = availableDemandingFactionIds,
|
||||
actingProvinceId = actingProvinceId,
|
||||
availableGold = 0,
|
||||
availableFood = 0,
|
||||
gameId = gameId,
|
||||
currentRoundId = currentRoundId
|
||||
)
|
||||
|
||||
val result = command.immediateExecute
|
||||
|
||||
inside(result) { case ar: ActionResultC =>
|
||||
inside(ar.changedProvinces.loneElement) {
|
||||
case province: ChangedProvinceC =>
|
||||
province.provinceId shouldBe actingProvinceId
|
||||
province.goldDelta shouldBe None
|
||||
province.foodDelta shouldBe None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "set hostile army status to TributePaid for demanding faction" in {
|
||||
val command = ResolveTributeCommand.make(
|
||||
actingFactionId = actingFactionId,
|
||||
demandingFactionId = demandingFactionId,
|
||||
tributeAmount = tributeAmount,
|
||||
paid = true,
|
||||
availableDemandingFactionIds = availableDemandingFactionIds,
|
||||
actingProvinceId = actingProvinceId,
|
||||
availableGold = 1000,
|
||||
availableFood = 500,
|
||||
gameId = gameId,
|
||||
currentRoundId = currentRoundId
|
||||
)
|
||||
|
||||
val result = command.immediateExecute
|
||||
|
||||
inside(result) { case ar: ActionResultC =>
|
||||
inside(ar.changedProvinces.loneElement) {
|
||||
case province: ChangedProvinceC =>
|
||||
inside(province.hostileArmyStatusChanges.loneElement) {
|
||||
case statusChange: HostileArmyStatusChange =>
|
||||
statusChange.factionId shouldBe demandingFactionId
|
||||
statusChange.newStatus shouldBe TributePaid(tributeAmount)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"execute with paid = false" should "set hostile army status to Attacking for demanding faction" in {
|
||||
val command = ResolveTributeCommand.make(
|
||||
actingFactionId = actingFactionId,
|
||||
demandingFactionId = demandingFactionId,
|
||||
tributeAmount = tributeAmount,
|
||||
paid = false,
|
||||
availableDemandingFactionIds = availableDemandingFactionIds,
|
||||
actingProvinceId = actingProvinceId,
|
||||
availableGold = 0,
|
||||
availableFood = 0,
|
||||
gameId = gameId,
|
||||
currentRoundId = currentRoundId
|
||||
)
|
||||
|
||||
val result = command.immediateExecute
|
||||
|
||||
inside(result) { case ar: ActionResultC =>
|
||||
inside(ar.changedProvinces.loneElement) {
|
||||
case province: ChangedProvinceC =>
|
||||
inside(province.hostileArmyStatusChanges.loneElement) {
|
||||
case statusChange: HostileArmyStatusChange =>
|
||||
statusChange.factionId shouldBe demandingFactionId
|
||||
statusChange.newStatus shouldBe Attacking
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user