mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 22:35:42 +00:00
Add text client march warnings (#8423)
This commit is contained in:
@@ -11,6 +11,7 @@ import scala.util.{Failure, Success, Try}
|
||||
import io.grpc.{ManagedChannel, ManagedChannelBuilder, Metadata}
|
||||
import io.grpc.stub.{MetadataUtils, StreamObserver}
|
||||
import net.eagle0.eagle.api.available_command.AvailableCommand as AvailableCommandProto
|
||||
import net.eagle0.eagle.api.available_command.MarchAvailableCommand
|
||||
import net.eagle0.eagle.api.command.AvailableCommands
|
||||
import net.eagle0.eagle.api.eagle.*
|
||||
import net.eagle0.eagle.api.eagle.EagleGrpc.EagleStub
|
||||
@@ -319,18 +320,21 @@ final class EagleTextClient(config: Config) {
|
||||
)
|
||||
|
||||
private def postJson(rest: String): Unit = {
|
||||
val firstSplit = rest.indexOf(' ')
|
||||
if firstSplit < 0 then println("usage: post-json <province-id> <SelectedCommandType> <json>")
|
||||
val (force, commandText) =
|
||||
if rest.startsWith("--force ") then (true, rest.stripPrefix("--force ").trim)
|
||||
else (false, rest)
|
||||
val firstSplit = commandText.indexOf(' ')
|
||||
if firstSplit < 0 then println("usage: post-json [--force] <province-id> <SelectedCommandType> <json>")
|
||||
else {
|
||||
val provinceId = rest.take(firstSplit).toInt
|
||||
val afterId = rest.drop(firstSplit + 1).trim
|
||||
val provinceId = commandText.take(firstSplit).toInt
|
||||
val afterId = commandText.drop(firstSplit + 1).trim
|
||||
val typeSplit = afterId.indexOf(' ')
|
||||
if typeSplit < 0 then println("usage: post-json <province-id> <SelectedCommandType> <json>")
|
||||
if typeSplit < 0 then println("usage: post-json [--force] <province-id> <SelectedCommandType> <json>")
|
||||
else {
|
||||
val commandType = afterId.take(typeSplit)
|
||||
val json = afterId.drop(typeSplit + 1).trim
|
||||
parseSelected(commandType, json) match {
|
||||
case Success(selected) => postSelected(provinceId, selected)
|
||||
case Success(selected) => postSelected(provinceId, selected, force)
|
||||
case Failure(error) => println(s"Could not parse $commandType JSON: ${error.getMessage}")
|
||||
}
|
||||
}
|
||||
@@ -338,25 +342,33 @@ final class EagleTextClient(config: Config) {
|
||||
end if
|
||||
}
|
||||
|
||||
private def postSelected(provinceId: Int, selected: SelectedCommandProto): Unit =
|
||||
private def postSelected(provinceId: Int, selected: SelectedCommandProto, force: Boolean = false): Unit =
|
||||
state.currentGameId match {
|
||||
case Some(gameId) =>
|
||||
state.commandToken match {
|
||||
case Some(token) =>
|
||||
send(
|
||||
UpdateStreamRequest(
|
||||
UpdateStreamRequest.RequestDetails.PostCommandRequest(
|
||||
PostCommandRequest(
|
||||
gameId = gameId,
|
||||
command = EagleCommand(
|
||||
provinceId = provinceId,
|
||||
command = selected,
|
||||
eagleToken = token
|
||||
val warnings = state.marchWarnings(provinceId, selected)
|
||||
if warnings.nonEmpty && !force then {
|
||||
println("March warning:")
|
||||
warnings.foreach(warning => println(s"- $warning"))
|
||||
println("Repeat with post-json --force to submit this march.")
|
||||
} else {
|
||||
warnings.foreach(warning => println(s"March warning (forced): $warning"))
|
||||
send(
|
||||
UpdateStreamRequest(
|
||||
UpdateStreamRequest.RequestDetails.PostCommandRequest(
|
||||
PostCommandRequest(
|
||||
gameId = gameId,
|
||||
command = EagleCommand(
|
||||
provinceId = provinceId,
|
||||
command = selected,
|
||||
eagleToken = token
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
case None => println("No command token yet. Stream a game and wait for available commands.")
|
||||
}
|
||||
case None => println("No current game. Use stream <game-id> first.")
|
||||
@@ -764,6 +776,97 @@ final class ClientState {
|
||||
|
||||
def commandToken: Option[Long] = synchronized(latestCommands.map(_.token))
|
||||
|
||||
def marchWarnings(provinceId: Int, command: SelectedCommandProto): Vector[String] = synchronized {
|
||||
command match {
|
||||
case march: sc.MarchSelectedCommand =>
|
||||
val state = latestState
|
||||
val origin = state.flatMap(_.provinces.get(march.originProvince))
|
||||
val originName = origin.map(_.name).filter(_.nonEmpty).getOrElse(s"province ${march.originProvince}")
|
||||
val destination = state.flatMap(_.provinces.get(march.destinationProvinceId))
|
||||
val destinationName =
|
||||
destination.map(_.name).filter(_.nonEmpty).getOrElse(s"province ${march.destinationProvinceId}")
|
||||
val selectedHeroIds = march.marchingUnits.map(_.heroId).toSet
|
||||
val originHeroIds = origin.flatMap(_.fullInfo).map(_.rulingFactionHeroIds.toSet).getOrElse(Set.empty)
|
||||
|
||||
val abandonWarning =
|
||||
Option.when(originHeroIds.nonEmpty && originHeroIds.subsetOf(selectedHeroIds))(
|
||||
s"This march would abandon $originName."
|
||||
)
|
||||
|
||||
val marchAvailable = availableMarchCommand(provinceId)
|
||||
val originCommand =
|
||||
marchAvailable.flatMap(_.oneProvinceCommands.find(_.originProvinceId == march.originProvince))
|
||||
val selectedBattalionIds = march.marchingUnits.flatMap(_.battalionId).toSet
|
||||
val monthlyFood = originCommand.toVector
|
||||
.flatMap(_.availableBattalions)
|
||||
.filter(battalion => selectedBattalionIds.contains(battalion.battalionId))
|
||||
.map(_.monthlyFood)
|
||||
.sum
|
||||
|
||||
val foodWarning =
|
||||
Option.when(monthlyFood > 0 && march.food < 2 * monthlyFood)(
|
||||
f"The selected food covers less than two months of these battalions ($monthlyFood%.0f monthly)."
|
||||
)
|
||||
|
||||
val restrictiveHeroIds = for {
|
||||
oneProvince <- originCommand.toVector
|
||||
unit <- march.marchingUnits
|
||||
battalionId <- unit.battalionId.toVector
|
||||
suitability <- oneProvince.suitableBattalionsForHeroes.get(unit.heroId).toVector
|
||||
battalionSuitability <- suitability.battalionIdsWithSuitability.find(_.battalionId == battalionId)
|
||||
if battalionSuitability.level.toString == "RESTRICTIVE"
|
||||
} yield unit.heroId
|
||||
val restrictiveUnitsWarning =
|
||||
Option.when(restrictiveHeroIds.nonEmpty)(
|
||||
"One or more heroes are leading a battalion type that would restrict their abilities."
|
||||
)
|
||||
|
||||
val originFactionId = origin.flatMap(_.rulingFactionId)
|
||||
val destinationFactionId = destination.flatMap(_.rulingFactionId)
|
||||
val truceWarning = for {
|
||||
gameState <- state
|
||||
factionId <- originFactionId
|
||||
targetFactionId <- destinationFactionId
|
||||
if factionId != targetFactionId
|
||||
relationship <- gameState.factions
|
||||
.get(factionId)
|
||||
.toVector
|
||||
.flatMap(_.factionRelationships)
|
||||
.find(_.targetFactionId == targetFactionId)
|
||||
if relationship.relationshipLevel.toString != "HOSTILE"
|
||||
} yield s"$destinationName belongs to a faction with which you have a treaty."
|
||||
|
||||
val noBattalionsWarning =
|
||||
Option.when(
|
||||
selectedBattalionIds.isEmpty && destinationFactionId.exists(_ != originFactionId.getOrElse(-1))
|
||||
)(
|
||||
s"$destinationName is hostile and no battalion was selected."
|
||||
)
|
||||
|
||||
val riverWarning = for {
|
||||
oneProvince <- originCommand
|
||||
destinationInfo <- oneProvince.availableDestinationProvinces.find(_.provinceId == march.destinationProvinceId)
|
||||
if destinationInfo.requiresRiverCrossing
|
||||
if destinationFactionId != originFactionId
|
||||
} yield s"Assaulting $destinationName may require crossing a river."
|
||||
|
||||
Vector(
|
||||
abandonWarning,
|
||||
foodWarning,
|
||||
restrictiveUnitsWarning,
|
||||
truceWarning,
|
||||
noBattalionsWarning,
|
||||
riverWarning
|
||||
).flatten
|
||||
case _ => Vector.empty
|
||||
}
|
||||
}
|
||||
|
||||
private def availableMarchCommand(provinceId: Int): Option[MarchAvailableCommand] =
|
||||
latestCommands
|
||||
.flatMap(_.commandsByProvince.get(provinceId))
|
||||
.flatMap(_.commands.collectFirst { case command: MarchAvailableCommand => command })
|
||||
|
||||
def shardokCommandPost(shardokGameId: Option[String], index: Int): Option[ShardokCommandPost] = synchronized {
|
||||
val candidates = shardokGameId match {
|
||||
case Some(id) => latestShardokBattles.get(id).toVector
|
||||
@@ -1027,7 +1130,7 @@ object Help {
|
||||
| shardok-commands-json
|
||||
| shardok-units
|
||||
| shardok-units-json
|
||||
| post-json <province-id> <SelectedCommandType> <JSON>
|
||||
| post-json [--force] <province-id> <SelectedCommandType> <JSON>
|
||||
| post-shardok <index> [roll]
|
||||
| post-shardok <shardok-game-id> <index> [roll]
|
||||
| post-rest <province-id>
|
||||
|
||||
Reference in New Issue
Block a user