more scalafmt (#4444)

* more scalafmt

* more scalafmt improvements
This commit is contained in:
2025-09-24 08:13:22 -07:00
committed by GitHub
parent 06ba7c2680
commit 39740f4211
583 changed files with 13384 additions and 17183 deletions
+30 -1
View File
@@ -1,12 +1,41 @@
version = "3.9.9" version = "3.9.9"
runner.dialect = scala3 runner.dialect = scala3
rewrite.scala3.convertToNewSyntax = true rewrite.scala3.convertToNewSyntax = true
# Keep braces, don't use significant indentation
# rewrite.scala3.removeOptionalBraces = yes # rewrite.scala3.removeOptionalBraces = yes
rewrite.scala3.insertEndMarkerMinLines = 15 rewrite.scala3.insertEndMarkerMinLines = 15
rewrite.scala3.removeEndMarkerMaxLines = 14 rewrite.scala3.removeEndMarkerMaxLines = 14
# Strip margin settings
assumeStandardLibraryStripMargin = false
align.stripMargin = true
# Code Style & Formatting
align.preset = more
align.multiline = true
align.arrowEnumeratorGenerator = true
spaces.inImportCurlyBraces = false
spaces.beforeContextBoundColon = Never
maxColumn = 120
docstrings.style = Asterisk
docstrings.wrap = yes
# Method chaining
newlines.beforeCurlyLambdaParams = multilineWithCaseOnly
optIn.breakChainOnFirstMethodDot = true
includeCurlyBraceInSelectChains = false
# Advanced Scala 3 Features
rewrite.scala3.countEndMarkerLines = all
rewrite.redundantBraces.stringInterpolation = true
rewrite.redundantBraces.parensForOneLineApply = true
# Project-Specific Considerations
optIn.annotationNewlines = true
runner.optimizer.forceConfigStyleMinArgCount = 3
# Import sorting configuration # Import sorting configuration
rewrite.rules = [SortImports] rewrite.rules = [SortImports, RedundantBraces, RedundantParens]
rewrite.imports.sort = scalastyle rewrite.imports.sort = scalastyle
rewrite.imports.groups = [ rewrite.imports.groups = [
["java\\..*"], ["java\\..*"],
@@ -7,7 +7,7 @@ case class RandomState[+T](
newValue: T, newValue: T,
nextRandom: FunctionalRandom nextRandom: FunctionalRandom
) { ) {
def map[U](f: T => U): RandomState[U] = RandomState(f(newValue), nextRandom) def map[U](f: T => U): RandomState[U] = RandomState(f(newValue), nextRandom)
def continue[U](f: (T, FunctionalRandom) => RandomState[U]): RandomState[U] = def continue[U](f: (T, FunctionalRandom) => RandomState[U]): RandomState[U] =
f(newValue, nextRandom) f(newValue, nextRandom)
@@ -62,10 +62,9 @@ abstract class FunctionalRandom {
else ni.nextRandom.nextInt(atLeast = atLeast, lessThan = lessThan) else ni.nextRandom.nextInt(atLeast = atLeast, lessThan = lessThan)
} else { } else {
val diff = lessThan - atLeast val diff = lessThan - atLeast
val max = (Int.MaxValue / diff) * diff val max = (Int.MaxValue / diff) * diff
val np = nextPositiveInt val np = nextPositiveInt
if np.newValue > max then if np.newValue > max then np.nextRandom.nextInt(atLeast = atLeast, lessThan = lessThan)
np.nextRandom.nextInt(atLeast = atLeast, lessThan = lessThan)
else RandomState(np.newValue % diff + atLeast, np.nextRandom) else RandomState(np.newValue % diff + atLeast, np.nextRandom)
} }
} }
@@ -106,7 +105,7 @@ abstract class FunctionalRandom {
nextItems[Double](count, _.nextDouble) nextItems[Double](count, _.nextDouble)
def nextPositiveInt: RandomState[Int] = { def nextPositiveInt: RandomState[Int] = {
val ni = nextInt val ni = nextInt
val nonNegative = val nonNegative =
if ni.newValue < 0 then -(ni.newValue + 1) if ni.newValue < 0 then -(ni.newValue + 1)
else ni.newValue else ni.newValue
@@ -127,12 +126,11 @@ abstract class FunctionalRandom {
shuffledHead: Vector[T], shuffledHead: Vector[T],
unshuffledTail: Vector[T], unshuffledTail: Vector[T],
fr: FunctionalRandom fr: FunctionalRandom
): RandomState[Vector[T]] = { ): RandomState[Vector[T]] =
if unshuffledTail.isEmpty then RandomState(shuffledHead, fr) if unshuffledTail.isEmpty then RandomState(shuffledHead, fr)
else if unshuffledTail.length == 1 then else if unshuffledTail.length == 1 then RandomState(shuffledHead ++ unshuffledTail, fr)
RandomState(shuffledHead ++ unshuffledTail, fr)
else { else {
val ni = fr.nextInt(0, unshuffledTail.length) val ni = fr.nextInt(0, unshuffledTail.length)
val index = ni.newValue val index = ni.newValue
if index == 0 then { if index == 0 then {
@@ -143,20 +141,19 @@ abstract class FunctionalRandom {
) )
} else { } else {
val nextHead = shuffledHead :+ unshuffledTail(index) val nextHead = shuffledHead :+ unshuffledTail(index)
val newTail = unshuffledTail.splitAt(index) match { val newTail = unshuffledTail.splitAt(index) match {
case (l, r) => case (l, r) =>
(l.drop(1) :+ unshuffledTail.head) ++ r.drop(1) (l.drop(1) :+ unshuffledTail.head) ++ r.drop(1)
} }
go(nextHead, newTail, ni.nextRandom) go(nextHead, newTail, ni.nextRandom)
} }
} }
}
go(Vector(), seq, this) go(Vector(), seq, this)
} }
def nextRandomSubset[T](size: Int, seq: Vector[T]): RandomState[Vector[T]] = def nextRandomSubset[T](size: Int, seq: Vector[T]): RandomState[Vector[T]] =
nextShuffled(seq).map { _.take(size) } nextShuffled(seq).map(_.take(size))
private def nextFlatMapImpl[T, U, S[X] <: Seq[X], FACT <: SeqFactory[S]]( private def nextFlatMapImpl[T, U, S[X] <: Seq[X], FACT <: SeqFactory[S]](
seq: S[T] seq: S[T]
@@ -164,8 +161,9 @@ abstract class FunctionalRandom {
f: (T, FunctionalRandom) => RandomState[S[U]] f: (T, FunctionalRandom) => RandomState[S[U]]
)(using xFactory: FACT): RandomState[S[U]] = { )(using xFactory: FACT): RandomState[S[U]] = {
@nowarn def foldGen(rsU: RandomState[S[U]], t: T): RandomState[S[U]] = @nowarn def foldGen(rsU: RandomState[S[U]], t: T): RandomState[S[U]] =
rsU.continue { case (us: S[U], fr: FunctionalRandom) => rsU.continue {
f(t, fr).map(x => (us ++ x).asInstanceOf[S[U]]) case (us: S[U], fr: FunctionalRandom) =>
f(t, fr).map(x => (us ++ x).asInstanceOf[S[U]])
} }
FunctionalRandom.nextFoldLeft( FunctionalRandom.nextFoldLeft(
seq, seq,
@@ -189,8 +187,9 @@ abstract class FunctionalRandom {
rsU: RandomState[S[U]], rsU: RandomState[S[U]],
t: T t: T
): RandomState[S[U]] = ): RandomState[S[U]] =
rsU.continue { case (us: S[U], fr: FunctionalRandom) => rsU.continue {
f(t, fr).map(x => (us :+ x).asInstanceOf[S[U]]) case (us: S[U], fr: FunctionalRandom) =>
f(t, fr).map(x => (us :+ x).asInstanceOf[S[U]])
} }
FunctionalRandom.nextFoldLeft(seq, RandomState(xFactory.empty[U], this))( FunctionalRandom.nextFoldLeft(seq, RandomState(xFactory.empty[U], this))(
@@ -213,11 +212,10 @@ abstract class FunctionalRandom {
): RandomState[Vector[U]] = remVec match { ): RandomState[Vector[U]] = remVec match {
case head +: tail => case head +: tail =>
val newAccRS = val newAccRS =
if pf.isDefinedAt(head) then if pf.isDefinedAt(head) then pf(head)(accRS.nextRandom).map(accRS.newValue :+ _)
pf(head)(accRS.nextRandom).map(accRS.newValue :+ _)
else accRS else accRS
go(tail, newAccRS) go(tail, newAccRS)
case _ => accRS case _ => accRS
} }
go(vec, RandomState(Vector(), this)) go(vec, RandomState(Vector(), this))
@@ -232,7 +230,7 @@ abstract class FunctionalRandom {
case head +: tail => case head +: tail =>
if pf.isDefinedAt(head) then pf(head)(this).map(Option(_)) if pf.isDefinedAt(head) then pf(head)(this).map(Option(_))
else go(tail) else go(tail)
case _ => ??? // above cases should cover case _ => ??? // above cases should cover
} }
go(seq) go(seq)
@@ -249,11 +247,10 @@ abstract class FunctionalRandom {
case items if items.isEmpty => accRS case items if items.isEmpty => accRS
case head +: tail => case head +: tail =>
val newAccRS = val newAccRS =
if pf.isDefinedAt(head) then if pf.isDefinedAt(head) then pf(head)(accRS.nextRandom).map(accRS.newValue ++ _)
pf(head)(accRS.nextRandom).map(accRS.newValue ++ _)
else accRS else accRS
go(tail, newAccRS) go(tail, newAccRS)
case _ => ??? // above cases should cover case _ => ??? // above cases should cover
} }
go(vec, RandomState(Vector(), this)) go(vec, RandomState(Vector(), this))
@@ -281,13 +278,13 @@ abstract class FunctionalRandom {
case class SeededRandom(override val seed: Long) extends FunctionalRandom { case class SeededRandom(override val seed: Long) extends FunctionalRandom {
private val magicMultiplier = 0x5deece66dL private val magicMultiplier = 0x5deece66dL
private val magicAdder = 0xbL private val magicAdder = 0xbL
private val mask = 0xffffffffffffffffL private val mask = 0xffffffffffffffffL
override def nextInt: RandomState[Int] = { override def nextInt: RandomState[Int] = {
// borrowed from Functional Programming in Scala // borrowed from Functional Programming in Scala
val newSeed = (seed * magicMultiplier + magicAdder) & mask val newSeed = (seed * magicMultiplier + magicAdder) & mask
val next = SeededRandom(newSeed) val next = SeededRandom(newSeed)
val newValue = (newSeed >>> 16).toInt val newValue = (newSeed >>> 16).toInt
RandomState(newValue, next) RandomState(newValue, next)
} }
@@ -12,35 +12,31 @@ final case class GrpcRetrier[Stub <: AbstractStub[Stub]](grpcClient: Stub) {
ExecutionContext.fromExecutor(Executors.newFixedThreadPool(4)) ExecutionContext.fromExecutor(Executors.newFixedThreadPool(4))
private val firstDelayMillis = TimeUnit.SECONDS.toMillis(1) private val firstDelayMillis = TimeUnit.SECONDS.toMillis(1)
private val maxDelayMillis = TimeUnit.SECONDS.toMillis(10) private val maxDelayMillis = TimeUnit.SECONDS.toMillis(10)
private val timeoutSeconds = 1200 private val timeoutSeconds = 1200
private def go[Response]( private def go[Response](
clientOp: Stub => Future[Response], clientOp: Stub => Future[Response],
delayMillis: Long delayMillis: Long
): Future[Response] = { ): Future[Response] =
clientOp(grpcClient.withDeadlineAfter(timeoutSeconds, TimeUnit.SECONDS)) clientOp(grpcClient.withDeadlineAfter(timeoutSeconds, TimeUnit.SECONDS)).recoverWith {
.recoverWith { case sre: StatusRuntimeException if sre.getStatus.getCode == Status.Code.UNAVAILABLE =>
case sre: StatusRuntimeException println(
if sre.getStatus.getCode == Status.Code.UNAVAILABLE => s"Unavailable, retrying in ${TimeUnit.MILLISECONDS.toSeconds(delayMillis)}s..."
println( )
s"Unavailable, retrying in ${TimeUnit.MILLISECONDS.toSeconds(delayMillis)}s..." Thread.sleep(delayMillis)
) go(
Thread.sleep(delayMillis) clientOp = clientOp,
go( delayMillis = Math.min(delayMillis * 2, maxDelayMillis)
clientOp = clientOp, )
delayMillis = Math.min(delayMillis * 2, maxDelayMillis) case sre: StatusRuntimeException if sre.getStatus.getCode == Status.Code.CANCELLED =>
) println("Cancelled, reconnecting...")
case sre: StatusRuntimeException go(
if sre.getStatus.getCode == Status.Code.CANCELLED => clientOp = clientOp,
println("Cancelled, reconnecting...") delayMillis = firstDelayMillis
go( )
clientOp = clientOp, case fr: Throwable => throw fr
delayMillis = firstDelayMillis }
)
case fr: Throwable => throw fr
}
}
def withRetries[Response]( def withRetries[Response](
clientOp: Stub => Future[Response] clientOp: Stub => Future[Response]
@@ -11,7 +11,7 @@ import org.json4s.DefaultFormats
object JsonUtils { object JsonUtils {
implicit val jsonFormats: DefaultFormats.type = DefaultFormats implicit val jsonFormats: DefaultFormats.type = DefaultFormats
private val json = new Json(jsonFormats) private val json = new Json(jsonFormats)
// This will just crash if the source url is not available, or if the // This will just crash if the source url is not available, or if the
// json is not a String:Vector[String] map // json is not a String:Vector[String] map
@@ -3,9 +3,9 @@ package net.eagle0.common
import scala.collection.mutable import scala.collection.mutable
case class Metrics[T](logFrequency: Int) { case class Metrics[T](logFrequency: Int) {
private var counters = mutable.Map[T, Int]() private var counters = mutable.Map[T, Int]()
private var totalCount = 0 private var totalCount = 0
private var lastLog = 0 private var lastLog = 0
def increment(counter: T): Unit = def increment(counter: T): Unit =
this.synchronized { this.synchronized {
@@ -19,15 +19,15 @@ case class Metrics[T](logFrequency: Int) {
} }
def logCounters: Unit = this.synchronized { def logCounters: Unit = this.synchronized {
counters.toVector.sortBy(-_._2).foreach { case (t, count) => counters.toVector.sortBy(-_._2).foreach {
println(s"$count $t") case (t, count) =>
println(s"$count $t")
} }
} }
def maybeLogCounters: Unit = { def maybeLogCounters: Unit =
if totalCount - lastLog > logFrequency then { if totalCount - lastLog > logFrequency then {
logCounters logCounters
lastLog = totalCount lastLog = totalCount
} }
}
} }
@@ -17,11 +17,13 @@ object MoreSeq {
def irregularTranspose[A](coll: Iterable[Seq[A]]): Vector[Vector[A]] = { def irregularTranspose[A](coll: Iterable[Seq[A]]): Vector[Vector[A]] = {
val maxLength = coll.map(_.length).max val maxLength = coll.map(_.length).max
coll.foldLeft(Vector.fill(maxLength)(Vector[A]())) { case (acc, nextLine) => coll.foldLeft(Vector.fill(maxLength)(Vector[A]())) {
acc.zipWithIndex.map { case (vec, idx) => case (acc, nextLine) =>
if idx < nextLine.length then vec :+ nextLine(idx) acc.zipWithIndex.map {
else vec case (vec, idx) =>
} if idx < nextLine.length then vec :+ nextLine(idx)
else vec
}
} }
} }
} }
@@ -35,7 +35,7 @@ object SimpleTimedLogger {
private def format(string: String): String = private def format(string: String): String =
s"${formatter.format(Instant.now())} $string" s"${formatter.format(Instant.now())} $string"
private def formatLine(string: String): String = s"${format(string)}\n" private def formatLine(string: String): String = s"${format(string)}\n"
private def formatNoLine(string: String): String = s"${format(string)} " private def formatNoLine(string: String): String = s"${format(string)} "
private class PrintLogger extends SimpleTimedLoggerT { private class PrintLogger extends SimpleTimedLoggerT {
@@ -44,15 +44,14 @@ object SimpleTimedLogger {
override def logLine(str: String): Unit = print(formatLine(str)) override def logLine(str: String): Unit = print(formatLine(str))
} }
private class SimpleTimedLogger(osConstructor: () => OutputStream) private class SimpleTimedLogger(osConstructor: () => OutputStream) extends SimpleTimedLoggerT {
extends SimpleTimedLoggerT {
def log(str: String): Unit = write(formatNoLine(str)) def log(str: String): Unit = write(formatNoLine(str))
def logLine(str: String): Unit = write(formatLine(str)) def logLine(str: String): Unit = write(formatLine(str))
private def write(str: String): Unit = private def write(str: String): Unit =
Using(new OutputStreamWriter(osConstructor(), StandardCharsets.UTF_8)) { Using(new OutputStreamWriter(osConstructor(), StandardCharsets.UTF_8)) { fw =>
fw => fw.write(str) fw.write(str)
} match { } match {
case Success(_) => () case Success(_) => ()
case Failure(exception) => case Failure(exception) =>
@@ -69,7 +68,7 @@ trait SimpleTimedLoggerT {
def logLine(str: String): Unit def logLine(str: String): Unit
def withStopwatch[T](str: String)(f: => T): T = { def withStopwatch[T](str: String)(f: => T): T = {
val start = System.currentTimeMillis() val start = System.currentTimeMillis()
val result = f val result = f
logLine(s"Finished $str in ${System.currentTimeMillis() - start} ms") logLine(s"Finished $str in ${System.currentTimeMillis() - start} ms")
result result
@@ -34,11 +34,11 @@ object TsvUtils {
} }
def loadLines(url: URL): Try[Vector[Vector[String]]] = def loadLines(url: URL): Try[Vector[Vector[String]]] =
Using(Source.fromURL(url, "UTF-8")) { loadLines } Using(Source.fromURL(url, "UTF-8"))(loadLines)
// Loads a tsv and returns a Vector of arrays of strings. // Loads a tsv and returns a Vector of arrays of strings.
def loadLines(path: String): Try[Vector[Vector[String]]] = def loadLines(path: String): Try[Vector[Vector[String]]] =
Using(Source.fromFile(path, "UTF-8")) { loadLines } Using(Source.fromFile(path, "UTF-8"))(loadLines)
def loadMaps(url: URL): Try[Vector[ListMap[String, String]]] = def loadMaps(url: URL): Try[Vector[ListMap[String, String]]] =
loadLines(url).map(linesToMaps) loadLines(url).map(linesToMaps)
@@ -80,7 +80,7 @@ object TsvUtils {
private def mapsToLines( private def mapsToLines(
maps: Vector[Map[String, String]] maps: Vector[Map[String, String]]
): Vector[Vector[String]] = { ): Vector[Vector[String]] = {
val headers = maps.head.keys.toVector val headers = maps.head.keys.toVector
val otherLines = maps.map(oneMap => headers.map(oneMap)) val otherLines = maps.map(oneMap => headers.map(oneMap))
headers +: otherLines headers +: otherLines
@@ -88,12 +88,11 @@ object TsvUtils {
private def saveLines(lines: Vector[Vector[String]], url: URL): Unit = { private def saveLines(lines: Vector[Vector[String]], url: URL): Unit = {
val file = new File(url.getPath) val file = new File(url.getPath)
val pw = new PrintWriter(file) val pw = new PrintWriter(file)
try { try
lines.foreach(line => pw.write(line.mkString("\t") + "\n")) lines.foreach(line => pw.write(line.mkString("\t") + "\n"))
} finally { finally
pw.close() pw.close()
}
} }
} }
@@ -9,7 +9,7 @@ object ZipUtils {
val os = new ByteArrayOutputStream() val os = new ByteArrayOutputStream()
val buf = new Array[Byte](1024) val buf = new Array[Byte](1024)
var n = gz.read(buf) var n = gz.read(buf)
while n > 0 do { while n > 0 do {
os.write(buf, 0, n) os.write(buf, 0, n)
n = gz.read(buf) n = gz.read(buf)
@@ -8,10 +8,10 @@ object ApiKeys {
"src/main/scala/net/eagle0/common/llm_integration/api_keys.txt" "src/main/scala/net/eagle0/common/llm_integration/api_keys.txt"
// These must have valid values set in api_keys.txt // These must have valid values set in api_keys.txt
private val openAIKeyName = "openai_api_key" private val openAIKeyName = "openai_api_key"
private val anthropicKeyName = "anthropic_api_key" private val anthropicKeyName = "anthropic_api_key"
private def readDictionary(): Map[String, String] = { private def readDictionary(): Map[String, String] =
Using(scala.io.Source.fromFile(apiKeyFilePath)) { source => Using(scala.io.Source.fromFile(apiKeyFilePath)) { source =>
val lines = source.getLines().filterNot(_.startsWith("#")).toList val lines = source.getLines().filterNot(_.startsWith("#")).toList
lines.map { line => lines.map { line =>
@@ -27,21 +27,19 @@ object ApiKeys {
"Failed to read api_keys.txt. Make sure you have copied api_keys_template.txt to api_keys.txt and set the keys." "Failed to read api_keys.txt. Make sure you have copied api_keys_template.txt to api_keys.txt and set the keys."
) )
} }
}
private val dictionary = readDictionary() private val dictionary = readDictionary()
private def getKey(key: String): String = { private def getKey(key: String): String =
dictionary.get(key) match { dictionary.get(key) match {
case None => case None =>
throw new ApiKeyException(s"$key not found") throw new ApiKeyException(s"$key not found")
case Some(value) if value.startsWith("your_") => case Some(value) if value.startsWith("your_") =>
throw new ApiKeyException( throw new ApiKeyException(
s"API key for $key is not set. Please set it in $apiKeyFilePath." s"API key for $key is not set. Please set it in $apiKeyFilePath."
) )
case Some(value) => value case Some(value) => value
} }
}
lazy val openAI: String = getKey(openAIKeyName) lazy val openAI: String = getKey(openAIKeyName)
@@ -7,10 +7,7 @@ import java.time.{Duration, ZonedDateTime}
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.function.Consumer import java.util.function.Consumer
import net.eagle0.common.llm_integration.ClaudeServiceImpl.{ import net.eagle0.common.llm_integration.ClaudeServiceImpl.{dictionaryFor, messageVector}
dictionaryFor,
messageVector
}
import org.json4s.{DefaultFormats, JString} import org.json4s.{DefaultFormats, JString}
import org.json4s.jvalue2extractable import org.json4s.jvalue2extractable
import org.json4s.jvalue2monadic import org.json4s.jvalue2monadic
@@ -18,22 +15,22 @@ import org.json4s.native.{Json, Serialization}
object ClaudeServiceImpl { object ClaudeServiceImpl {
val model: String = "claude-sonnet-4-20250514" val model: String = "claude-sonnet-4-20250514"
val maxTokens = 2048 val maxTokens = 2048
private val apiKey = ApiKeys.anthropic private val apiKey = ApiKeys.anthropic
private val baseURL = new URL("https://api.anthropic.com/v1/messages") private val baseURL = new URL("https://api.anthropic.com/v1/messages")
private def dictionaryFor( private def dictionaryFor(
modelName: String modelName: String
): Map[String, Any] = Map( ): Map[String, Any] = Map(
"model" -> modelName, "model" -> modelName,
"max_tokens" -> maxTokens, "max_tokens" -> maxTokens,
"stream" -> true "stream" -> true
) )
private def messageVector(inputText: String): Vector[Map[String, String]] = private def messageVector(inputText: String): Vector[Map[String, String]] =
Vector( Vector(
Map( Map(
"role" -> "user", "role" -> "user",
"content" -> inputText "content" -> inputText
) )
) )
@@ -83,14 +80,14 @@ class ClaudeServiceImpl(
private class ClaudeStringConsumer( private class ClaudeStringConsumer(
streamingConsumer: Consumer[StreamingTextResults] streamingConsumer: Consumer[StreamingTextResults]
) extends Consumer[String] { ) extends Consumer[String] {
private val json = new Json(DefaultFormats) private val json = new Json(DefaultFormats)
private var streamId = "" private var streamId = ""
override def accept(t: String): Unit = { override def accept(t: String): Unit = {
val parsedJson = json.parse(t) val parsedJson = json.parse(t)
parsedJson \ "type" match { parsedJson \ "type" match {
case JString("message_start") => case JString("message_start") =>
streamId = (parsedJson \ "message" \ "id").extract[String] streamId = (parsedJson \ "message" \ "id").extract[String]
case JString("content_block_delta") => case JString("content_block_delta") =>
(parsedJson \ "delta" \ "text") match { (parsedJson \ "delta" \ "text") match {
@@ -102,7 +99,7 @@ class ClaudeServiceImpl(
completed = false completed = false
) )
) )
case _ => ??? case _ => ???
} }
case JString("content_block_start") => () case JString("content_block_start") => ()
case JString("content_block_stop") => () case JString("content_block_stop") => ()
@@ -116,12 +113,12 @@ class ClaudeServiceImpl(
completed = true completed = true
) )
) )
case JString("error") => case JString("error") =>
val errorDetails = parsedJson \ "error" val errorDetails = parsedJson \ "error"
val errorType = (errorDetails \ "type").extract[String] val errorType = (errorDetails \ "type").extract[String]
val errorMessage = (errorDetails \ "message").extract[String] val errorMessage = (errorDetails \ "message").extract[String]
println(s"Got an \"($errorType)\" error from Claude: $errorMessage") println(s"Got an \"($errorType)\" error from Claude: $errorMessage")
case _ => ??? case _ => ???
} }
} }
} }
@@ -136,26 +133,24 @@ class ClaudeServiceImpl(
headers: Map[String, Vector[String]] headers: Map[String, Vector[String]]
): Option[RateLimits] = ): Option[RateLimits] =
for { for {
requestLimit <- headers.get("anthropic-ratelimit-requests-limit") requestLimit <- headers.get("anthropic-ratelimit-requests-limit")
tokenLimit <- headers.get("anthropic-ratelimit-tokens-limit") tokenLimit <- headers.get("anthropic-ratelimit-tokens-limit")
requestsRemaining <- headers.get("anthropic-ratelimit-requests-remaining") requestsRemaining <- headers.get("anthropic-ratelimit-requests-remaining")
tokensRemaining <- headers.get("anthropic-ratelimit-tokens-remaining") tokensRemaining <- headers.get("anthropic-ratelimit-tokens-remaining")
requestResetTime <- headers.get("anthropic-ratelimit-requests-reset") requestResetTime <- headers.get("anthropic-ratelimit-requests-reset")
tokenResetTime <- headers.get("anthropic-ratelimit-tokens-reset") tokenResetTime <- headers.get("anthropic-ratelimit-tokens-reset")
} yield { } yield RateLimits(
RateLimits( requestLimit = requestLimit.head.toInt,
requestLimit = requestLimit.head.toInt, tokenLimit = tokenLimit.head.toInt,
tokenLimit = tokenLimit.head.toInt, requestsRemaining = requestsRemaining.head.toInt,
requestsRemaining = requestsRemaining.head.toInt, tokensRemaining = tokensRemaining.head.toInt,
tokensRemaining = tokensRemaining.head.toInt, requestResetTime = ZonedDateTime.parse(
requestResetTime = ZonedDateTime.parse( requestResetTime.head,
requestResetTime.head, rfc3339formatter
rfc3339formatter ),
), tokenResetTime = ZonedDateTime.parse(
tokenResetTime = ZonedDateTime.parse( tokenResetTime.head,
tokenResetTime.head, rfc3339formatter
rfc3339formatter
)
) )
} )
} }
@@ -1,11 +1,6 @@
package net.eagle0.common.llm_integration package net.eagle0.common.llm_integration
import java.net.http.{ import java.net.http.{HttpClient, HttpRequest, HttpResponse, HttpTimeoutException}
HttpClient,
HttpRequest,
HttpResponse,
HttpTimeoutException
}
import java.net.http.HttpClient.{Redirect, Version} import java.net.http.HttpClient.{Redirect, Version}
import java.net.http.HttpResponse.ResponseInfo import java.net.http.HttpResponse.ResponseInfo
import java.net.HttpURLConnection import java.net.HttpURLConnection
@@ -49,10 +44,10 @@ final class ExternalTextGenerationCaller(
private var inProgressCount = 0 private var inProgressCount = 0
def maxConcurrentStreams: Int = serviceImpl.maxConcurrentStreams def maxConcurrentStreams: Int = serviceImpl.maxConcurrentStreams
def getInProgressCount: Int = inProgressCount def getInProgressCount: Int = inProgressCount
private var currentRateLimits: Option[RateLimits] = None private var currentRateLimits: Option[RateLimits] = None
private var currentRateLimitTime: ZonedDateTime = ZonedDateTime.now() private var currentRateLimitTime: ZonedDateTime = ZonedDateTime.now()
def millisUntilReset: Long = def millisUntilReset: Long =
ChronoUnit.MILLIS.between( ChronoUnit.MILLIS.between(
@@ -76,9 +71,8 @@ final class ExternalTextGenerationCaller(
inputText: String, inputText: String,
partialCompletion: Option[String], partialCompletion: Option[String],
streamingConsumer: Consumer[StreamingTextResults], streamingConsumer: Consumer[StreamingTextResults],
backoffSeconds: Double = backoffSeconds: Double = ExternalTextGenerationCaller.initialBackoffSeconds
ExternalTextGenerationCaller.initialBackoffSeconds ): Future[HttpResponse[Unit]] =
): Future[HttpResponse[Unit]] = {
streamCompletion( streamCompletion(
inputText = inputText, inputText = inputText,
request = serviceImpl.makeRequest( request = serviceImpl.makeRequest(
@@ -88,7 +82,6 @@ final class ExternalTextGenerationCaller(
backoffSeconds = backoffSeconds, backoffSeconds = backoffSeconds,
streamingConsumer = streamingConsumer streamingConsumer = streamingConsumer
) )
}
private def streamCompletion( private def streamCompletion(
inputText: String, inputText: String,
@@ -102,20 +95,18 @@ final class ExternalTextGenerationCaller(
println(s"Trying again after $backoffSeconds seconds...") println(s"Trying again after $backoffSeconds seconds...")
val promise = Promise[HttpResponse[Unit]]() val promise = Promise[HttpResponse[Unit]]()
val t = new Timer() val t = new Timer()
t.schedule( t.schedule(
new TimerTask { new TimerTask {
override def run(): Unit = { override def run(): Unit =
promise.completeWith( promise.completeWith(
streamCompletion( streamCompletion(
inputText = inputText, inputText = inputText,
request = request, request = request,
backoffSeconds = backoffSeconds = ExternalTextGenerationCaller.nextBackoff(backoffSeconds),
ExternalTextGenerationCaller.nextBackoff(backoffSeconds),
streamingConsumer = streamingConsumer streamingConsumer = streamingConsumer
) )
) )
}
}, },
(backoffSeconds * 1000).toLong (backoffSeconds * 1000).toLong
) )
@@ -126,19 +117,19 @@ final class ExternalTextGenerationCaller(
httpClient httpClient
.sendAsync( .sendAsync(
request, request,
(respInfo: ResponseInfo) => { (respInfo: ResponseInfo) =>
if respInfo.statusCode() == HttpURLConnection.HTTP_OK then if respInfo.statusCode() == HttpURLConnection.HTTP_OK then
new SseSubscriber(serviceImpl.stringConsumer(streamingConsumer)) new SseSubscriber(serviceImpl.stringConsumer(streamingConsumer))
else else
throw new RuntimeException( throw new RuntimeException(
s"Unexpected response code: ${respInfo.statusCode()}" s"Unexpected response code: ${respInfo.statusCode()}"
) )
}
) )
.asScala .asScala
.andThen { case x => .andThen {
inProgressCount -= 1 case x =>
x inProgressCount -= 1
x
} }
.transform { .transform {
case Success(httpResponse) => case Success(httpResponse) =>
@@ -196,7 +187,7 @@ final class ExternalTextGenerationCaller(
case e: CompletionException => case e: CompletionException =>
println(s"CompletionException error $e") println(s"CompletionException error $e")
tryAgain() tryAgain()
case e: Throwable => case e: Throwable =>
println(s"error $e") println(s"error $e")
tryAgain() tryAgain()
} }
@@ -66,13 +66,11 @@ object ExternalTextGenerationCallerApp {
|Alakanda's faction controls Atfordia. |Alakanda's faction controls Atfordia.
|Roger the Shrubber's faction controls Chapellia.""".stripMargin |Roger the Shrubber's faction controls Chapellia.""".stripMargin
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
val completion = caller.streamCompletion( val completion = caller.streamCompletion(
inputText = inputText, inputText = inputText,
partialCompletion = None, partialCompletion = None,
streamingConsumer = results => { streamingConsumer = results => print(results.value)
print(results.value)
}
) )
Await.result(completion, Duration(500, SECONDS)) Await.result(completion, Duration(500, SECONDS))
println("") println("")
@@ -6,10 +6,7 @@ import java.net.http.HttpRequest.BodyPublishers
import java.time.{Duration, ZonedDateTime} import java.time.{Duration, ZonedDateTime}
import java.util.function.Consumer import java.util.function.Consumer
import net.eagle0.common.llm_integration.OpenAIChatCompletionsServiceImpl.{ import net.eagle0.common.llm_integration.OpenAIChatCompletionsServiceImpl.{dictionaryFor, messageVector}
dictionaryFor,
messageVector
}
import org.json4s.{DefaultFormats, JArray, JObject, JString} import org.json4s.{DefaultFormats, JArray, JObject, JString}
import org.json4s.jvalue2extractable import org.json4s.jvalue2extractable
import org.json4s.jvalue2monadic import org.json4s.jvalue2monadic
@@ -18,16 +15,16 @@ import org.json4s.native.{Json, Serialization}
object OpenAIChatCompletionsServiceImpl { object OpenAIChatCompletionsServiceImpl {
val gpt5: String = "gpt-5" val gpt5: String = "gpt-5"
private val apiKey = ApiKeys.openAI private val apiKey = ApiKeys.openAI
private val baseURL = new URL("https://api.openai.com/v1/chat/completions") private val baseURL = new URL("https://api.openai.com/v1/chat/completions")
private val temperature: Double = 1.0 private val temperature: Double = 1.0
private def dictionaryFor( private def dictionaryFor(
modelName: String modelName: String
): Map[String, Any] = Map( ): Map[String, Any] = Map(
"model" -> modelName, "model" -> modelName,
"temperature" -> temperature, "temperature" -> temperature,
"stream" -> true, "stream" -> true,
"reasoning_effort" -> "minimal" "reasoning_effort" -> "minimal"
) )
@@ -37,12 +34,12 @@ object OpenAIChatCompletionsServiceImpl {
): Vector[Map[String, String]] = ): Vector[Map[String, String]] =
Vector( Vector(
Map( Map(
"role" -> "user", "role" -> "user",
"content" -> inputText "content" -> inputText
) )
) ++ partialCompletion.map(pc => ) ++ partialCompletion.map(pc =>
Map( Map(
"role" -> "assistant", "role" -> "assistant",
"content" -> pc "content" -> pc
) )
) )
@@ -99,18 +96,18 @@ class OpenAIChatCompletionsServiceImpl(
override def stringConsumer( override def stringConsumer(
streamingConsumer: Consumer[StreamingTextResults] streamingConsumer: Consumer[StreamingTextResults]
): Consumer[String] = (t: String) => { ): Consumer[String] = (t: String) => {
val json = new Json(DefaultFormats) val json = new Json(DefaultFormats)
val parsedJson = val parsedJson =
try { try
json.parse(t) json.parse(t)
} catch { catch {
case pe: org.json4s.ParserUtil.ParseException => case pe: org.json4s.ParserUtil.ParseException =>
println(s"Failed to parse JSON: $t") println(s"Failed to parse JSON: $t")
throw pe throw pe
} }
val streamId = (parsedJson \ "id").extract[String] val streamId = (parsedJson \ "id").extract[String]
val choice = (parsedJson \ "choices") val choice = (parsedJson \ "choices")
.asInstanceOf[JArray] .asInstanceOf[JArray]
.arr .arr
.head .head
@@ -126,7 +123,7 @@ class OpenAIChatCompletionsServiceImpl(
case JString(str) => case JString(str) =>
println(s"Unexpected finish reason $str") println(s"Unexpected finish reason $str")
true true
case _ => false case _ => false
} }
(choice \ "delta" \ "content") match { (choice \ "delta" \ "content") match {
@@ -138,7 +135,7 @@ class OpenAIChatCompletionsServiceImpl(
completed = completed completed = completed
) )
) )
case _ => case _ =>
if completed then if completed then
streamingConsumer.accept( streamingConsumer.accept(
StreamingTextResults( StreamingTextResults(
@@ -154,32 +151,30 @@ class OpenAIChatCompletionsServiceImpl(
headers: Map[String, Vector[String]] headers: Map[String, Vector[String]]
): Option[RateLimits] = ): Option[RateLimits] =
for { for {
requestLimit <- headers.get("x-ratelimit-limit-requests") requestLimit <- headers.get("x-ratelimit-limit-requests")
tokenLimit <- headers.get("x-ratelimit-limit-tokens") tokenLimit <- headers.get("x-ratelimit-limit-tokens")
requestsRemaining <- headers.get("x-ratelimit-remaining-requests") requestsRemaining <- headers.get("x-ratelimit-remaining-requests")
tokensRemaining <- headers.get("x-ratelimit-remaining-tokens") tokensRemaining <- headers.get("x-ratelimit-remaining-tokens")
requestResetTime <- headers.get("x-ratelimit-reset-requests") requestResetTime <- headers.get("x-ratelimit-reset-requests")
tokenResetTime <- headers.get("x-ratelimit-reset-tokens") tokenResetTime <- headers.get("x-ratelimit-reset-tokens")
} yield { } yield RateLimits(
RateLimits( requestLimit = requestLimit.head.toInt,
requestLimit = requestLimit.head.toInt, tokenLimit = tokenLimit.head.toInt,
tokenLimit = tokenLimit.head.toInt, requestsRemaining = requestsRemaining.head.toInt,
requestsRemaining = requestsRemaining.head.toInt, tokensRemaining = tokensRemaining.head.toInt,
tokensRemaining = tokensRemaining.head.toInt, requestResetTime = ZonedDateTime
requestResetTime = ZonedDateTime .now()
.now() .plus(
.plus( OpenAiDurationParser
OpenAiDurationParser .parseDuration(requestResetTime.head)
.parseDuration(requestResetTime.head) .getOrElse(Duration.ZERO)
.getOrElse(Duration.ZERO) ),
), tokenResetTime = ZonedDateTime
tokenResetTime = ZonedDateTime .now()
.now() .plus(
.plus( OpenAiDurationParser
OpenAiDurationParser .parseDuration(tokenResetTime.head)
.parseDuration(tokenResetTime.head) .getOrElse(Duration.ZERO)
.getOrElse(Duration.ZERO) )
) )
)
}
} }
@@ -4,13 +4,13 @@ import java.time.Duration
object OpenAiDurationParser { object OpenAiDurationParser {
private val durationRegex = """(\d+)((ms)|[smhdy])""".r private val durationRegex = """(\d+)((ms)|[smhdy])""".r
private val durationMap = Map( private val durationMap = Map(
"ms" -> Duration.ofMillis(1), "ms" -> Duration.ofMillis(1),
"s" -> Duration.ofSeconds(1), "s" -> Duration.ofSeconds(1),
"m" -> Duration.ofMinutes(1), "m" -> Duration.ofMinutes(1),
"h" -> Duration.ofHours(1), "h" -> Duration.ofHours(1),
"d" -> Duration.ofDays(1), "d" -> Duration.ofDays(1),
"y" -> Duration.ofDays(365) "y" -> Duration.ofDays(365)
) )
def parseDuration(duration: String): Option[Duration] = def parseDuration(duration: String): Option[Duration] =
@@ -18,7 +18,7 @@ object OpenAiDurationParser {
.findAllMatchIn(duration) .findAllMatchIn(duration)
.map { m => .map { m =>
val amount = m.group(1).toInt val amount = m.group(1).toInt
val unit = m.group(2) val unit = m.group(2)
durationMap(unit).multipliedBy(amount) durationMap(unit).multipliedBy(amount)
} }
.toSeq match { .toSeq match {
@@ -7,14 +7,14 @@ import scala.util.Random
import net.eagle0.common.{SeededRandom, TsvUtils} import net.eagle0.common.{SeededRandom, TsvUtils}
class NameGenerator { class NameGenerator {
private val random = SeededRandom(Random.nextLong()) private val random = SeededRandom(Random.nextLong())
private val emptySubstitutions = Map[String, String]() private val emptySubstitutions = Map[String, String]()
private val nameConstructionUrl = new File( private val nameConstructionUrl = new File(
"/opt/nameConstruction.txt" "/opt/nameConstruction.txt"
).toURI.toURL ).toURI.toURL
private val namesFileUrl = new File("/opt/names.tsv").toURI.toURL private val namesFileUrl = new File("/opt/names.tsv").toURI.toURL
private val namesMap = TsvUtils.loadColumnMaps(namesFileUrl).get private val namesMap = TsvUtils.loadColumnMaps(namesFileUrl).get
private val formatString = private val formatString =
StringConstructionTokenGenerator.formatStringFromUrl( StringConstructionTokenGenerator.formatStringFromUrl(
nameConstructionUrl nameConstructionUrl
@@ -41,17 +41,17 @@ class NameGenerator {
atSpecializations = Vector("male") atSpecializations = Vector("male")
) )
def nextFemaleName(): String = def nextFemaleName(): String =
femaleTok.nextString(random, emptySubstitutions).newValue femaleTok.nextString(random, emptySubstitutions).newValue
def nextMaleName(): String = def nextMaleName(): String =
maleTok.nextString(random, emptySubstitutions).newValue maleTok.nextString(random, emptySubstitutions).newValue
def nextNeutralName(): String = def nextNeutralName(): String =
allTok.nextString(random, emptySubstitutions).newValue allTok.nextString(random, emptySubstitutions).newValue
def nextName(): String = { def nextName(): String = {
val intR = random.nextInt(0, 20) val intR = random.nextInt(0, 20)
val newInt = intR.newValue val newInt = intR.newValue
val tok = val tok =
if newInt == 0 then allTok if newInt == 0 then allTok
else if newInt <= 10 then femaleTok else if newInt <= 10 then femaleTok
else maleTok else maleTok
@@ -10,8 +10,9 @@ class StringConstructionParser(
import ParseSequence.* import ParseSequence.*
private def charString(seq: Seq[CharacterOrToken]): String = private def charString(seq: Seq[CharacterOrToken]): String =
seq.collect { case C(char) => seq.collect {
char case C(char) =>
char
}.mkString }.mkString
def parseLiteral(fmt: String): Option[(String, StringConstructionToken)] = { def parseLiteral(fmt: String): Option[(String, StringConstructionToken)] = {
@@ -25,12 +26,11 @@ class StringConstructionParser(
def parseRemaining(str: String, acc: String): ParseStatus = def parseRemaining(str: String, acc: String): ParseStatus =
str.head match { str.head match {
case '\\' => case '\\' =>
if str.length < 2 then if str.length < 2 then throw new IllegalArgumentException("Found \\ at end of literal")
throw new IllegalArgumentException("Found \\ at end of literal")
else parseRemaining(str.drop(2), acc + str.charAt(1)) else parseRemaining(str.drop(2), acc + str.charAt(1))
case '\"' => case '\"' =>
ParseStatus(str.drop(1), acc) ParseStatus(str.drop(1), acc)
case _ => case _ =>
parseRemaining(str.drop(1), acc + str.head) parseRemaining(str.drop(1), acc + str.head)
} }
@@ -41,44 +41,44 @@ class StringConstructionParser(
private def parseOptional( private def parseOptional(
ts: ParseSequence ts: ParseSequence
): Try[(ParseSequence, StringConstructionToken)] = { ): Try[(ParseSequence, StringConstructionToken)] =
insideBalanced(ts, open = '{', closed = '}').flatMap { insideBalanced(ts, open = '{', closed = '}').flatMap {
case (remainAfter, inWithOdds) => case (remainAfter, inWithOdds) =>
val DecimalParseResults(inAfterOdds, odds) = readDecimal(inWithOdds) val DecimalParseResults(inAfterOdds, odds) = readDecimal(inWithOdds)
parsePossiblyParsed(inAfterOdds).flatMap { case (rem, parsed) => parsePossiblyParsed(inAfterOdds).flatMap {
if rem.nonEmpty then case (rem, parsed) =>
Failure( if rem.nonEmpty then
new IllegalArgumentException( Failure(
"Found extra characters inside optional" new IllegalArgumentException(
"Found extra characters inside optional"
)
) )
) else Success((remainAfter, OptionalToken(parsed, odds)))
else Success((remainAfter, OptionalToken(parsed, odds)))
} }
} }
}
private def parseOneof( private def parseOneof(
ts: ParseSequence ts: ParseSequence
): Try[(ParseSequence, StringConstructionToken)] = { ): Try[(ParseSequence, StringConstructionToken)] =
insideBalanced(ts, open = '[', closed = ']').map { insideBalanced(ts, open = '[', closed = ']').map {
case (remainAfter, inside) => case (remainAfter, inside) =>
var entries = Vector.empty[OneofListEntry] var entries = Vector.empty[OneofListEntry]
var pos = inside var pos = inside
while pos.nonEmpty do { while pos.nonEmpty do {
val t = pos.head val t = pos.head
if t == ',' then pos = pos.drop(1) if t == ',' then pos = pos.drop(1)
else { else {
val DecimalParseResults(remIn, weight) = readDecimal(pos) val DecimalParseResults(remIn, weight) = readDecimal(pos)
parsePossiblyParsed(remIn).map { case (rem, parsed) => parsePossiblyParsed(remIn).map {
entries = entries :+ OneofListEntry(parsed, weight) case (rem, parsed) =>
pos = rem entries = entries :+ OneofListEntry(parsed, weight)
pos = rem
} }
} }
} }
(remainAfter, OneofListToken(entries)) (remainAfter, OneofListToken(entries))
} }
}
def parseListSelector( def parseListSelector(
ts: ParseSequence ts: ParseSequence
@@ -112,12 +112,11 @@ class StringConstructionParser(
def parseCapitalized( def parseCapitalized(
ts: ParseSequence ts: ParseSequence
): Try[(ParseSequence, StringConstructionToken)] = { ): Try[(ParseSequence, StringConstructionToken)] =
for { for {
(remainAfter, inside) <- insideBalanced(ts, open = '-', closed = '+') (remainAfter, inside) <- insideBalanced(ts, open = '-', closed = '+')
(_, parsed) <- parsePossiblyParsed(inside) (_, parsed) <- parsePossiblyParsed(inside)
} yield (remainAfter, TitleCaseToken(parsed)) } yield (remainAfter, TitleCaseToken(parsed))
}
def parseOrdinal( def parseOrdinal(
ts: ParseSequence ts: ParseSequence
@@ -146,7 +145,7 @@ class StringConstructionParser(
def go( def go(
tokens: ParseSequence, tokens: ParseSequence,
acc: Seq[StringConstructionToken] acc: Seq[StringConstructionToken]
): Try[(ParseSequence, Seq[StringConstructionToken])] = { ): Try[(ParseSequence, Seq[StringConstructionToken])] =
if tokens.isEmpty then Success((Vector.empty, acc)) if tokens.isEmpty then Success((Vector.empty, acc))
else { else {
tokens.head match { tokens.head match {
@@ -168,10 +167,10 @@ class StringConstructionParser(
case T(t) => go(tokens.drop(1), acc :+ t) case T(t) => go(tokens.drop(1), acc :+ t)
} }
} }
}
go(tokens, Vector.empty).map { case (rem, ts) => go(tokens, Vector.empty).map {
(rem, if ts.size == 1 then ts.head else SequenceToken(ts)) case (rem, ts) =>
(rem, if ts.size == 1 then ts.head else SequenceToken(ts))
} }
} }
@@ -180,7 +179,7 @@ class StringConstructionParser(
def parseNext( def parseNext(
remainingString: String, remainingString: String,
acc: ParseSequence acc: ParseSequence
): ParseSequence = { ): ParseSequence =
if remainingString.isEmpty then acc if remainingString.isEmpty then acc
else else
remainingString.head match { remainingString.head match {
@@ -192,19 +191,18 @@ class StringConstructionParser(
throw new IllegalArgumentException( throw new IllegalArgumentException(
s"Illegal literal in $remainingString" s"Illegal literal in $remainingString"
) )
case _ => case _ =>
parseNext( parseNext(
remainingString.drop(1), remainingString.drop(1),
acc :+ C(remainingString.head) acc :+ C(remainingString.head)
) )
} }
}
val tokens = parseNext(formatString, Vector.empty) val tokens = parseNext(formatString, Vector.empty)
parsePossiblyParsed(tokens).flatMap { case (rem, tok) => parsePossiblyParsed(tokens).flatMap {
if rem.nonEmpty then case (rem, tok) =>
Failure(new IllegalArgumentException("Failed to parse")) if rem.nonEmpty then Failure(new IllegalArgumentException("Failed to parse"))
else Success(tok) else Success(tok)
} }
} }
@@ -213,8 +211,8 @@ class StringConstructionParser(
open: Char, open: Char,
closed: Char closed: Char
): Try[(ParseSequence, ParseSequence)] = { ): Try[(ParseSequence, ParseSequence)] = {
var pos = ts.drop(1) var pos = ts.drop(1)
var level = 1 var level = 1
var inside = Vector.empty[CharacterOrToken] var inside = Vector.empty[CharacterOrToken]
while level > 0 && pos.nonEmpty do { while level > 0 && pos.nonEmpty do {
@@ -226,8 +224,7 @@ class StringConstructionParser(
pos = pos.drop(1) pos = pos.drop(1)
} }
if level > 0 then if level > 0 then Failure(new IllegalArgumentException("Did not find closing character"))
Failure(new IllegalArgumentException("Did not find closing character"))
Success((pos, inside)) Success((pos, inside))
} }
@@ -26,7 +26,7 @@ object StringConstructionTester {
"longbowmen" "longbowmen"
) )
types.foreach { typeName => types.foreach { typeName =>
val adj = s"${typeName}_adj" val adj = s"${typeName}_adj"
val newSubs = val newSubs =
substitutions + ("BATTALION_NAME" -> typeName) + ("BATTALION_ADJECTIVE" -> adj) substitutions + ("BATTALION_NAME" -> typeName) + ("BATTALION_ADJECTIVE" -> adj)
@@ -41,7 +41,7 @@ object StringConstructionTester {
val random = new JankyRandom(new Random()) val random = new JankyRandom(new Random())
print(s"---\n$typeName\n---\n") print(s"---\n$typeName\n---\n")
((1 to 20).toVector).foreach { _ => (1 to 20).toVector.foreach { _ =>
println(token.nextString(random, Map("PLACE" -> "Pieska")).newValue) println(token.nextString(random, Map("PLACE" -> "Pieska")).newValue)
} }
print("\n\n\n") print("\n\n\n")
@@ -19,8 +19,7 @@ case class LiteralToken(literal: String) extends StringConstructionToken {
RandomState(literal, random) RandomState(literal, random)
} }
class MissingLiteralKeyException(key: String) class MissingLiteralKeyException(key: String) extends Exception(s"Missing literal key $key") {}
extends Exception(s"Missing literal key $key") {}
case class SubstitutionToken(key: String) extends StringConstructionToken { case class SubstitutionToken(key: String) extends StringConstructionToken {
override def nextString( override def nextString(
@@ -34,27 +33,25 @@ case class SubstitutionToken(key: String) extends StringConstructionToken {
) )
} }
case class SequenceToken(tokens: Seq[StringConstructionToken]) case class SequenceToken(tokens: Seq[StringConstructionToken]) extends StringConstructionToken {
extends StringConstructionToken {
override def nextString( override def nextString(
random: FunctionalRandom, random: FunctionalRandom,
literalSubstitutions: Map[String, String] literalSubstitutions: Map[String, String]
): RandomState[String] = ): RandomState[String] =
tokens.foldLeft(RandomState[String]("", random)) { case (state, tok) => tokens.foldLeft(RandomState[String]("", random)) {
val nextStr = tok.nextString(state.nextRandom, literalSubstitutions) case (state, tok) =>
RandomState(state.newValue + nextStr.newValue, nextStr.nextRandom) val nextStr = tok.nextString(state.nextRandom, literalSubstitutions)
RandomState(state.newValue + nextStr.newValue, nextStr.nextRandom)
} }
} }
case class OptionalToken(token: StringConstructionToken, odds: Double) case class OptionalToken(token: StringConstructionToken, odds: Double) extends StringConstructionToken {
extends StringConstructionToken {
override def nextString( override def nextString(
random: FunctionalRandom, random: FunctionalRandom,
literalSubstitutions: Map[String, String] literalSubstitutions: Map[String, String]
): RandomState[String] = { ): RandomState[String] = {
val state = random.nextDouble val state = random.nextDouble
if state.newValue < odds then if state.newValue < odds then token.nextString(state.nextRandom, literalSubstitutions)
token.nextString(state.nextRandom, literalSubstitutions)
else RandomState("", state.nextRandom) else RandomState("", state.nextRandom)
} }
} }
@@ -67,33 +64,32 @@ case class OneofListEntry(token: StringConstructionToken, weight: Double) {
token.nextString(random, literalSubstitutions) token.nextString(random, literalSubstitutions)
} }
case class OneofListToken(entries: Seq[OneofListEntry]) case class OneofListToken(entries: Seq[OneofListEntry]) extends StringConstructionToken {
extends StringConstructionToken {
override def nextString( override def nextString(
random: FunctionalRandom, random: FunctionalRandom,
literalSubstitutions: Map[String, String] literalSubstitutions: Map[String, String]
): RandomState[String] = { ): RandomState[String] = {
val totalWeight = entries.map(_.weight).sum val totalWeight = entries.map(_.weight).sum
random.nextDouble.continue { case (double, fr) => random.nextDouble.continue {
@tailrec case (double, fr) =>
def go( @tailrec
remainingEntries: Seq[OneofListEntry], def go(
remainingD: Double remainingEntries: Seq[OneofListEntry],
): RandomState[String] = remainingEntries match { remainingD: Double
case Nil => ): RandomState[String] = remainingEntries match {
throw new IllegalStateException("Failed to find a valid oneof") case Nil =>
case w +: tail => throw new IllegalStateException("Failed to find a valid oneof")
if remainingD < w.weight then w.toString(fr, literalSubstitutions) case w +: tail =>
else go(tail, remainingD - w.weight) if remainingD < w.weight then w.toString(fr, literalSubstitutions)
} else go(tail, remainingD - w.weight)
}
go(entries, double * totalWeight) go(entries, double * totalWeight)
} }
} }
} }
case class ListSelectionToken(choices: Seq[String]) case class ListSelectionToken(choices: Seq[String]) extends StringConstructionToken {
extends StringConstructionToken {
override def nextString( override def nextString(
random: FunctionalRandom, random: FunctionalRandom,
literalSubstitutions: Map[String, String] literalSubstitutions: Map[String, String]
@@ -104,7 +100,7 @@ case class OrdinalSelectionToken(max: Int) extends StringConstructionToken {
override def nextString( override def nextString(
random: FunctionalRandom, random: FunctionalRandom,
literalSubstitutions: Map[String, String] literalSubstitutions: Map[String, String]
): RandomState[String] = { ): RandomState[String] =
random.nextInt(1, max).map { value => random.nextInt(1, max).map { value =>
if value == 11 then "11th" if value == 11 then "11th"
else { else {
@@ -116,11 +112,9 @@ case class OrdinalSelectionToken(max: Int) extends StringConstructionToken {
}) })
} }
} }
}
} }
case class TitleCaseToken(base: StringConstructionToken) case class TitleCaseToken(base: StringConstructionToken) extends StringConstructionToken {
extends StringConstructionToken {
val uncapitalizedWords: Set[String] = val uncapitalizedWords: Set[String] =
Set("and", "but", "for", "or", "nor", "the", "a", "an", "to", "as", "of") Set("and", "but", "for", "or", "nor", "the", "a", "an", "to", "as", "of")
@@ -128,8 +122,8 @@ case class TitleCaseToken(base: StringConstructionToken)
words match { words match {
case first +: middle :+ last => case first +: middle :+ last =>
first.capitalize +: middle :+ last.capitalize first.capitalize +: middle :+ last.capitalize
case Vector(only: String) => Vector(only.capitalize) case Vector(only: String) => Vector(only.capitalize)
case Vector() => Vector() case Vector() => Vector()
} }
private def byWordCapitalized(string: String): Vector[String] = private def byWordCapitalized(string: String): Vector[String] =
@@ -141,9 +135,8 @@ case class TitleCaseToken(base: StringConstructionToken)
override def nextString( override def nextString(
random: FunctionalRandom, random: FunctionalRandom,
literalSubstitutions: Map[String, String] literalSubstitutions: Map[String, String]
): RandomState[String] = { ): RandomState[String] =
base.nextString(random, literalSubstitutions).map { cap => base.nextString(random, literalSubstitutions).map { cap =>
firstAndLastCapitalized(byWordCapitalized(cap)).mkString(" ") firstAndLastCapitalized(byWordCapitalized(cap)).mkString(" ")
} }
}
} }
@@ -23,8 +23,8 @@ object StringConstructionTokenGenerator {
} }
val (stringsMap, unfilteredStringsMap) = { val (stringsMap, unfilteredStringsMap) = {
val allSpecializations = namesMap val allSpecializations = namesMap.map {
.map { case (key, words) => case (key, words) =>
key.split("@", 2) match { key.split("@", 2) match {
case Array(realKey) => (realKey, "", words) case Array(realKey) => (realKey, "", words)
case Array(realKey, spec) => (realKey, spec, words) case Array(realKey, spec) => (realKey, spec, words)
@@ -33,17 +33,14 @@ object StringConstructionTokenGenerator {
"Too many @s in column name" "Too many @s in column name"
) )
} }
} }.toVector.groupBy(_._1)
.toVector
.groupBy { _._1 }
val stringsMap = allSpecializations val stringsMap = allSpecializations.map {
.map { case (realWord, specializations) => case (realWord, specializations) =>
val includedSpecializations = specializations.filter(spec => val includedSpecializations =
spec._2.isEmpty || atSpecializations.contains(spec._2) specializations.filter(spec => spec._2.isEmpty || atSpecializations.contains(spec._2))
)
(realWord, includedSpecializations.flatMap(_._3)) (realWord, includedSpecializations.flatMap(_._3))
} }
val unfilteredStringsMap = allSpecializations.map { val unfilteredStringsMap = allSpecializations.map {
case (realWord, specializations) => case (realWord, specializations) =>
@@ -5,10 +5,10 @@ import scala.annotation.tailrec
// Functions for parsing events from an SSE stream. Uses spec as defined in // Functions for parsing events from an SSE stream. Uses spec as defined in
// https://html.spec.whatwg.org/multipage/server-sent-events.html // https://html.spec.whatwg.org/multipage/server-sent-events.html
object SseEventReader { object SseEventReader {
private val DataKey = "data" private val DataKey = "data"
private val EventKey = "event" private val EventKey = "event"
private val IdKey = "id" private val IdKey = "id"
private val RetryKey = "retry" private val RetryKey = "retry"
private val DefaultEventValue = "message" private val DefaultEventValue = "message"
sealed trait ReadResult sealed trait ReadResult
@@ -36,8 +36,7 @@ object SseEventReader {
else else
readLines( readLines(
IncompleteEvent( IncompleteEvent(
eventType = eventType = carryoverEvent.map(_.eventType).getOrElse(DefaultEventValue),
carryoverEvent.map(_.eventType).getOrElse(DefaultEventValue),
metadata = carryoverEvent.map(_.metadata).getOrElse(Map.empty), metadata = carryoverEvent.map(_.metadata).getOrElse(Map.empty),
data = carryoverEvent.map(_.data).getOrElse(""), data = carryoverEvent.map(_.data).getOrElse(""),
carryover = carryoverEvent.map(_.carryover).getOrElse("") + text carryover = carryoverEvent.map(_.carryover).getOrElse("") + text
@@ -53,14 +52,14 @@ object SseEventReader {
if value.startsWith(" ") then value.drop(1) else value if value.startsWith(" ") then value.drop(1) else value
key match { key match {
case DataKey => case DataKey =>
IncompleteEvent( IncompleteEvent(
eventType = acc.eventType, eventType = acc.eventType,
metadata = acc.metadata, metadata = acc.metadata,
data = acc.data + trimmedSpaceValue, data = acc.data + trimmedSpaceValue,
carryover = acc.carryover carryover = acc.carryover
) )
case EventKey => case EventKey =>
IncompleteEvent( IncompleteEvent(
eventType = trimmedSpaceValue, eventType = trimmedSpaceValue,
metadata = acc.metadata, metadata = acc.metadata,
@@ -74,23 +73,23 @@ object SseEventReader {
data = acc.data, data = acc.data,
carryover = acc.carryover carryover = acc.carryover
) )
case _ => case _ =>
// The spec actually says to ignore fields except if the key is "event", "data", "id", or "retry" // The spec actually says to ignore fields except if the key is "event", "data", "id", or "retry"
acc acc
} }
} }
@tailrec @tailrec
private def readLines(acc: IncompleteEvent): ReadResult = { private def readLines(acc: IncompleteEvent): ReadResult =
acc.carryover.split("\n", 2) match { acc.carryover.split("\n", 2) match {
case Array("", moreText) => // blank line indicates end of event case Array("", moreText) => // blank line indicates end of event
CompleteEvent( CompleteEvent(
eventType = acc.eventType, eventType = acc.eventType,
metadata = acc.metadata, metadata = acc.metadata,
data = acc.data, data = acc.data,
remainingText = moreText remainingText = moreText
) )
case Array(singleLine) => // did not end in a newline case Array(singleLine) => // did not end in a newline
IncompleteEvent( IncompleteEvent(
eventType = acc.eventType, eventType = acc.eventType,
metadata = acc.metadata, metadata = acc.metadata,
@@ -101,7 +100,7 @@ object SseEventReader {
case Array(kv, newCarryover) => case Array(kv, newCarryover) =>
val accWithNewCarryover = acc.copy(carryover = newCarryover) val accWithNewCarryover = acc.copy(carryover = newCarryover)
kv.split(":", 2) match { kv.split(":", 2) match {
case Array("", _) => case Array("", _) =>
// Line started with a colon; ignore line // Line started with a colon; ignore line
readLines(accWithNewCarryover) readLines(accWithNewCarryover)
case Array(key, value) => case Array(key, value) =>
@@ -109,14 +108,13 @@ object SseEventReader {
readLines( readLines(
withSingleKeyValuePair(accWithNewCarryover, key, value) withSingleKeyValuePair(accWithNewCarryover, key, value)
) )
case Array(key) => case Array(key) =>
// Line contained a key with no value, use empty string // Line contained a key with no value, use empty string
readLines( readLines(
withSingleKeyValuePair(accWithNewCarryover, key, "") withSingleKeyValuePair(accWithNewCarryover, key, "")
) )
case _ => ??? case _ => ???
} }
case _ => ??? case _ => ???
} }
}
} }
@@ -12,15 +12,13 @@ import scala.jdk.CollectionConverters.CollectionHasAsScala
import org.json4s.ParserUtil import org.json4s.ParserUtil
class SseException(message: String, cause: Throwable = null) class SseException(message: String, cause: Throwable = null) extends Exception(message, cause)
extends Exception(message, cause)
class SseSubscriber(messageDataConsumer: Consumer[String]) class SseSubscriber(messageDataConsumer: Consumer[String]) extends BodySubscriber[Unit] {
extends BodySubscriber[Unit] {
@volatile private var subscription: Flow.Subscription = _ @volatile private var subscription: Flow.Subscription = _
private val future: CompletableFuture[Unit] = new CompletableFuture[Unit] private val future: CompletableFuture[Unit] = new CompletableFuture[Unit]
private val DoneToken = "[DONE]" private val DoneToken = "[DONE]"
private val MessageType = "message" private val MessageType = "message"
override def getBody: CompletionStage[Unit] = future override def getBody: CompletionStage[Unit] = future
@@ -42,7 +40,7 @@ class SseSubscriber(messageDataConsumer: Consumer[String])
@volatile private var incompleteText: Option[SseEventReader.IncompleteEvent] = @volatile private var incompleteText: Option[SseEventReader.IncompleteEvent] =
None None
override def onNext(buffers: util.List[ByteBuffer]): Unit = try { override def onNext(buffers: util.List[ByteBuffer]): Unit = try {
val newText = buffers.asScala val newText = buffers.asScala
.map(StandardCharsets.UTF_8.decode) .map(StandardCharsets.UTF_8.decode)
.mkString .mkString
@@ -51,7 +49,7 @@ class SseSubscriber(messageDataConsumer: Consumer[String])
def consumeEvents( def consumeEvents(
incompleteEvent: Option[SseEventReader.IncompleteEvent], incompleteEvent: Option[SseEventReader.IncompleteEvent],
remainingText: String remainingText: String
): Option[SseEventReader.IncompleteEvent] = { ): Option[SseEventReader.IncompleteEvent] =
SseEventReader.readOneEvent(incompleteEvent, remainingText) match { SseEventReader.readOneEvent(incompleteEvent, remainingText) match {
case SseEventReader.EndOfStream => case SseEventReader.EndOfStream =>
None None
@@ -92,7 +90,6 @@ class SseSubscriber(messageDataConsumer: Consumer[String])
case incomplete: SseEventReader.IncompleteEvent => case incomplete: SseEventReader.IncompleteEvent =>
Some(incomplete) Some(incomplete)
} }
}
this.incompleteText = consumeEvents(this.incompleteText, newText) this.incompleteText = consumeEvents(this.incompleteText, newText)
@@ -107,7 +104,7 @@ class SseSubscriber(messageDataConsumer: Consumer[String])
println(fullBuffer) println(fullBuffer)
future.completeExceptionally(pe) future.completeExceptionally(pe)
subscription.cancel() subscription.cancel()
case e: Exception => case e: Exception =>
future.completeExceptionally(e) future.completeExceptionally(e)
subscription.cancel() subscription.cancel()
} }
+6 -7
View File
@@ -12,10 +12,10 @@ import net.eagle0.eagle.service.*
object Main { object Main {
import ServerSetupHelpers.* import ServerSetupHelpers.*
private val eagleGrpcPortKey = Symbol("eagleGrpcPort") private val eagleGrpcPortKey = Symbol("eagleGrpcPort")
private val defaultEagleGrpcPort = "40032" private val defaultEagleGrpcPort = "40032"
private val shardokInterfaceRemoteAddressKey = Symbol( private val shardokInterfaceRemoteAddressKey = Symbol(
"shardokInterfaceRemoteAddress" "shardokInterfaceRemoteAddress"
) )
private val defaultShardokInterfaceRemoteAddress = "eagle0.net:443" private val defaultShardokInterfaceRemoteAddress = "eagle0.net:443"
@@ -26,26 +26,25 @@ object Main {
def nextOption( def nextOption(
map: Map[Symbol, String], map: Map[Symbol, String],
list: Vector[String] list: Vector[String]
): Map[Symbol, String] = { ): Map[Symbol, String] =
list match { list match {
case "--eagle-grpc-port" +: value +: tail => case "--eagle-grpc-port" +: value +: tail =>
nextOption(map ++ Map(eagleGrpcPortKey -> value), tail) nextOption(map ++ Map(eagleGrpcPortKey -> value), tail)
case "--shardok-interface-remote-address" +: value +: tail => case "--shardok-interface-remote-address" +: value +: tail =>
nextOption( nextOption(
map ++ Map(shardokInterfaceRemoteAddressKey -> value), map ++ Map(shardokInterfaceRemoteAddressKey -> value),
tail tail
) )
case "--gpt-model-name" +: value +: tail => case "--gpt-model-name" +: value +: tail =>
nextOption( nextOption(
map ++ Map(gptModelNameKey -> value), map ++ Map(gptModelNameKey -> value),
tail tail
) )
case option +: _ => case option +: _ =>
throw new IllegalArgumentException("Unknown option " + option) throw new IllegalArgumentException("Unknown option " + option)
case _ => map case _ => map
} }
}
nextOption(Map(), args.toVector) nextOption(Map(), args.toVector)
} }
@@ -23,7 +23,7 @@ object RemoveActions {
val persister = new LocalFilePersister( val persister = new LocalFilePersister(
path + gameId.toHexString path + gameId.toHexString
) )
val history = val history =
PersistedHistory.gameHistoryFromPersister(gameId).get PersistedHistory.gameHistoryFromPersister(gameId).get
val found = history.recentHistory.indexWhere( val found = history.recentHistory.indexWhere(
@@ -53,8 +53,8 @@ object RemoveActions {
} }
def main(args: Array[String]): Unit = { def main(args: Array[String]): Unit = {
val path = args(0) val path = args(0)
val gameId = BigInt(args(1), 16).toLong val gameId = BigInt(args(1), 16).toLong
val countToTruncate = args(2).toInt val countToTruncate = args(2).toInt
truncate(path = path, gameId = gameId, countToTruncate = countToTruncate) truncate(path = path, gameId = gameId, countToTruncate = countToTruncate)
@@ -18,14 +18,13 @@ object MessageComparators {
acc: Vector[FieldDescriptor] = Vector() acc: Vector[FieldDescriptor] = Vector()
): Vector[FieldDescriptor] = fieldNames match { ): Vector[FieldDescriptor] = fieldNames match {
case name +: tail => case name +: tail =>
val field = { val field =
comp.scalaDescriptor.fields comp.scalaDescriptor.fields
.find(_.name == name) .find(_.name == name)
.getOrElse { .getOrElse {
println(s"No field $name in ${comp.scalaDescriptor.name}") println(s"No field $name in ${comp.scalaDescriptor.name}")
sys.exit(1) sys.exit(1)
} }
}
if tail.isEmpty then acc :+ field if tail.isEmpty then acc :+ field
else else
@@ -34,7 +33,7 @@ object MessageComparators {
tail, tail,
acc :+ field acc :+ field
) )
case _ => acc case _ => acc
} }
def nestedPValue( def nestedPValue(
@@ -50,7 +49,7 @@ object MessageComparators {
gm.getFieldByNumber(fd.number).asInstanceOf[GeneratedMessage], gm.getFieldByNumber(fd.number).asInstanceOf[GeneratedMessage],
tail tail
) )
case _ => gm.toPMessage case _ => gm.toPMessage
} }
def typedComparison[T]( def typedComparison[T](
@@ -59,19 +58,17 @@ object MessageComparators {
value: String value: String
): (GeneratedMessage => Boolean) = comparator match { ): (GeneratedMessage => Boolean) = comparator match {
case "=" => case "=" =>
( gm =>
gm => nestedPValue(gm, fieldDescriptors) match {
nestedPValue(gm, fieldDescriptors) match { case PInt(i) => i == value.toInt
case PInt(i) => i == value.toInt case PDouble(d) => d == value.toDouble
case PDouble(d) => d == value.toDouble case PString(s) => s == value
case PString(s) => s == value case PBoolean(b) => b == value.toBoolean
case PBoolean(b) => b == value.toBoolean case PEmpty => value.isEmpty
case PEmpty => value.isEmpty case x =>
case x => throw new NotImplementedError(s"$x pvalue not implemented")
throw new NotImplementedError(s"$x pvalue not implemented") }
} case x => throw new NotImplementedError(s"$x comparator not implemented")
)
case x => throw new NotImplementedError(s"$x comparator not implemented")
} }
} }
@@ -80,9 +77,9 @@ object GameStateFilters {
def filter(filter: String): ActionWithResultingState => Boolean = { def filter(filter: String): ActionWithResultingState => Boolean = {
val (left, rightWithComparator) = filter.span(!comparisonChars.contains(_)) val (left, rightWithComparator) = filter.span(!comparisonChars.contains(_))
val (comparator, right) = rightWithComparator.splitAt(1) val (comparator, right) = rightWithComparator.splitAt(1)
val fieldNames = left.split('.').toVector val fieldNames = left.split('.').toVector
val (prefix, remaining) = fieldNames.splitAt(1) val (prefix, remaining) = fieldNames.splitAt(1)
prefix.head match { prefix.head match {
case "gs" => case "gs" =>
@@ -121,11 +118,11 @@ object SavedGameUtils {
def filterOne( def filterOne(
results: Vector[ActionWithResultingState], results: Vector[ActionWithResultingState],
filter: String filter: String
): Vector[ActionWithResultingState] = { ): Vector[ActionWithResultingState] =
results.filter { case awrs => results.filter {
GameStateFilters.filter(filter)(awrs) case awrs =>
GameStateFilters.filter(filter)(awrs)
} }
}
def filteredResults( def filteredResults(
results: Vector[ActionWithResultingState], results: Vector[ActionWithResultingState],
@@ -141,9 +138,9 @@ object SavedGameUtils {
def fieldValues( def fieldValues(
results: Vector[ActionWithResultingState], results: Vector[ActionWithResultingState],
fields: Vector[String] fields: Vector[String]
): Vector[FieldWithValue] = { ): Vector[FieldWithValue] =
for { for {
result <- results result <- results
qualifiedFieldName <- fields qualifiedFieldName <- fields
} yield { } yield {
val (ident, subfieldNames) = qualifiedFieldName.split('.').splitAt(1) val (ident, subfieldNames) = qualifiedFieldName.split('.').splitAt(1)
@@ -172,7 +169,6 @@ object SavedGameUtils {
) )
} }
} }
}
def main(args: Array[String]): Unit = { def main(args: Array[String]): Unit = {
val argList = args.toList val argList = args.toList
@@ -207,8 +203,7 @@ object SavedGameUtils {
) )
def withoutFilterIndices(indices: Vector[Int]): FilteredResultsState = { def withoutFilterIndices(indices: Vector[Int]): FilteredResultsState = {
val newFilters = filters.zipWithIndex val newFilters = filters.zipWithIndex.filterNot { case (_, idx) => indices.contains(idx) }
.filterNot { case (_, idx) => indices.contains(idx) }
.map(_._1) .map(_._1)
this.copy( this.copy(
filters = newFilters, filters = newFilters,
@@ -218,8 +213,7 @@ object SavedGameUtils {
def withoutPrintIndices(indices: Vector[Int]): FilteredResultsState = def withoutPrintIndices(indices: Vector[Int]): FilteredResultsState =
this.copy( this.copy(
prints = prints.zipWithIndex prints = prints.zipWithIndex.filterNot { case (_, idx) => indices.contains(idx) }
.filterNot { case (_, idx) => indices.contains(idx) }
.map(_._1) .map(_._1)
) )
} }
@@ -228,12 +222,14 @@ object SavedGameUtils {
state: FilteredResultsState state: FilteredResultsState
): FilteredResultsState = { ): FilteredResultsState = {
println("Your filters:") println("Your filters:")
state.filters.zipWithIndex.foreach { case (filt, idx) => state.filters.zipWithIndex.foreach {
println(s" ($idx) $filt") case (filt, idx) =>
println(s" ($idx) $filt")
} }
println("Your prints:") println("Your prints:")
state.prints.zipWithIndex.foreach { case (pf, idx) => state.prints.zipWithIndex.foreach {
println(s" ($idx) $pf") case (pf, idx) =>
println(s" ($idx) $pf")
} }
println(s"${state.partialResults.size} filtered results") println(s"${state.partialResults.size} filtered results")
@@ -248,8 +244,8 @@ object SavedGameUtils {
state.withoutFilterIndices(idxStrings.map(_.toInt).toVector) state.withoutFilterIndices(idxStrings.map(_.toInt).toVector)
case "dp" :: idxStrings => case "dp" :: idxStrings =>
state.withoutPrintIndices(idxStrings.map(_.toInt).toVector) state.withoutPrintIndices(idxStrings.map(_.toInt).toVector)
case "q" :: _ => sys.exit(0) case "q" :: _ => sys.exit(0)
case x :: _ => case x :: _ =>
println(s"Unknown command $x, stopping") println(s"Unknown command $x, stopping")
state state
} }
@@ -18,8 +18,7 @@ object TestAIGame {
(1 to gameCount).foreach { _ => (1 to gameCount).foreach { _ =>
gamesManager.synchronizedCreateGame( gamesManager.synchronizedCreateGame(
expandedGameParameters = expandedGameParameters = GameParametersUtils.defaultExpandedGameParameters,
GameParametersUtils.defaultExpandedGameParameters,
gameId = Random.nextLong(), gameId = Random.nextLong(),
humanPlayers = Vector.empty, humanPlayers = Vector.empty,
aiPlayerCount = 5 aiPlayerCount = 5
@@ -118,7 +118,7 @@ case class AIClient(
gs: GameState, gs: GameState,
functionalRandom: FunctionalRandom functionalRandom: FunctionalRandom
): RandomState[AIClientWithSelectedCommand] = { ): RandomState[AIClientWithSelectedCommand] = {
val opac = acs.head._2 val opac = acs.head._2
val oneProvinceAcs = acs.head._2.commands val oneProvinceAcs = acs.head._2.commands
CommandChooser.choose( CommandChooser.choose(
@@ -215,7 +215,7 @@ case class AIClient(
) match { ) match {
case RandomState(Some(cs), fr) => case RandomState(Some(cs), fr) =>
RandomState(AIClientWithSelectedCommand(this, Some(cs)), fr) RandomState(AIClientWithSelectedCommand(this, Some(cs)), fr)
case RandomState(None, fr) => case RandomState(None, fr) =>
if EarlyGameAIClient.isEarlyGame(gs, factionId) then if EarlyGameAIClient.isEarlyGame(gs, factionId) then
EarlyGameAIClient EarlyGameAIClient
.chooseEarlyGameCommand( .chooseEarlyGameCommand(
@@ -14,11 +14,10 @@ object AIClientUtils {
gs gs
)).max(0) )).max(0)
def desiredHeroCount(pid: ProvinceId, fid: FactionId, gs: GameState): Int = { def desiredHeroCount(pid: ProvinceId, fid: FactionId, gs: GameState): Int =
if LegacyFactionUtils.hostileNeighbors(pid, fid, gs).nonEmpty then 3 if LegacyFactionUtils.hostileNeighbors(pid, fid, gs).nonEmpty then 3
else if gs.provinces(pid).support < 65 then 2 else if gs.provinces(pid).support < 65 then 2
else 1 else 1
}
def desiredCountForMarchTowardFocus( def desiredCountForMarchTowardFocus(
originProvince: Province, originProvince: Province,
@@ -55,8 +54,7 @@ object AIClientUtils {
originProvince = originProvince, originProvince = originProvince,
destinationProvince = destinationProvince, destinationProvince = destinationProvince,
availableHeroIds = availableHeroIds, availableHeroIds = availableHeroIds,
factionLeaders = factionLeaders = gs.factions(originProvince.rulingFactionId.get).leaders.toVector,
gs.factions(originProvince.rulingFactionId.get).leaders.toVector,
favorLeaders = favorLeaders favorLeaders = favorLeaders
), ),
gs, gs,
@@ -78,8 +76,7 @@ object AIClientUtils {
availableHeroes availableHeroes
.sortBy(hero => .sortBy(hero =>
( (
if favorLeaders then if favorLeaders then !LegacyFactionUtils.isFactionLeader(hero.id, gs)
!LegacyFactionUtils.isFactionLeader(hero.id, gs)
else LegacyFactionUtils.isFactionLeader(hero.id, gs), else LegacyFactionUtils.isFactionLeader(hero.id, gs),
-LegacyHeroUtils.power(hero) -LegacyHeroUtils.power(hero)
) )
@@ -26,7 +26,7 @@ object EarlyGameAIClient {
gs: GameState, gs: GameState,
acs: Vector[AvailableCommand], acs: Vector[AvailableCommand],
functionalRandom: FunctionalRandom functionalRandom: FunctionalRandom
): RandomState[CommandSelection] = { ): RandomState[CommandSelection] =
CommandChooser CommandChooser
.choose( .choose(
actingFactionId = actingFactionId, actingFactionId = actingFactionId,
@@ -40,5 +40,4 @@ object EarlyGameAIClient {
functionalRandom = functionalRandom functionalRandom = functionalRandom
) )
.map(_.get) .map(_.get)
}
} }
@@ -36,9 +36,7 @@ object FactionLeaderProvinceRanker {
factionId: FactionId, factionId: FactionId,
gameState: GameState gameState: GameState
): Ordering[Province] = Ordering ): Ordering[Province] = Ordering
.by((p: Province) => .by((p: Province) => LegacyFactionUtils.hostileNeighbors(p, factionId, gameState).size)
LegacyFactionUtils.hostileNeighbors(p, factionId, gameState).size
)
.reverse .reverse
// Orders by most-neutral-neighbors-first // Orders by most-neutral-neighbors-first
@@ -1,10 +1,7 @@
package net.eagle0.eagle.ai package net.eagle0.eagle.ai
import net.eagle0.common.{FunctionalRandom, RandomState} import net.eagle0.common.{FunctionalRandom, RandomState}
import net.eagle0.eagle.api.available_command.{ import net.eagle0.eagle.api.available_command.{AvailableCommand, MarchAvailableCommand}
AvailableCommand,
MarchAvailableCommand
}
import net.eagle0.eagle.api.selected_command.MarchSelectedCommand import net.eagle0.eagle.api.selected_command.MarchSelectedCommand
import net.eagle0.eagle.common.combat_unit.CombatUnit import net.eagle0.eagle.common.combat_unit.CombatUnit
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
@@ -23,53 +20,53 @@ object FixLeaderAloneCommandSelector {
randomSelectionForType[MarchAvailableCommand]( randomSelectionForType[MarchAvailableCommand](
availableCommands, availableCommands,
functionalRandom functionalRandom
) { case (ac, frOuter) => ) {
frOuter.nextFlatCollectFirst( case (ac, frOuter) =>
ac.oneProvinceCommands frOuter.nextFlatCollectFirst(
.filter(opmc => ac.oneProvinceCommands
gameState .filter(opmc =>
.provinces(opmc.originProvinceId) gameState
.rulingFactionHeroIds .provinces(opmc.originProvinceId)
.filterNot(LegacyFactionUtils.isFactionLeader(_, gameState)) .rulingFactionHeroIds
.size > 1 .filterNot(LegacyFactionUtils.isFactionLeader(_, gameState))
) .size > 1
) { opmc => fr => )
opmc.availableDestinationProvinces ) { opmc => fr =>
.map(dest => gameState.provinces(dest.provinceId)) opmc.availableDestinationProvinces
.filter(_.rulingFactionId.contains(actingFactionId)) .map(dest => gameState.provinces(dest.provinceId))
.filter(_.rulingFactionHeroIds.size == 1) .filter(_.rulingFactionId.contains(actingFactionId))
.filter(dest => .filter(_.rulingFactionHeroIds.size == 1)
LegacyFactionUtils .filter(dest =>
.isFactionLeader(dest.rulingFactionHeroIds.head, gameState) LegacyFactionUtils
) .isFactionLeader(dest.rulingFactionHeroIds.head, gameState)
.map { destination => )
fr.nextRandomElement(opmc.availableHeroIds) .map { destination =>
.map { hid => fr.nextRandomElement(opmc.availableHeroIds)
CombatUnit(factionId = actingFactionId, heroId = hid) .map { hid =>
} CombatUnit(factionId = actingFactionId, heroId = hid)
.map { combatUnit => }
Some( .map { combatUnit =>
CommandSelection( Some(
actingFactionId = actingFactionId, CommandSelection(
actingProvinceId = ac.actingProvinceId, actingFactionId = actingFactionId,
available = ac, actingProvinceId = ac.actingProvinceId,
selected = MarchSelectedCommand( available = ac,
gold = 0, selected = MarchSelectedCommand(
food = 0, gold = 0,
originProvince = opmc.originProvinceId, food = 0,
destinationProvinceId = destination.id, originProvince = opmc.originProvinceId,
marchingUnits = Vector( destinationProvinceId = destination.id,
combatUnit marchingUnits = Vector(
) combatUnit
), )
reason = ),
"Moving a hero so that a faction leader won't be alone" reason = "Moving a hero so that a faction leader won't be alone"
)
) )
) }
} }
} .headOption
.headOption .getOrElse(RandomState(None, fr))
.getOrElse(RandomState(None, fr)) }
}
} }
} }
@@ -1,17 +1,11 @@
package net.eagle0.eagle.ai package net.eagle0.eagle.ai
import net.eagle0.eagle.api.available_command.{ import net.eagle0.eagle.api.available_command.{AvailableCommand, DiplomacyAvailableCommand}
AvailableCommand,
DiplomacyAvailableCommand
}
import net.eagle0.eagle.api.command.util.diplomacy_option.InvitationOption import net.eagle0.eagle.api.command.util.diplomacy_option.InvitationOption
import net.eagle0.eagle.api.selected_command.DiplomacySelectedCommand import net.eagle0.eagle.api.selected_command.DiplomacySelectedCommand
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.library.settings.MinChanceForAIInvite import net.eagle0.eagle.library.settings.MinChanceForAIInvite
import net.eagle0.eagle.library.util.command_choice_helpers.{ import net.eagle0.eagle.library.util.command_choice_helpers.{ProvinceGoldSurplusCalculator, TrustForDiplomacy}
ProvinceGoldSurplusCalculator,
TrustForDiplomacy
}
import net.eagle0.eagle.library.util.command_choice_helpers.AvailableCommandSelector.flatSelectionForType import net.eagle0.eagle.library.util.command_choice_helpers.AvailableCommandSelector.flatSelectionForType
import net.eagle0.eagle.library.util.CommandSelection import net.eagle0.eagle.library.util.CommandSelection
import net.eagle0.eagle.FactionId import net.eagle0.eagle.FactionId
@@ -27,48 +21,41 @@ object InvitationCommandSelector {
gameState: GameState, gameState: GameState,
availableCommands: Vector[AvailableCommand] availableCommands: Vector[AvailableCommand]
): Option[CommandSelection] = ): Option[CommandSelection] =
flatSelectionForType[DiplomacyAvailableCommand](acs = availableCommands) { flatSelectionForType[DiplomacyAvailableCommand](acs = availableCommands) { diplomacyAvailableCommand =>
diplomacyAvailableCommand => diplomacyAvailableCommand.options.collect { case io: InvitationOption => io }.filter { invitationOption =>
diplomacyAvailableCommand.options TrustForDiplomacy.meetsConditionsForInvitation(
.collect { case io: InvitationOption => io } actingFactionId = actingFactionId,
.filter { invitationOption => targetFactionId = invitationOption.targetFactionId,
TrustForDiplomacy.meetsConditionsForInvitation( gameState = gameState
actingFactionId = actingFactionId, )
targetFactionId = invitationOption.targetFactionId, }.filter { invitationOption =>
gameState = gameState ProvinceGoldSurplusCalculator.provinceGoldSurplus(
) diplomacyAvailableCommand.actingProvinceId,
} gameState
.filter { invitationOption => ) >= invitationOption.goldCost
ProvinceGoldSurplusCalculator.provinceGoldSurplus( }.map { invitationOption =>
diplomacyAvailableCommand.actingProvinceId, InvitationOptionWithChance(
gameState invitationOption = invitationOption,
) >= invitationOption.goldCost acceptanceChance = ResolveDiplomacyCommandSelector.invitationAcceptanceChance(
} actingFactionId,
.map { invitationOption => invitationOption.targetFactionId,
InvitationOptionWithChance( gameState
invitationOption = invitationOption, )
acceptanceChance = )
ResolveDiplomacyCommandSelector.invitationAcceptanceChance( }.maxByOption(_.acceptanceChance)
actingFactionId, .filter(_.acceptanceChance >= MinChanceForAIInvite.intValue)
invitationOption.targetFactionId, .map { iowc =>
gameState CommandSelection(
) actingFactionId = actingFactionId,
) actingProvinceId = diplomacyAvailableCommand.actingProvinceId,
} available = diplomacyAvailableCommand,
.maxByOption { _.acceptanceChance } selected = DiplomacySelectedCommand(
.filter(_.acceptanceChance >= MinChanceForAIInvite.intValue) selectedOption = iowc.invitationOption,
.map { iowc => targetFactionId = iowc.invitationOption.targetFactionId,
CommandSelection( sentHeroId = diplomacyAvailableCommand.recommendedHeroId
actingFactionId = actingFactionId, ),
actingProvinceId = diplomacyAvailableCommand.actingProvinceId, reason = "maybeInviteOtherFaction"
available = diplomacyAvailableCommand, )
selected = DiplomacySelectedCommand( }
selectedOption = iowc.invitationOption,
targetFactionId = iowc.invitationOption.targetFactionId,
sentHeroId = diplomacyAvailableCommand.recommendedHeroId
),
reason = "maybeInviteOtherFaction"
)
}
} }
} }
@@ -1,18 +1,11 @@
package net.eagle0.eagle.ai package net.eagle0.eagle.ai
import net.eagle0.eagle.{FactionId, ProvinceId} import net.eagle0.eagle.{FactionId, ProvinceId}
import net.eagle0.eagle.api.available_command.{ import net.eagle0.eagle.api.available_command.{MarchAvailableCommand, MarchCommandFromOneProvince}
MarchAvailableCommand,
MarchCommandFromOneProvince
}
import net.eagle0.eagle.api.selected_command.MarchSelectedCommand import net.eagle0.eagle.api.selected_command.MarchSelectedCommand
import net.eagle0.eagle.common.combat_unit.CombatUnit import net.eagle0.eagle.common.combat_unit.CombatUnit
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.library.util.{ import net.eagle0.eagle.library.util.{CommandSelection, LegacyBattalionUtils, ProvinceDistances}
CommandSelection,
LegacyBattalionUtils,
ProvinceDistances
}
import net.eagle0.eagle.library.util.command_choice_helpers.ProvinceGoldSurplusCalculator.provinceGoldSurplus import net.eagle0.eagle.library.util.command_choice_helpers.ProvinceGoldSurplusCalculator.provinceGoldSurplus
import net.eagle0.eagle.library.util.EagleRequire.internalRequire import net.eagle0.eagle.library.util.EagleRequire.internalRequire
@@ -28,79 +21,80 @@ object MarchTowardProvinceCommandChooser {
val bestCommand = val bestCommand =
opmc.availableDestinationProvinces opmc.availableDestinationProvinces
.map(dest => (opmc, gs.provinces(dest.provinceId))) .map(dest => (opmc, gs.provinces(dest.provinceId)))
.flatMap { case (opmc, p) => .flatMap {
ProvinceDistances case (opmc, p) =>
.distanceThroughFriendliesOption( ProvinceDistances
destinationProvinceId, .distanceThroughFriendliesOption(
p.id, destinationProvinceId,
fid, p.id,
gs fid,
) gs
.map(distance => (opmc, p, distance)) )
.map(distance => (opmc, p, distance))
} }
.minByOption(_._3) .minByOption(_._3)
val actingProvinceId = ac.actingProvinceId val actingProvinceId = ac.actingProvinceId
bestCommand.flatMap { case (opmc, dest, _) => bestCommand.flatMap {
internalRequire( case (opmc, dest, _) =>
opmc.originProvinceId != destinationProvinceId, internalRequire(
s"Marching away from $destinationProvinceId in an effort to get to that province" opmc.originProvinceId != destinationProvinceId,
) s"Marching away from $destinationProvinceId in an effort to get to that province"
val originProvince = gs.provinces(opmc.originProvinceId)
val takenHeroIds =
AIClientUtils.takenHeroIdsForMarchTowardFocus(
originProvince = originProvince,
destinationProvince = dest,
availableHeroIds = opmc.availableHeroIds.toVector,
favorLeaders = favorLeaders,
gs = gs
) )
Option.when(takenHeroIds.nonEmpty) { val originProvince = gs.provinces(opmc.originProvinceId)
val takenBattalions =
if originProvince.battalionIds.size <= takenHeroIds.size then
originProvince.battalionIds.map(gs.battalions)
else
originProvince.battalionIds
.map(gs.battalions)
.sortBy(_.size)
.take(takenHeroIds.size)
val takenUnits = takenHeroIds val takenHeroIds =
.zipAll( AIClientUtils.takenHeroIdsForMarchTowardFocus(
takenBattalions.map(batt => Some(batt.id)), originProvince = originProvince,
0, destinationProvince = dest,
None availableHeroIds = opmc.availableHeroIds.toVector,
favorLeaders = favorLeaders,
gs = gs
) )
.map { case (hid, bid) =>
CombatUnit(factionId = fid, heroId = hid, battalionId = bid)
}
val foodToTake = 2 * LegacyBattalionUtils.monthlyConsumedFood( Option.when(takenHeroIds.nonEmpty) {
takenBattalions, val takenBattalions =
gs.battalionTypes.toVector if originProvince.battalionIds.size <= takenHeroIds.size then originProvince.battalionIds.map(gs.battalions)
) else
val goldToTake = provinceGoldSurplus(originProvince.id, gs) originProvince.battalionIds
CommandSelection( .map(gs.battalions)
actingFactionId = fid, .sortBy(_.size)
actingProvinceId = actingProvinceId, .take(takenHeroIds.size)
available = ac,
selected = MarchSelectedCommand( val takenUnits = takenHeroIds
marchingUnits = takenUnits, // Check the food cost here? .zipAll(
destinationProvinceId = dest.id, takenBattalions.map(batt => Some(batt.id)),
originProvince = opmc.originProvinceId, 0,
gold = Math.min(goldToTake, opmc.goldAvailable), None
food = Math.min(foodToTake, opmc.foodAvailable) )
), .map {
reason = if favorLeaders then case (hid, bid) =>
s"marching leaders toward ${gs.provinces(destinationProvinceId).name}" CombatUnit(factionId = fid, heroId = hid, battalionId = bid)
else }
s"marching heroes toward ${gs.provinces(destinationProvinceId).name}"
) val foodToTake = 2 * LegacyBattalionUtils.monthlyConsumedFood(
} takenBattalions,
gs.battalionTypes.toVector
)
val goldToTake = provinceGoldSurplus(originProvince.id, gs)
CommandSelection(
actingFactionId = fid,
actingProvinceId = actingProvinceId,
available = ac,
selected = MarchSelectedCommand(
marchingUnits = takenUnits, // Check the food cost here?
destinationProvinceId = dest.id,
originProvince = opmc.originProvinceId,
gold = Math.min(goldToTake, opmc.goldAvailable),
food = Math.min(foodToTake, opmc.foodAvailable)
),
reason =
if favorLeaders then s"marching leaders toward ${gs.provinces(destinationProvinceId).name}"
else s"marching heroes toward ${gs.provinces(destinationProvinceId).name}"
)
}
} }
} }
} }
@@ -5,19 +5,13 @@ import net.eagle0.eagle.{FactionId, ProvinceId}
import net.eagle0.eagle.ai.AIClientUtils.extraHeroCount import net.eagle0.eagle.ai.AIClientUtils.extraHeroCount
import net.eagle0.eagle.api.available_command.* import net.eagle0.eagle.api.available_command.*
import net.eagle0.eagle.api.command.OneProvinceAvailableCommands import net.eagle0.eagle.api.command.OneProvinceAvailableCommands
import net.eagle0.eagle.api.selected_command.{ import net.eagle0.eagle.api.selected_command.{MarchSelectedCommand, ReconSelectedCommand}
MarchSelectedCommand,
ReconSelectedCommand
}
import net.eagle0.eagle.common.combat_unit.CombatUnit import net.eagle0.eagle.common.combat_unit.CombatUnit
import net.eagle0.eagle.common.improvement_type.ImprovementType import net.eagle0.eagle.common.improvement_type.ImprovementType
import net.eagle0.eagle.internal.faction_relationship.FactionRelationship import net.eagle0.eagle.internal.faction_relationship.FactionRelationship
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.province.Province import net.eagle0.eagle.internal.province.Province
import net.eagle0.eagle.library.settings.{ import net.eagle0.eagle.library.settings.{MinSupportForTaxes, MonthsReconConsideredRecent}
MinSupportForTaxes,
MonthsReconConsideredRecent
}
import net.eagle0.eagle.library.util.* import net.eagle0.eagle.library.util.*
import net.eagle0.eagle.library.util.command_choice_helpers.{ import net.eagle0.eagle.library.util.command_choice_helpers.{
AlmsCommandSelector, AlmsCommandSelector,
@@ -141,20 +135,20 @@ object MidGameAIClient {
_ /* availableHeroIds */, _ /* availableHeroIds */,
_ /* unknownFieldSet */ _ /* unknownFieldSet */
) => ) =>
val actingProvince = gameState.provinces(actingProvinceId) val actingProvince = gameState.provinces(actingProvinceId)
val foodMonthsToHoldBack = val foodMonthsToHoldBack =
FoodConsumptionUtils.foodConsumptionMonthsToHold( FoodConsumptionUtils.foodConsumptionMonthsToHold(
actingProvinceId, actingProvinceId,
gameState gameState
) )
val foodToHoldBack = val foodToHoldBack =
foodMonthsToHoldBack * LegacyProvinceUtils.monthlyFoodConsumption( foodMonthsToHoldBack * LegacyProvinceUtils.monthlyFoodConsumption(
actingProvinceId, actingProvinceId,
gameState gameState
) )
val maxFoodToSend = val maxFoodToSend =
Math.min(actingProvince.food - foodToHoldBack, foodAvailable) Math.min(actingProvince.food - foodToHoldBack, foodAvailable)
val maxGoldToSend = Math.min( val maxGoldToSend = Math.min(
ProvinceGoldSurplusCalculator ProvinceGoldSurplusCalculator
.provinceGoldSurplus(actingProvinceId, gameState), .provinceGoldSurplus(actingProvinceId, gameState),
goldAvailable goldAvailable
@@ -172,12 +166,12 @@ object MidGameAIClient {
.map(gameState.provinces) .map(gameState.provinces)
.filter(_.rulingFactionId.contains(factionId)) .filter(_.rulingFactionId.contains(factionId))
.map { province => .map { province =>
val consumption = LegacyProvinceUtils val consumption = LegacyProvinceUtils
.monthlyFoodConsumption(province.id, gameState) .monthlyFoodConsumption(province.id, gameState)
val incomingFood = val incomingFood =
province.incomingShipments.flatMap(_.supplies).map(_.food).sum province.incomingShipments.flatMap(_.supplies).map(_.food).sum
val availableFood = province.food + incomingFood val availableFood = province.food + incomingFood
val monthsOfFood = val monthsOfFood =
if consumption == 0 then 120 if consumption == 0 then 120
else availableFood / consumption else availableFood / consumption
DestinationFoodStatus( DestinationFoodStatus(
@@ -189,29 +183,28 @@ object MidGameAIClient {
.filter(_.monthsOfFood < 4) .filter(_.monthsOfFood < 4)
.minByOption(_.monthsOfFood) .minByOption(_.monthsOfFood)
bestDestination bestDestination.map { destinationFoodStatus =>
.map { destinationFoodStatus => val destinationProvince = destinationFoodStatus.province
val destinationProvince = destinationFoodStatus.province val desiredFood = LegacyProvinceUtils.monthlyFoodConsumption(
val desiredFood = LegacyProvinceUtils.monthlyFoodConsumption( destinationProvince.id,
destinationProvince.id, gameState
gameState ) * 6 - destinationProvince.food
) * 6 - destinationProvince.food val foodToSend =
val foodToSend = if desiredFood > maxFoodToSend then maxFoodToSend
if desiredFood > maxFoodToSend then maxFoodToSend else (desiredFood + maxFoodToSend) / 2
else (desiredFood + maxFoodToSend) / 2
CommandChoiceHelpers CommandChoiceHelpers
.chosenSendSuppliesCommandWithSuppliesAndDestination( .chosenSendSuppliesCommandWithSuppliesAndDestination(
actingFactionId = factionId, actingFactionId = factionId,
gameState = gameState, gameState = gameState,
ac = available, ac = available,
supplies = Supplies( supplies = Supplies(
gold = maxGoldToSend / 2, gold = maxGoldToSend / 2,
food = foodToSend food = foodToSend
), ),
destination = destinationFoodStatus.province.id destination = destinationFoodStatus.province.id
) )
} }
} }
} }
.map(_.withReason("relay supplies")) .map(_.withReason("relay supplies"))
@@ -335,71 +328,70 @@ object MidGameAIClient {
availableCommands: Vector[AvailableCommand], availableCommands: Vector[AvailableCommand],
functionalRandom: FunctionalRandom functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] = ): RandomState[Option[CommandSelection]] =
provincesWithFactionLeader(actingFactionId, gameState) provincesWithFactionLeader(actingFactionId, gameState).map { province =>
.map { province => MoreOption
MoreOption .flatWhen(province.rulingFactionHeroIds.size < 4)(
.flatWhen(province.rulingFactionHeroIds.size < 4)( maybeMoveHeroesTowardFactionLeaderCommand(
maybeMoveHeroesTowardFactionLeaderCommand( actingFactionId = actingFactionId,
actingFactionId = actingFactionId, gs = gameState,
gs = gameState, acs = availableCommands
acs = availableCommands )
) ) match {
) match { case Some(cs) => RandomState(Some(cs), functionalRandom)
case Some(cs) => RandomState(Some(cs), functionalRandom) case None =>
case None => chosenGenericCommandFromRankedOptions(
chosenGenericCommandFromRankedOptions( actingFactionId = actingFactionId,
actingFactionId = actingFactionId, gs = gameState,
gs = gameState, acs = availableCommands,
acs = availableCommands, choosersAndReasons = Vector(
choosersAndReasons = Vector( (
( ExpandCommandSelector.maybeChosenExpandCommand,
ExpandCommandSelector.maybeChosenExpandCommand, "no hostile neighbors, expand"
"no hostile neighbors, expand"
),
(
maybeMoveToRecruitCommand,
"no hostile neighbors, travel to recruit"
),
(
(
actingFactionId,
gameState,
availableCommands,
functionalRandom
) =>
RandomState(
ImproveCommandSelector.chosenImproveCommand(
actingFactionId,
gameState,
availableCommands
),
functionalRandom
),
"chosen neutral neighbors: Improve b/c nothing better"
),
(
(
actingFactionId,
gameState,
availableCommands,
functionalRandom
) =>
RandomState(
chosenRestCommand(
actingFactionId,
gameState,
availableCommands,
"chosen neutral neighbors: rest"
),
functionalRandom
),
"chosen neutral neighbors: rest"
)
), ),
functionalRandom = functionalRandom (
) maybeMoveToRecruitCommand,
} "no hostile neighbors, travel to recruit"
),
(
(
actingFactionId,
gameState,
availableCommands,
functionalRandom
) =>
RandomState(
ImproveCommandSelector.chosenImproveCommand(
actingFactionId,
gameState,
availableCommands
),
functionalRandom
),
"chosen neutral neighbors: Improve b/c nothing better"
),
(
(
actingFactionId,
gameState,
availableCommands,
functionalRandom
) =>
RandomState(
chosenRestCommand(
actingFactionId,
gameState,
availableCommands,
"chosen neutral neighbors: rest"
),
functionalRandom
),
"chosen neutral neighbors: rest"
)
),
functionalRandom = functionalRandom
)
} }
}
.find(_.newValue.isDefined) .find(_.newValue.isDefined)
.get .get
@@ -410,85 +402,84 @@ object MidGameAIClient {
functionalRandom: FunctionalRandom functionalRandom: FunctionalRandom
): RandomState[Option[CommandSelection]] = { ): RandomState[Option[CommandSelection]] = {
import net.eagle0.eagle.library.util.command_choice_helpers.CommandChooserImplicits.DeterministicCommandChooser import net.eagle0.eagle.library.util.command_choice_helpers.CommandChooserImplicits.DeterministicCommandChooser
provincesWithFactionLeader(actingFactionId, gameState) provincesWithFactionLeader(actingFactionId, gameState).map { province =>
.map { province => MoreOption
MoreOption .flatWhen(province.rulingFactionHeroIds.size < 4)(
.flatWhen(province.rulingFactionHeroIds.size < 4)( maybeMoveHeroesTowardFactionLeaderCommand(
maybeMoveHeroesTowardFactionLeaderCommand( actingFactionId = actingFactionId,
actingFactionId = actingFactionId, gs = gameState,
gs = gameState, acs = availableCommands
acs = availableCommands )
) ) match {
) match { case Some(cs) => RandomState(Some(cs), functionalRandom)
case Some(cs) => RandomState(Some(cs), functionalRandom) case None =>
case None => chosenGenericCommandFromRankedOptions(
chosenGenericCommandFromRankedOptions( actingFactionId = actingFactionId,
actingFactionId = actingFactionId, gs = gameState,
gs = gameState, acs = availableCommands,
acs = availableCommands, choosersAndReasons = Vector(
choosersAndReasons = Vector( (
maybeMoveToRecruitCommand,
"chosen no neutral neighbors, travel to recruit"
),
(
( (
maybeMoveToRecruitCommand, actingFactionId,
"chosen no neutral neighbors, travel to recruit" gameState,
), availableCommands,
( functionalRandom
( ) =>
actingFactionId, RandomState(
gameState, MoveLeaderToBetterProvinceCommandChooser
availableCommands, .maybeMoveLeaderToBetterProvinceCommand(
functionalRandom
) =>
RandomState(
MoveLeaderToBetterProvinceCommandChooser
.maybeMoveLeaderToBetterProvinceCommand(
actingFactionId,
gameState,
availableCommands
),
functionalRandom
),
"chosen no neutral neighbors, travel to front"
),
(
(
actingFactionId,
gameState,
availableCommands,
functionalRandom
) =>
RandomState(
ImproveCommandSelector.chosenImproveCommand(
actingFactionId, actingFactionId,
gameState, gameState,
availableCommands availableCommands
), ),
functionalRandom functionalRandom
), ),
"chosen no neutral neighbors: Improve b/c nothing better" "chosen no neutral neighbors, travel to front"
), ),
(
( (
( actingFactionId,
gameState,
availableCommands,
functionalRandom
) =>
RandomState(
ImproveCommandSelector.chosenImproveCommand(
actingFactionId,
gameState,
availableCommands
),
functionalRandom
),
"chosen no neutral neighbors: Improve b/c nothing better"
),
(
(
actingFactionId,
gameState,
availableCommands,
functionalRandom
) =>
RandomState(
chosenRestCommand(
actingFactionId, actingFactionId,
gameState, gameState,
availableCommands, availableCommands,
functionalRandom "chosen no neutral neighbors: rest"
) =>
RandomState(
chosenRestCommand(
actingFactionId,
gameState,
availableCommands,
"chosen no neutral neighbors: rest"
),
functionalRandom
), ),
"chosen neutral neighbors: rest" functionalRandom
) ),
), "chosen neutral neighbors: rest"
functionalRandom = functionalRandom )
) ),
} functionalRandom = functionalRandom
)
} }
}
.find(_.newValue.isDefined) .find(_.newValue.isDefined)
.get .get
} }
@@ -503,11 +494,11 @@ object MidGameAIClient {
.reconnedProvinces .reconnedProvinces
.find(_.id == provinceId) .find(_.id == provinceId)
.flatMap(_.fullInfo) .flatMap(_.fullInfo)
def reconnedHeroCountFor(provinceId: ProvinceId): Int = def reconnedHeroCountFor(provinceId: ProvinceId): Int =
reconnedFullInfoFor(provinceId) reconnedFullInfoFor(provinceId)
.map(_.rulingFactionHeroIds.size) .map(_.rulingFactionHeroIds.size)
.getOrElse(0) .getOrElse(0)
def reconnedBattalionsFor(provinceId: ProvinceId): Vector[BattalionView] = def reconnedBattalionsFor(provinceId: ProvinceId): Vector[BattalionView] =
reconnedFullInfoFor(provinceId) reconnedFullInfoFor(provinceId)
.map(_.battalions.toVector) .map(_.battalions.toVector)
.getOrElse(Vector()) .getOrElse(Vector())
@@ -673,8 +664,7 @@ object MidGameAIClient {
} }
} }
), ),
(fid, gs, acs, fr) => (fid, gs, acs, fr) => chosenNoNeutralNeighborsCommand(fid, gs, acs, fr)
chosenNoNeutralNeighborsCommand(fid, gs, acs, fr)
), ),
functionalRandom = functionalRandom functionalRandom = functionalRandom
) )
@@ -696,7 +686,7 @@ object MidGameAIClient {
factionId: FactionId factionId: FactionId
): Option[CommandSelection] = ): Option[CommandSelection] =
flatSelectionForType[ImproveAvailableCommand](acs) { availableCommand => flatSelectionForType[ImproveAvailableCommand](acs) { availableCommand =>
val province = gs.provinces(availableCommand.actingProvinceId) val province = gs.provinces(availableCommand.actingProvinceId)
val existingBattalions = province.battalionIds val existingBattalions = province.battalionIds
.map(gs.battalions) .map(gs.battalions)
@@ -725,7 +715,7 @@ object MidGameAIClient {
.map(_.minimumAgriculture - province.agriculture) .map(_.minimumAgriculture - province.agriculture)
.filter(_ > 0) .filter(_ > 0)
.min .min
val neededEconomy = notAvailable val neededEconomy = notAvailable
.map(_.minimumEconomy - province.economy) .map(_.minimumEconomy - province.economy)
.filter(_ > 0) .filter(_ > 0)
.min .min
@@ -870,7 +860,7 @@ object MidGameAIClient {
case (None, fr2) => go(RandomState(tail, fr2)) case (None, fr2) => go(RandomState(tail, fr2))
} }
} }
case (_, fr) => RandomState(None, fr) case (_, fr) => RandomState(None, fr)
} }
go( go(
@@ -889,35 +879,34 @@ object MidGameAIClient {
val factionLeaderProvincesNeedingHeroes = val factionLeaderProvincesNeedingHeroes =
provincesWithFactionLeader(actingFactionId, gs) provincesWithFactionLeader(actingFactionId, gs)
.filter(p => p.rulingFactionHeroIds.size < p.heroCap) .filter(p => p.rulingFactionHeroIds.size < p.heroCap)
val cs = for { val cs = for {
(marchCommand, idx) <- acs.zipWithIndex.collectFirst { (marchCommand, idx) <- acs.zipWithIndex.collectFirst {
case (ac: MarchAvailableCommand, idx) => (ac, idx) case (ac: MarchAvailableCommand, idx) => (ac, idx)
} }
} yield { } yield {
// Find the march command origin provinces that contain extra heroes // Find the march command origin provinces that contain extra heroes
val candidateCommands = marchCommand.oneProvinceCommands.filter { opmc => val candidateCommands = marchCommand.oneProvinceCommands.filter { opmc =>
extraHeroCount(opmc.originProvinceId, actingFactionId, gs) > 0 extraHeroCount(opmc.originProvinceId, actingFactionId, gs) > 0
} }
val furthestOrigin = candidateCommands val furthestOrigin = candidateCommands.flatMap { opmc =>
.flatMap { opmc => // distance to the faction leader province we're closest to
// distance to the faction leader province we're closest to factionLeaderProvincesNeedingHeroes.flatMap { flp =>
factionLeaderProvincesNeedingHeroes ProvinceDistances
.flatMap { flp => .distanceThroughFriendliesOption(
ProvinceDistances p1 = flp.id,
.distanceThroughFriendliesOption( p2 = opmc.originProvinceId,
p1 = flp.id, fid = actingFactionId,
p2 = opmc.originProvinceId, gs = gs
fid = actingFactionId, )
gs = gs .map {
) (_: Int, flp.id: ProvinceId)
.map {
(_: Int, flp.id: ProvinceId)
}
} }
.minByOption(_._1) }
.filterNot(_._1 == 0) // If distance is zero, we'd be moving away from the province we're trying to get to .minByOption(_._1)
.map { case (distance: Int, destinationProvinceId: ProvinceId) => .filterNot(_._1 == 0) // If distance is zero, we'd be moving away from the province we're trying to get to
.map {
case (distance: Int, destinationProvinceId: ProvinceId) =>
( (
// distance to the faction leader province we're closest to // distance to the faction leader province we're closest to
distance, distance,
@@ -925,21 +914,22 @@ object MidGameAIClient {
extraHeroCount(opmc.originProvinceId, actingFactionId, gs), extraHeroCount(opmc.originProvinceId, actingFactionId, gs),
opmc opmc
) )
} }
} }
.maxByOption(tup => (tup._1, tup._3)) .maxByOption(tup => (tup._1, tup._3))
.map { tup => (tup._2, tup._4) } .map(tup => (tup._2, tup._4))
furthestOrigin.flatMap { case (destinationProvinceId, opmc) => furthestOrigin.flatMap {
MarchTowardProvinceCommandChooser case (destinationProvinceId, opmc) =>
.marchTowardProvinceCommand( MarchTowardProvinceCommandChooser
ac = marchCommand, .marchTowardProvinceCommand(
fid = actingFactionId, ac = marchCommand,
opmc = opmc, fid = actingFactionId,
destinationProvinceId = destinationProvinceId, opmc = opmc,
gs = gs, destinationProvinceId = destinationProvinceId,
favorLeaders = false gs = gs,
) favorLeaders = false
)
} }
} }
@@ -957,8 +947,7 @@ object MidGameAIClient {
actingProvinceId = ac.actingProvinceId, actingProvinceId = ac.actingProvinceId,
available = ac, available = ac,
selected = ReconSelectedCommand( selected = ReconSelectedCommand(
actingHeroId = actingHeroId = ac.availableHeroIds.maxBy(hid => gameState.heroes(hid).vigor),
ac.availableHeroIds.maxBy(hid => gameState.heroes(hid).vigor),
targetProvinceId = targetProvinceId targetProvinceId = targetProvinceId
), ),
reason = "selectedReconCommand" reason = "selectedReconCommand"
@@ -1046,11 +1035,11 @@ object MidGameAIClient {
gameState: GameState, gameState: GameState,
acs: Vector[AvailableCommand] acs: Vector[AvailableCommand]
): Option[CommandSelection] = { ): Option[CommandSelection] = {
val faction = gameState.factions(actingFactionId) val faction = gameState.factions(actingFactionId)
val factionLeaders = faction.leaders val factionLeaders = faction.leaders
flatSelectionForType[MarchAvailableCommand](acs) { marchAvailableCommand => flatSelectionForType[MarchAvailableCommand](acs) { marchAvailableCommand =>
marchAvailableCommand.oneProvinceCommands.flatMap { opmc => marchAvailableCommand.oneProvinceCommands.flatMap { opmc =>
val originProvince = gameState.provinces(opmc.originProvinceId) val originProvince = gameState.provinces(opmc.originProvinceId)
val leadersInOriginProvince = val leadersInOriginProvince =
originProvince.rulingFactionHeroIds.intersect(factionLeaders) originProvince.rulingFactionHeroIds.intersect(factionLeaders)
MoreOption.flatWhen( MoreOption.flatWhen(
@@ -1058,7 +1047,7 @@ object MidGameAIClient {
.intersect(factionLeaders) .intersect(factionLeaders)
.nonEmpty .nonEmpty
) { ) {
val leaderIdToMove = val leaderIdToMove =
opmc.availableHeroIds opmc.availableHeroIds
.intersect(factionLeaders) .intersect(factionLeaders)
.maxBy(hid => LegacyHeroUtils.seniorityOrder(faction, hid)) .maxBy(hid => LegacyHeroUtils.seniorityOrder(faction, hid))
@@ -1074,27 +1063,26 @@ object MidGameAIClient {
IncomingArmyUtils.isUnderAttack(province, gameState) IncomingArmyUtils.isUnderAttack(province, gameState)
) // don't send a leader into a province under attack ) // don't send a leader into a province under attack
validDestinations.minByOption(_.rulingFactionHeroIds.size).map { validDestinations.minByOption(_.rulingFactionHeroIds.size).map { destinationProvince =>
destinationProvince => CommandSelection(
CommandSelection( actingFactionId = actingFactionId,
actingFactionId = actingFactionId, actingProvinceId = marchAvailableCommand.actingProvinceId,
actingProvinceId = marchAvailableCommand.actingProvinceId, available = marchAvailableCommand,
available = marchAvailableCommand, selected = MarchSelectedCommand(
selected = MarchSelectedCommand( gold = 0,
gold = 0, food = 0,
food = 0, originProvince = originProvince.id,
originProvince = originProvince.id, destinationProvinceId = destinationProvince.id,
destinationProvinceId = destinationProvince.id, marchingUnits = Vector(
marchingUnits = Vector( CombatUnit(
CombatUnit( factionId = actingFactionId,
factionId = actingFactionId, heroId = leaderIdToMove,
heroId = leaderIdToMove, battalionId = None
battalionId = None
)
) )
), )
reason = "Spreading out the faction leaders" ),
) reason = "Spreading out the faction leaders"
)
} }
} }
}.headOption }.headOption
@@ -1,11 +1,7 @@
package net.eagle0.eagle.ai package net.eagle0.eagle.ai
import net.eagle0.eagle.{FactionId, ProvinceId} import net.eagle0.eagle.{FactionId, ProvinceId}
import net.eagle0.eagle.api.available_command.{ import net.eagle0.eagle.api.available_command.{AvailableCommand, MarchAvailableCommand, MarchCommandFromOneProvince}
AvailableCommand,
MarchAvailableCommand,
MarchCommandFromOneProvince
}
import net.eagle0.eagle.internal.faction.Faction import net.eagle0.eagle.internal.faction.Faction
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.library.util.CommandSelection import net.eagle0.eagle.library.util.CommandSelection
@@ -30,8 +26,7 @@ object MoveLeaderToBetterProvinceCommandChooser {
actingFactionId = actingFactionId, actingFactionId = actingFactionId,
gs = gs, gs = gs,
acs = acs, acs = acs,
betterDestinationChooser = betterDestinationChooser = FactionLeaderProvinceRanker.bestSettlementPidForFactionLeader
FactionLeaderProvinceRanker.bestSettlementPidForFactionLeader
) )
private def candidatesForMarchCommand( private def candidatesForMarchCommand(
@@ -57,22 +52,21 @@ object MoveLeaderToBetterProvinceCommandChooser {
actingFactionId, actingFactionId,
mcfop.originProvinceId, mcfop.originProvinceId,
gs gs
) ).flatMap { desiredPid =>
.flatMap { desiredPid => ProvinceDistances
ProvinceDistances .closestProvinceThroughFriendliesOption(
.closestProvinceThroughFriendliesOption( desiredPid,
desiredPid, mcfop.availableDestinationProvinces.map(_.provinceId).toVector,
mcfop.availableDestinationProvinces.map(_.provinceId).toVector, actingFactionId,
actingFactionId, gs
gs )
.map { provinceAndDistance =>
MCFOPWithDestinationAndDistance(
mcfop = mcfop,
provinceAndDistance = provinceAndDistance
) )
.map { provinceAndDistance => }
MCFOPWithDestinationAndDistance( }
mcfop = mcfop,
provinceAndDistance = provinceAndDistance
)
}
}
} }
def maybeMoveLeaderToBetterProvinceCommandWithChooser( def maybeMoveLeaderToBetterProvinceCommandWithChooser(
@@ -82,15 +76,14 @@ object MoveLeaderToBetterProvinceCommandChooser {
betterDestinationChooser: DestinationChooser betterDestinationChooser: DestinationChooser
): Option[CommandSelection] = { ): Option[CommandSelection] = {
val cs = for { val cs = for {
faction <- gs.factions.get(actingFactionId) faction <- gs.factions.get(actingFactionId)
marchCommand <- acs.collectFirst { case ac: MarchAvailableCommand => ac } marchCommand <- acs.collectFirst { case ac: MarchAvailableCommand => ac }
} yield { } yield {
// Find the march command origin provinces that contain a faction leader // Find the march command origin provinces that contain a faction leader
val candidatesWithDistances = candidateCommandsWithDistances( val candidatesWithDistances = candidateCommandsWithDistances(
actingFactionId = actingFactionId, actingFactionId = actingFactionId,
gs = gs, gs = gs,
candidateCommands = candidateCommands = candidatesForMarchCommand(marchCommand, gs, faction),
candidatesForMarchCommand(marchCommand, gs, faction),
betterDestinationChooser = betterDestinationChooser betterDestinationChooser = betterDestinationChooser
) )
@@ -77,23 +77,24 @@ object ResolveDiplomacyCommandSelector {
randomSelectionForType[ResolveInvitationAvailableCommand]( randomSelectionForType[ResolveInvitationAvailableCommand](
availableCommands, availableCommands,
functionalRandom functionalRandom
) { case (resolveInvitationAc, fr) => ) {
selectionForResolveInvitationCommand( case (resolveInvitationAc, fr) =>
actingFactionId = actingFactionId, selectionForResolveInvitationCommand(
ac = resolveInvitationAc, actingFactionId = actingFactionId,
gs = gameState, ac = resolveInvitationAc,
functionalRandom = fr gs = gameState,
).map { resolveInviteSelection => functionalRandom = fr
Some( ).map { resolveInviteSelection =>
CommandSelection( Some(
actingFactionId = actingFactionId, CommandSelection(
actingProvinceId = 0, actingFactionId = actingFactionId,
available = resolveInvitationAc, actingProvinceId = 0,
selected = resolveInviteSelection, available = resolveInvitationAc,
reason = "resolving invitation" selected = resolveInviteSelection,
reason = "resolving invitation"
)
) )
) }
}
} }
private def selectionForResolveInvitationCommand( private def selectionForResolveInvitationCommand(
@@ -102,11 +103,9 @@ object ResolveDiplomacyCommandSelector {
gs: GameState, gs: GameState,
functionalRandom: FunctionalRandom functionalRandom: FunctionalRandom
): RandomState[SelectedCommand] = functionalRandom.nextDouble.map { roll => ): RandomState[SelectedCommand] = functionalRandom.nextDouble.map { roll =>
val invitedFid = actingFactionId val invitedFid = actingFactionId
val bestInvitation = ac.invitations val bestInvitation = ac.invitations
.maxBy(inv => .maxBy(inv => invitationAcceptanceChance(inv.originatingFactionId, invitedFid, gs))
invitationAcceptanceChance(inv.originatingFactionId, invitedFid, gs)
)
internalRequire( internalRequire(
bestInvitation.eligibleStatuses.contains(DIPLOMACY_OFFER_STATUS_ACCEPTED), bestInvitation.eligibleStatuses.contains(DIPLOMACY_OFFER_STATUS_ACCEPTED),
@@ -150,23 +149,24 @@ object ResolveDiplomacyCommandSelector {
randomSelectionForType[ResolveTruceOfferAvailableCommand]( randomSelectionForType[ResolveTruceOfferAvailableCommand](
availableCommands, availableCommands,
functionalRandom functionalRandom
) { case (resolveTruceAc, fr) => ) {
selectionForResolveTruceOfferSelectedCommand( case (resolveTruceAc, fr) =>
actingFactionId = actingFactionId, selectionForResolveTruceOfferSelectedCommand(
ac = resolveTruceAc, actingFactionId = actingFactionId,
gs = gameState, ac = resolveTruceAc,
functionalRandom = fr gs = gameState,
).map { resolveTruceOfferSelection => functionalRandom = fr
Some( ).map { resolveTruceOfferSelection =>
CommandSelection( Some(
actingFactionId = actingFactionId, CommandSelection(
actingProvinceId = 0, actingFactionId = actingFactionId,
available = resolveTruceAc, actingProvinceId = 0,
selected = resolveTruceOfferSelection, available = resolveTruceAc,
reason = "resolving truce offer" selected = resolveTruceOfferSelection,
reason = "resolving truce offer"
)
) )
) }
}
} }
private def selectionForResolveTruceOfferSelectedCommand( private def selectionForResolveTruceOfferSelectedCommand(
@@ -177,9 +177,7 @@ object ResolveDiplomacyCommandSelector {
): RandomState[SelectedCommand] = functionalRandom.nextDouble.map { roll => ): RandomState[SelectedCommand] = functionalRandom.nextDouble.map { roll =>
val actingFid = actingFactionId val actingFid = actingFactionId
val bestOffer = ac.offers val bestOffer = ac.offers
.maxBy(inv => .maxBy(inv => truceOfferAcceptanceChance(inv.originatingFactionId, actingFid, gs))
truceOfferAcceptanceChance(inv.originatingFactionId, actingFid, gs)
)
internalRequire( internalRequire(
bestOffer.eligibleStatuses.contains(DIPLOMACY_OFFER_STATUS_ACCEPTED), bestOffer.eligibleStatuses.contains(DIPLOMACY_OFFER_STATUS_ACCEPTED),
@@ -225,7 +223,7 @@ object ResolveDiplomacyCommandSelector {
originatingFid: FactionId, originatingFid: FactionId,
targetFid: FactionId, targetFid: FactionId,
gs: GameState gs: GameState
): Int = { ): Int =
// Always reject if the faction already has an alliance // Always reject if the faction already has an alliance
if gs if gs
.factions(targetFid) .factions(targetFid)
@@ -235,11 +233,10 @@ object ResolveDiplomacyCommandSelector {
// reject if this is our only neighbor and we have no expansion possibilities // reject if this is our only neighbor and we have no expansion possibilities
else if LegacyFactionUtils.provinces(targetFid, gs).forall { province => else if LegacyFactionUtils.provinces(targetFid, gs).forall { province =>
province.neighbors.map { n => gs.provinces(n.provinceId) }.forall { province.neighbors.map(n => gs.provinces(n.provinceId)).forall { neighborProvince =>
neighborProvince => neighborProvince.rulingFactionId.contains(
neighborProvince.rulingFactionId.contains( originatingFid
originatingFid ) || neighborProvince.rulingFactionId.contains(targetFid)
) || neighborProvince.rulingFactionId.contains(targetFid)
} }
} }
then 0 then 0
@@ -254,26 +251,24 @@ object ResolveDiplomacyCommandSelector {
gameState = gs gameState = gs
) )
).floor.toInt.max(0) ).floor.toInt.max(0)
}
private def resolveRansomOfferSelectedCommand( private def resolveRansomOfferSelectedCommand(
actingFactionId: FactionId, actingFactionId: FactionId,
gameState: GameState, gameState: GameState,
availableCommands: Vector[AvailableCommand] availableCommands: Vector[AvailableCommand]
): Option[CommandSelection] = ): Option[CommandSelection] =
selectionForType[ResolveRansomOfferAvailableCommand](availableCommands) { selectionForType[ResolveRansomOfferAvailableCommand](availableCommands) { resolveRansomAc =>
resolveRansomAc => CommandSelection(
CommandSelection( actingFactionId = actingFactionId,
actingProvinceId = 0,
available = resolveRansomAc,
selected = selectionForResolveRansomOfferSelectedCommand(
actingFactionId = actingFactionId, actingFactionId = actingFactionId,
actingProvinceId = 0, ac = resolveRansomAc,
available = resolveRansomAc, gs = gameState
selected = selectionForResolveRansomOfferSelectedCommand( ),
actingFactionId = actingFactionId, reason = "resolving ransom offer"
ac = resolveRansomAc, )
gs = gameState
),
reason = "resolving ransom offer"
)
} }
private def selectionForResolveRansomOfferSelectedCommand( private def selectionForResolveRansomOfferSelectedCommand(
@@ -288,7 +283,7 @@ object ResolveDiplomacyCommandSelector {
ransomOffer = Some(ac.offers.head), ransomOffer = Some(ac.offers.head),
resolution = RansomOfferHelpers.chosenResolution(rod) resolution = RansomOfferHelpers.chosenResolution(rod)
) )
case _ => throw new EagleInternalException("Not a ransom offer") case _ => throw new EagleInternalException("Not a ransom offer")
} }
private def resolveAllianceOfferSelectedCommand( private def resolveAllianceOfferSelectedCommand(
@@ -300,23 +295,24 @@ object ResolveDiplomacyCommandSelector {
randomSelectionForType[ResolveAllianceOfferAvailableCommand]( randomSelectionForType[ResolveAllianceOfferAvailableCommand](
availableCommands, availableCommands,
functionalRandom functionalRandom
) { case (resolveAllianceAc, fr) => ) {
selectionForResolveAllianceOfferSelectedCommand( case (resolveAllianceAc, fr) =>
actingFactionId = actingFactionId, selectionForResolveAllianceOfferSelectedCommand(
ac = resolveAllianceAc, actingFactionId = actingFactionId,
gs = gameState, ac = resolveAllianceAc,
functionalRandom = fr gs = gameState,
).map { resolveAllianceOfferSelection => functionalRandom = fr
Some( ).map { resolveAllianceOfferSelection =>
CommandSelection( Some(
actingFactionId = actingFactionId, CommandSelection(
actingProvinceId = 0, actingFactionId = actingFactionId,
available = resolveAllianceAc, actingProvinceId = 0,
selected = resolveAllianceOfferSelection, available = resolveAllianceAc,
reason = "resolving alliance offer" selected = resolveAllianceOfferSelection,
reason = "resolving alliance offer"
)
) )
) }
}
} }
private def resolveBreakAllianceSelectedCommand( private def resolveBreakAllianceSelectedCommand(
@@ -328,23 +324,24 @@ object ResolveDiplomacyCommandSelector {
randomSelectionForType[ResolveBreakAllianceAvailableCommand]( randomSelectionForType[ResolveBreakAllianceAvailableCommand](
availableCommands, availableCommands,
functionalRandom functionalRandom
) { case (resolveBreakAllianceAc, fr) => ) {
selectionForResolveBreakAllianceSelectedCommand( case (resolveBreakAllianceAc, fr) =>
actingFactionId = actingFactionId, selectionForResolveBreakAllianceSelectedCommand(
ac = resolveBreakAllianceAc, actingFactionId = actingFactionId,
gs = gameState, ac = resolveBreakAllianceAc,
functionalRandom = fr gs = gameState,
).map { resolveBreakAllianceSelection => functionalRandom = fr
Some( ).map { resolveBreakAllianceSelection =>
CommandSelection( Some(
actingFactionId = actingFactionId, CommandSelection(
actingProvinceId = 0, actingFactionId = actingFactionId,
available = resolveBreakAllianceAc, actingProvinceId = 0,
selected = resolveBreakAllianceSelection, available = resolveBreakAllianceAc,
reason = "resolving break alliance" selected = resolveBreakAllianceSelection,
reason = "resolving break alliance"
)
) )
) }
}
} }
private def selectionForResolveAllianceOfferSelectedCommand( private def selectionForResolveAllianceOfferSelectedCommand(
@@ -355,9 +352,7 @@ object ResolveDiplomacyCommandSelector {
): RandomState[SelectedCommand] = functionalRandom.nextDouble.map { roll => ): RandomState[SelectedCommand] = functionalRandom.nextDouble.map { roll =>
val actingFid = actingFactionId val actingFid = actingFactionId
val bestOffer = ac.offers val bestOffer = ac.offers
.maxBy(inv => .maxBy(inv => allianceOfferAcceptanceChance(inv.originatingFactionId, actingFid, gs))
allianceOfferAcceptanceChance(inv.originatingFactionId, actingFid, gs)
)
internalRequire( internalRequire(
bestOffer.eligibleStatuses.contains(DIPLOMACY_OFFER_STATUS_ACCEPTED), bestOffer.eligibleStatuses.contains(DIPLOMACY_OFFER_STATUS_ACCEPTED),
@@ -1,19 +1,13 @@
package net.eagle0.eagle.ai package net.eagle0.eagle.ai
import net.eagle0.common.MoreOption import net.eagle0.common.MoreOption
import net.eagle0.eagle.api.available_command.{ import net.eagle0.eagle.api.available_command.{AvailableCommand, MarchAvailableCommand}
AvailableCommand,
MarchAvailableCommand
}
import net.eagle0.eagle.api.selected_command.MarchSelectedCommand import net.eagle0.eagle.api.selected_command.MarchSelectedCommand
import net.eagle0.eagle.common.combat_unit.CombatUnit import net.eagle0.eagle.common.combat_unit.CombatUnit
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.hero.Hero import net.eagle0.eagle.internal.hero.Hero
import net.eagle0.eagle.internal.province.Province import net.eagle0.eagle.internal.province.Province
import net.eagle0.eagle.library.settings.{ import net.eagle0.eagle.library.settings.{LoyaltyGainFromFeast, MinimumLoyaltyForSwearBrotherhood}
LoyaltyGainFromFeast,
MinimumLoyaltyForSwearBrotherhood
}
import net.eagle0.eagle.library.util.{CommandSelection, ProvinceDistances} import net.eagle0.eagle.library.util.{CommandSelection, ProvinceDistances}
import net.eagle0.eagle.library.util.command_choice_helpers.{ import net.eagle0.eagle.library.util.command_choice_helpers.{
CommandChoiceHelpers, CommandChoiceHelpers,
@@ -43,21 +37,22 @@ object SeekMoreLeadersCommandChooser {
factionHeadProvince: Province, factionHeadProvince: Province,
gameState: GameState gameState: GameState
): Option[CommandSelection] = { ): Option[CommandSelection] = {
val opmc = command.oneProvinceCommands val opmc = command.oneProvinceCommands
.find(_.originProvinceId == startingProvince.id) .find(_.originProvinceId == startingProvince.id)
.get .get
val bestDestination = val bestDestination =
opmc.availableDestinationProvinces opmc.availableDestinationProvinces
.map(dest => (opmc, gameState.provinces(dest.provinceId))) .map(dest => (opmc, gameState.provinces(dest.provinceId)))
.flatMap { case (opmc, p) => .flatMap {
ProvinceDistances case (opmc, p) =>
.distanceThroughFriendliesOption( ProvinceDistances
factionHeadProvince.id, .distanceThroughFriendliesOption(
p.id, factionHeadProvince.id,
factionHeadProvince.rulingFactionId.get, p.id,
gameState factionHeadProvince.rulingFactionId.get,
) gameState
.map(distance => (opmc, p, distance)) )
.map(distance => (opmc, p, distance))
} }
.minByOption(_._3) .minByOption(_._3)
@@ -88,7 +83,7 @@ object SeekMoreLeadersCommandChooser {
actingFactionId: FactionId, actingFactionId: FactionId,
gameState: GameState, gameState: GameState,
acs: Vector[AvailableCommand] acs: Vector[AvailableCommand]
): Option[CommandSelection] = { ): Option[CommandSelection] =
LegacyFactionUtils LegacyFactionUtils
.provinces(actingFactionId, gameState) .provinces(actingFactionId, gameState)
.find( .find(
@@ -106,36 +101,34 @@ object SeekMoreLeadersCommandChooser {
province.rulingFactionHeroIds.map(gameState.heroes), province.rulingFactionHeroIds.map(gameState.heroes),
gameState.factions(actingFactionId).leaders.toVector gameState.factions(actingFactionId).leaders.toVector
) )
.map { hero => Candidate(hero, province) } .map(hero => Candidate(hero, province))
} }
val marchACs = acs val marchACs = acs.collect { case ac: MarchAvailableCommand => ac }
.collect { case ac: MarchAvailableCommand => ac }
val marchableCandidates = candidates.flatMap { c => val marchableCandidates = candidates.flatMap { c =>
marchACs marchACs.find { mac =>
.find { mac => mac.oneProvinceCommands.exists(opmc =>
mac.oneProvinceCommands.exists(opmc => opmc.originProvinceId == c.province.id && opmc.availableHeroIds.toVector
opmc.originProvinceId == c.province.id && opmc.availableHeroIds.toVector .contains(c.hero.id)
.contains(c.hero.id) )
) }
}
.map(mac => (mac, c)) .map(mac => (mac, c))
} }
marchableCandidates marchableCandidates
.maxByOption(x => LegacyHeroUtils.power(x._2.hero)) .maxByOption(x => LegacyHeroUtils.power(x._2.hero))
.flatMap { case (command, candidate) => .flatMap {
maybeMoveTowardDestination( case (command, candidate) =>
command = command, maybeMoveTowardDestination(
hero = candidate.hero, command = command,
startingProvince = candidate.province, hero = candidate.hero,
factionHeadProvince = provinceWithFactionHead, startingProvince = candidate.province,
gameState = gameState factionHeadProvince = provinceWithFactionHead,
) gameState = gameState
)
} }
} }
}
def maybeSeekMoreLeadersCommand( def maybeSeekMoreLeadersCommand(
actingFactionId: FactionId, actingFactionId: FactionId,
@@ -154,37 +147,36 @@ object SeekMoreLeadersCommandChooser {
faction.leaders.toVector faction.leaders.toVector
) )
desiredHero desiredHero.flatMap { hero =>
.flatMap { hero => SwearBrotherhoodCommandSelector
SwearBrotherhoodCommandSelector .chosenCommandForSpecificHero(
.chosenCommandForSpecificHero( actingFactionId = actingFactionId,
actingFactionId = actingFactionId, gameState = gameState,
gameState = gameState, availableCommands = acs,
availableCommands = acs, desiredHeroId = hero.id
desiredHeroId = hero.id )
) .orElse {
.orElse { MoreOption.flatWhen(
MoreOption.flatWhen( MinimumLoyaltyForSwearBrotherhood.doubleValue - hero.loyalty < LoyaltyGainFromFeast.doubleValue
MinimumLoyaltyForSwearBrotherhood.doubleValue - hero.loyalty < LoyaltyGainFromFeast.doubleValue ) {
) { CommandChoiceHelpers.chosenFeastCommandIfAvailable(
CommandChoiceHelpers.chosenFeastCommandIfAvailable(
actingFactionId = actingFactionId,
gameState = gameState,
availableCommands = acs,
reason = s"want to make hero ${hero.id} a leader"
)
}
}
.orElse {
HeroGiftCommandSelector.chosenCommandForSpecificHero(
actingFactionId = actingFactionId, actingFactionId = actingFactionId,
gameState = gameState, gameState = gameState,
availableCommands = acs, availableCommands = acs,
heroId = hero.id, reason = s"want to make hero ${hero.id} a leader"
reason = "want to make this hero a leader"
) )
} }
} }
.orElse {
HeroGiftCommandSelector.chosenCommandForSpecificHero(
actingFactionId = actingFactionId,
gameState = gameState,
availableCommands = acs,
heroId = hero.id,
reason = "want to make this hero a leader"
)
}
}
.orElse( .orElse(
// Then try moving a hero this way // Then try moving a hero this way
maybeMoveBestCandidateCommand( maybeMoveBestCandidateCommand(
@@ -37,5 +37,5 @@ case class UnrequestedClientText(
llmRequest: GeneratedTextRequest llmRequest: GeneratedTextRequest
) extends ClientText { ) extends ClientText {
def complete: Boolean = false def complete: Boolean = false
def text: String = "" def text: String = ""
} }
@@ -9,11 +9,7 @@ import scala.util.Try
import net.eagle0.common.ProtoParser import net.eagle0.common.ProtoParser
import net.eagle0.eagle.{ClientTextId, FactionId} import net.eagle0.eagle.{ClientTextId, FactionId}
import net.eagle0.eagle.internal.client_text.{ import net.eagle0.eagle.internal.client_text.{IncompleteText, IncompleteTextStore, UnrequestedText}
IncompleteText,
IncompleteTextStore,
UnrequestedText
}
import net.eagle0.eagle.internal.generated_text_request.GeneratedTextRequest import net.eagle0.eagle.internal.generated_text_request.GeneratedTextRequest
import net.eagle0.eagle.library.util.EagleRequire.internalRequire import net.eagle0.eagle.library.util.EagleRequire.internalRequire
import net.eagle0.eagle.library.EagleInternalException import net.eagle0.eagle.library.EagleInternalException
@@ -56,7 +52,7 @@ case class ClientTextStoreImpl(
requestedAfterHistoryCount = requestedAfterHistoryCount, requestedAfterHistoryCount = requestedAfterHistoryCount,
llmRequest = llmRequest llmRequest = llmRequest
)), )),
accessibleTo = this.accessibleTo + (id -> accessibleTo), accessibleTo = this.accessibleTo + (id -> accessibleTo),
accessibleToIsSaved = false, accessibleToIsSaved = false,
incompleteTextsAreSaved = false incompleteTextsAreSaved = false
) )
@@ -73,8 +69,7 @@ case class ClientTextStoreImpl(
id = id, id = id,
partialText = "", partialText = "",
llmRequest = unrequested.llmRequest, llmRequest = unrequested.llmRequest,
requestedAfterHistoryCount = requestedAfterHistoryCount = unrequested.requestedAfterHistoryCount
unrequested.requestedAfterHistoryCount
)), )),
unrequestedTexts = unrequestedTexts - id, unrequestedTexts = unrequestedTexts - id,
incompleteTextsAreSaved = false incompleteTextsAreSaved = false
@@ -98,7 +93,7 @@ case class ClientTextStoreImpl(
.orElse { .orElse {
completeTexts.get(id).map(_.text) completeTexts.get(id).map(_.text)
} }
.map { text => TextGenerationSuccess(text) } .map(text => TextGenerationSuccess(text))
.getOrElse { .getOrElse {
incompleteTexts incompleteTexts
.get(id) .get(id)
@@ -172,7 +167,7 @@ case class ClientTextStoreImpl(
addedFactionIds: Vector[FactionId] addedFactionIds: Vector[FactionId]
): ClientTextStore = { ): ClientTextStore = {
val originalFactionIds = accessibleTo.getOrElse(id, Vector()).sorted val originalFactionIds = accessibleTo.getOrElse(id, Vector()).sorted
val updatedFactionIds = val updatedFactionIds =
(originalFactionIds ++ addedFactionIds).distinct.sorted (originalFactionIds ++ addedFactionIds).distinct.sorted
if updatedFactionIds == originalFactionIds then this if updatedFactionIds == originalFactionIds then this
@@ -206,22 +201,22 @@ case class ClientTextStoreImpl(
} }
object ClientTextStoreImpl { object ClientTextStoreImpl {
private val separator = "\n******\n" private val separator = "\n******\n"
private val separatorPattern = Pattern.quote(separator) private val separatorPattern = Pattern.quote(separator)
private def completeTextsKey = "completeText.txt" private def completeTextsKey = "completeText.txt"
private def incompleteTextsKey = "incompleteText.dat" private def incompleteTextsKey = "incompleteText.dat"
private def visibilityKey = "visibility.txt" private def visibilityKey = "visibility.txt"
def saved(clientTextStore: ClientTextStoreImpl): ClientTextStore = { def saved(clientTextStore: ClientTextStoreImpl): ClientTextStore = {
val completeSaved = val completeSaved =
if clientTextStore.completeTexts.size > clientTextStore.savedCompleteCount if clientTextStore.completeTexts.size > clientTextStore.savedCompleteCount
then { then {
// FIXME: make this append, will require Persister changes // FIXME: make this append, will require Persister changes
val saveData = clientTextStore.completeTexts val saveData = clientTextStore.completeTexts.map {
.map { case (_: String, ct: CompleteClientText) => case (_: String, ct: CompleteClientText) =>
s"${ct.id}\n${ct.requestedAfterHistoryCount}\n${ct.text}$separator" s"${ct.id}\n${ct.requestedAfterHistoryCount}\n${ct.text}$separator"
} }
.mkString("") .mkString("")
.getBytes(StandardCharsets.UTF_8) .getBytes(StandardCharsets.UTF_8)
@@ -231,9 +226,7 @@ object ClientTextStoreImpl {
) )
} }
clientTextStore.copy(savedCompleteCount = clientTextStore.copy(savedCompleteCount = clientTextStore.completeTexts.size)
clientTextStore.completeTexts.size
)
} else clientTextStore } else clientTextStore
val incompleteSaved = val incompleteSaved =
@@ -270,10 +263,10 @@ object ClientTextStoreImpl {
val accessibleToSaved = val accessibleToSaved =
if !incompleteSaved.accessibleToIsSaved then { if !incompleteSaved.accessibleToIsSaved then {
val accessibleToData = incompleteSaved.accessibleTo val accessibleToData = incompleteSaved.accessibleTo.map {
.map { case (id, factions) => case (id, factions) =>
s"$id\n${factions.mkString(",")}$separator" s"$id\n${factions.mkString(",")}$separator"
} }
.mkString("") .mkString("")
.getBytes(StandardCharsets.UTF_8) .getBytes(StandardCharsets.UTF_8)
@@ -316,9 +309,10 @@ object ClientTextStoreImpl {
} }
.toVector .toVector
} }
.recover { case _: FileNotFoundException => .recover {
println("No complete text store found") case _: FileNotFoundException =>
Vector() println("No complete text store found")
Vector()
} }
private def loadedIncompleteTexts( private def loadedIncompleteTexts(
@@ -350,9 +344,10 @@ object ClientTextStoreImpl {
} }
.getOrElse((Vector(), Vector())) .getOrElse((Vector(), Vector()))
} }
.recover { case _: FileNotFoundException => .recover {
println("No incomplete text store found") case _: FileNotFoundException =>
(Vector(), Vector()) println("No incomplete text store found")
(Vector(), Vector())
} }
private def loadedAccessibleTo( private def loadedAccessibleTo(
@@ -376,9 +371,10 @@ object ClientTextStoreImpl {
} }
.toMap .toMap
} }
.recover { case _: FileNotFoundException => .recover {
println("No accessibleTo store found") case _: FileNotFoundException =>
Map() println("No accessibleTo store found")
Map()
} }
def loaded( def loaded(
@@ -386,22 +382,18 @@ object ClientTextStoreImpl {
persister: Persister persister: Persister
): Try[ClientTextStore] = ): Try[ClientTextStore] =
for { for {
loadedCompletedTexts <- loadedCompleteTexts(persister) loadedCompletedTexts <- loadedCompleteTexts(persister)
loadedIncompleteTexts <- loadedIncompleteTexts(persister) loadedIncompleteTexts <- loadedIncompleteTexts(persister)
loadedAccessibleTo <- loadedAccessibleTo(persister) loadedAccessibleTo <- loadedAccessibleTo(persister)
} yield { } yield ClientTextStoreImpl(
ClientTextStoreImpl( pregenerated = pregenerated,
pregenerated = pregenerated, persister = persister,
persister = persister, completeTexts = loadedCompletedTexts.map(ct => ct.id -> ct).toMap,
completeTexts = loadedCompletedTexts.map(ct => ct.id -> ct).toMap, incompleteTexts = loadedIncompleteTexts._1.map(ict => ict.id -> ict).toMap,
incompleteTexts = unrequestedTexts = loadedIncompleteTexts._2.map(ut => ut.id -> ut).toMap,
loadedIncompleteTexts._1.map(ict => ict.id -> ict).toMap, accessibleTo = loadedAccessibleTo,
unrequestedTexts = savedCompleteCount = loadedCompletedTexts.size,
loadedIncompleteTexts._2.map(ut => ut.id -> ut).toMap, incompleteTextsAreSaved = true,
accessibleTo = loadedAccessibleTo, accessibleToIsSaved = true
savedCompleteCount = loadedCompletedTexts.size, )
incompleteTextsAreSaved = true,
accessibleToIsSaved = true
)
}
} }
@@ -22,7 +22,7 @@ object TextGenerationResult {
acc :+ result, acc :+ result,
tail tail
) )
case x => x case x => x
} }
} }
@@ -47,30 +47,28 @@ trait TextGenerationResult {
} }
case class TextGenerationSuccess(result: String) extends TextGenerationResult { case class TextGenerationSuccess(result: String) extends TextGenerationResult {
override def resolved: Boolean = true override def resolved: Boolean = true
override def get: String = result override def get: String = result
} }
case class TextGenerationDependencyInProgress( case class TextGenerationDependencyInProgress(
inProgressTextId: String, inProgressTextId: String,
partialText: String partialText: String
) extends TextGenerationResult { ) extends TextGenerationResult {
override def resolved: Boolean = false override def resolved: Boolean = false
override def get: String = override def get: String =
throw new EagleInternalException( throw new EagleInternalException(
s"Text generation dependency in progress: $inProgressTextId" s"Text generation dependency in progress: $inProgressTextId"
) )
} }
case class TextGenerationDependencyWaiting(notSatisfiedTextId: String) case class TextGenerationDependencyWaiting(notSatisfiedTextId: String) extends TextGenerationResult {
extends TextGenerationResult {
override def resolved: Boolean = false override def resolved: Boolean = false
override def get: String = override def get: String =
throw new EagleInternalException( throw new EagleInternalException(
s"Text generation dependency not satisfied: $notSatisfiedTextId" s"Text generation dependency not satisfied: $notSatisfiedTextId"
) )
} }
case class TextGenerationDependencyUnknown(unknownTextId: String) case class TextGenerationDependencyUnknown(unknownTextId: String) extends TextGenerationResult {
extends TextGenerationResult {
override def resolved: Boolean = false override def resolved: Boolean = false
override def get: String = override def get: String =
throw new EagleInternalException( throw new EagleInternalException(
s"Text generation dependency unknown: $unknownTextId" s"Text generation dependency unknown: $unknownTextId"
) )
@@ -84,8 +84,8 @@ object ActionResultFilter {
RoundPhase.HERO_DEPARTURES, RoundPhase.HERO_DEPARTURES,
RoundPhase.UNCONTESTED_CONQUEST, RoundPhase.UNCONTESTED_CONQUEST,
RoundPhase.BATTLE_RESOLUTION, RoundPhase.BATTLE_RESOLUTION,
RoundPhase.BATTLE_AFTERMATH, // MIGHT BE WRONG RoundPhase.BATTLE_AFTERMATH, // MIGHT BE WRONG
RoundPhase.ATTACK_DECISION, // MIGHT BE WRONG RoundPhase.ATTACK_DECISION, // MIGHT BE WRONG
RoundPhase.DIPLOMACY_RESOLUTION // IS WRONG RoundPhase.DIPLOMACY_RESOLUTION // IS WRONG
) )
@@ -97,8 +97,7 @@ object ActionResultFilter {
GameStateViewDiffer.diff( GameStateViewDiffer.diff(
before = GameStateViewFilter before = GameStateViewFilter
.filteredGameState(gs = before, factionId = factionId), .filteredGameState(gs = before, factionId = factionId),
after = after = GameStateViewFilter.filteredGameState(gs = after, factionId = factionId)
GameStateViewFilter.filteredGameState(gs = after, factionId = factionId)
) )
private def transformOne( private def transformOne(
@@ -115,9 +114,7 @@ object ActionResultFilter {
) )
def shouldInclude(note: Notification): Boolean = def shouldInclude(note: Notification): Boolean =
note.targetFactions.isEmpty || factionId.exists(fid => note.targetFactions.isEmpty || factionId.exists(fid => note.targetFactions.exists(_.factionId == fid))
note.targetFactions.exists(_.factionId == fid)
)
if gsDiff.isDefined && if gsDiff.isDefined &&
result.actionResult.player.isDefined && result.actionResult.player.isDefined &&
@@ -143,8 +140,7 @@ object ActionResultFilter {
province = actionResult.province, province = actionResult.province,
leader = actionResult.leader, leader = actionResult.leader,
gameStateDiff = gsDiff, gameStateDiff = gsDiff,
notifications = notifications = actionResult.notificationsToDeliver.filter(shouldInclude)
actionResult.notificationsToDeliver.filter(shouldInclude)
) )
} }
} }
@@ -154,10 +150,9 @@ object ActionResultFilter {
results: Vector[ActionWithResultingState], results: Vector[ActionWithResultingState],
factionId: Option[FactionId] factionId: Option[FactionId]
): Vector[ActionResultView] = ): Vector[ActionResultView] =
factionId factionId.map { pid =>
.map { pid => filterForPlayer(startingState, results, pid)
filterForPlayer(startingState, results, pid) }
}
.getOrElse(filterForObserver(startingState, results)) .getOrElse(filterForObserver(startingState, results))
def filterForPlayer( def filterForPlayer(
@@ -182,7 +177,5 @@ object ActionResultFilter {
startingState: GameState, startingState: GameState,
results: Vector[ActionWithResultingState] results: Vector[ActionWithResultingState]
): Vector[ActionResultView] = ): Vector[ActionResultView] =
results.flatMap(res => results.flatMap(res => observeOne(result = res, startingState = startingState))
observeOne(result = res, startingState = startingState)
)
} }
@@ -1,4 +1,3 @@
package net.eagle0.eagle.library package net.eagle0.eagle.library
class EagleValidationException(message: String) class EagleValidationException(message: String) extends EagleInternalException(message) {}
extends EagleInternalException(message) {}
@@ -8,24 +8,15 @@ import net.eagle0.eagle.api.command.AvailableCommands
import net.eagle0.eagle.api.selected_command.SelectedCommand import net.eagle0.eagle.api.selected_command.SelectedCommand
import net.eagle0.eagle.internal.action_result.ActionResult import net.eagle0.eagle.internal.action_result.ActionResult
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.library.actions.applier.{ import net.eagle0.eagle.library.actions.applier.{ActionResultProtoApplier, ActionResultProtoApplierImpl}
ActionResultProtoApplier,
ActionResultProtoApplierImpl
}
import net.eagle0.eagle.library.actions.availability.AvailableCommandsFactory import net.eagle0.eagle.library.actions.availability.AvailableCommandsFactory
import net.eagle0.eagle.library.actions.impl.action.{ import net.eagle0.eagle.library.actions.impl.action.{
CheckForFulfilledQuestsAction, CheckForFulfilledQuestsAction,
HeroBackstoryUpdateActionGenerator, HeroBackstoryUpdateActionGenerator,
ResolveBattleAction ResolveBattleAction
} }
import net.eagle0.eagle.library.actions.impl.command.{ import net.eagle0.eagle.library.actions.impl.command.{AvailableCommandTypeMap, CommandFactory}
AvailableCommandTypeMap, import net.eagle0.eagle.library.actions.impl.common.{ActionWithResultingState, RandomStateProtoSequencer}
CommandFactory
}
import net.eagle0.eagle.library.actions.impl.common.{
ActionWithResultingState,
RandomStateProtoSequencer
}
import net.eagle0.eagle.library.util.hero_generator.HeroGenerator import net.eagle0.eagle.library.util.hero_generator.HeroGenerator
import net.eagle0.eagle.library.util.validations.RuntimeValidator import net.eagle0.eagle.library.util.validations.RuntimeValidator
import net.eagle0.eagle.library.util.EagleRequire.internalRequire import net.eagle0.eagle.library.util.EagleRequire.internalRequire
@@ -83,11 +74,9 @@ object EngineImpl {
.toVector, .toVector,
battalions = eng.currentState.battalions.values.toVector battalions = eng.currentState.battalions.values.toVector
.map(BattalionConverter.fromProto), .map(BattalionConverter.fromProto),
getHero = getHero = hid => eng.currentState.heroes.get(hid).map(HeroConverter.fromProto),
hid => eng.currentState.heroes.get(hid).map(HeroConverter.fromProto),
battalionTypes = eng.currentState.battalionTypes.toVector, battalionTypes = eng.currentState.battalionTypes.toVector,
heroBackstoryTextIdLookup = heroBackstoryTextIdLookup = hid => eng.currentState.heroes(hid).backstoryVersions.last.textId
hid => eng.currentState.heroes(hid).backstoryVersions.last.textId
).results ).results
) )
@@ -99,8 +88,7 @@ object EngineImpl {
withPhaseAdvancement(engineAndResults) withPhaseAdvancement(engineAndResults)
) )
if newEar.results.length > engineAndResults.results.length then if newEar.results.length > engineAndResults.results.length then withUpdateChecks(newEar)
withUpdateChecks(newEar)
else newEar else newEar
} }
@@ -110,8 +98,7 @@ object EngineImpl {
): EngineAndResults = withUpdateChecks( ): EngineAndResults = withUpdateChecks(
EngineAndResultsImpl( EngineAndResultsImpl(
engine = engine.copy( engine = engine.copy(
currentState = currentState = results.lastOption.map(_.gameState).getOrElse(engine.currentState),
results.lastOption.map(_.gameState).getOrElse(engine.currentState),
history = engine.history.withNewResults(results) history = engine.history.withNewResults(results)
), ),
results = results.map(_.actionResult) results = results.map(_.actionResult)
@@ -146,14 +133,14 @@ final case class EngineAndResultsImpl(
def recursiveTransformT( def recursiveTransformT(
f: EngineImpl => Vector[ActionResultT] f: EngineImpl => Vector[ActionResultT]
): EngineAndResultsImpl = recursiveTransform(eng => { ): EngineAndResultsImpl = recursiveTransform { eng =>
val results = f(eng) val results = f(eng)
RandomStateProtoSequencer( RandomStateProtoSequencer(
initialStateProto = eng.currentState, initialStateProto = eng.currentState,
actionResultProtoApplier = eng.actionResultProtoApplier, actionResultProtoApplier = eng.actionResultProtoApplier,
functionalRandom = SeededRandom(eng.currentState.randomSeed) functionalRandom = SeededRandom(eng.currentState.randomSeed)
).withActionResultTs(_ => results).results.newValue ).withActionResultTs(_ => results).results.newValue
}) }
def saveNow: EngineAndResultsImpl = def saveNow: EngineAndResultsImpl =
this.copy(engine = this.engine.saveNow) this.copy(engine = this.engine.saveNow)
@@ -169,8 +156,7 @@ case class EngineImpl(
override val currentState: GameState, override val currentState: GameState,
heroGenerator: HeroGenerator, heroGenerator: HeroGenerator,
override val history: GameHistory, override val history: GameHistory,
actionResultProtoApplier: ActionResultProtoApplier = actionResultProtoApplier: ActionResultProtoApplier = new ActionResultProtoApplierImpl(validator = RuntimeValidator)
new ActionResultProtoApplierImpl(validator = RuntimeValidator)
) extends Engine { ) extends Engine {
import net.eagle0.eagle.library.util.EagleRequire.commandRequire import net.eagle0.eagle.library.util.EagleRequire.commandRequire
@@ -298,7 +284,7 @@ case class EngineImpl(
availableCommandOpt.isDefined, availableCommandOpt.isDefined,
s"No matching available command for selected command $selectedCommand" s"No matching available command for selected command $selectedCommand"
) )
val availableCommand = availableCommandOpt.get val availableCommand = availableCommandOpt.get
val sequencer = RandomStateProtoSequencer( val sequencer = RandomStateProtoSequencer(
initialStateProto = this.currentState, initialStateProto = this.currentState,
@@ -326,9 +312,7 @@ case class EngineImpl(
) )
results results
}.withProtolessSequentialResultsAction(gs => }.withProtolessSequentialResultsAction(gs => HeroBackstoryUpdateActionGenerator.fromGameState(gs))
HeroBackstoryUpdateActionGenerator.fromGameState(gs)
)
appliedResults( appliedResults(
engine = this, engine = this,
@@ -17,8 +17,7 @@ trait GameHistory {
def all: Vector[ActionWithResultingState] def all: Vector[ActionWithResultingState]
def since(count: Int): Vector[ActionWithResultingState] def since(count: Int): Vector[ActionWithResultingState]
def sinceCapped(count: Int, resultSizeCap: Int): CappedResults = def sinceCapped(count: Int, resultSizeCap: Int): CappedResults =
if this.count - count <= resultSizeCap then if this.count - count <= resultSizeCap then CappedResults(since(count), None)
CappedResults(since(count), None)
else else
CappedResults( CappedResults(
since(this.count - resultSizeCap), since(this.count - resultSizeCap),
@@ -6,10 +6,7 @@ import net.eagle0.common.SeededRandom
import net.eagle0.eagle.common.round_phase.RoundPhase import net.eagle0.eagle.common.round_phase.RoundPhase
import net.eagle0.eagle.common.round_phase.RoundPhase.* import net.eagle0.eagle.common.round_phase.RoundPhase.*
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.library.actions.applier.{ import net.eagle0.eagle.library.actions.applier.{ActionResultProtoApplier, ActionResultTApplierImpl}
ActionResultProtoApplier,
ActionResultTApplierImpl
}
import net.eagle0.eagle.library.actions.availability.AvailableCommandsFactory import net.eagle0.eagle.library.actions.availability.AvailableCommandsFactory
import net.eagle0.eagle.library.actions.impl.action.* import net.eagle0.eagle.library.actions.impl.action.*
import net.eagle0.eagle.library.actions.impl.command.CommandFactory import net.eagle0.eagle.library.actions.impl.command.CommandFactory
@@ -29,8 +26,8 @@ import net.eagle0.eagle.RoundId
object RoundPhaseAdvancer { object RoundPhaseAdvancer {
private val times: mutable.Map[RoundPhase, Long] = private val times: mutable.Map[RoundPhase, Long] =
mutable.Map(RoundPhase.values.map(_ -> 0L)*) mutable.Map(RoundPhase.values.map(_ -> 0L)*)
private val print = false private val print = false
private val roundsBetweenPrint = 100 private val roundsBetweenPrint = 100
private def printTimings(roundId: RoundId): Unit = { private def printTimings(roundId: RoundId): Unit = {
val totalTime = times.values.sum.toDouble / 1000.0 val totalTime = times.values.sum.toDouble / 1000.0
@@ -39,7 +36,7 @@ object RoundPhaseAdvancer {
case (phase, time) => case (phase, time) =>
val timeInSecs = time.toDouble / 1000.0 val timeInSecs = time.toDouble / 1000.0
val msPerRound = time.toDouble / roundId.toDouble val msPerRound = time.toDouble / roundId.toDouble
val timePct = timeInSecs / totalTime * 100.0 val timePct = timeInSecs / totalTime * 100.0
println( println(
f"$phase%-25s: $timeInSecs%6.2fs ($msPerRound%4.2fms per round), $timePct%4.1f%%" f"$phase%-25s: $timeInSecs%6.2fs ($msPerRound%4.2fms per round), $timePct%4.1f%%"
) )
@@ -56,7 +53,7 @@ object RoundPhaseAdvancer {
commandFactory: CommandFactory commandFactory: CommandFactory
): Vector[ActionWithResultingState] = { ): Vector[ActionWithResultingState] = {
val currentPhase = currentState.currentPhase val currentPhase = currentState.currentPhase
val startTime = System.currentTimeMillis val startTime = System.currentTimeMillis
if print && currentPhase == NEW_ROUND && currentState.currentRoundId % roundsBetweenPrint == 0 if print && currentPhase == NEW_ROUND && currentState.currentRoundId % roundsBetweenPrint == 0
then { then {
@@ -270,8 +267,7 @@ object RoundPhaseAdvancer {
currentRoundId = currentState.currentRoundId, currentRoundId = currentState.currentRoundId,
currentDate = DateConverter.fromProto(currentState.currentDate), currentDate = DateConverter.fromProto(currentState.currentDate),
battleCounter = currentState.battleCounter, battleCounter = currentState.battleCounter,
heroes = heroes = currentState.heroes.view.mapValues(HeroConverter.fromProto).toMap,
currentState.heroes.view.mapValues(HeroConverter.fromProto).toMap,
battalions = currentState.battalions.view battalions = currentState.battalions.view
.mapValues(BattalionConverter.fromProto) .mapValues(BattalionConverter.fromProto)
.toMap, .toMap,
@@ -286,7 +282,7 @@ object RoundPhaseAdvancer {
converted.typeId -> converted converted.typeId -> converted
}.toMap }.toMap
) )
val requestResults = actionResultProtoApplier.applyActionResults( val requestResults = actionResultProtoApplier.applyActionResults(
currentState, currentState,
requestBattlesAction.results.map( requestBattlesAction.results.map(
ActionResultProtoConverter.toProto(_) ActionResultProtoConverter.toProto(_)
@@ -337,8 +333,7 @@ object RoundPhaseAdvancer {
currentState, currentState,
EndDiplomacyResolutionPhaseAction( EndDiplomacyResolutionPhaseAction(
currentState, currentState,
actionResultTApplier = actionResultTApplier = ActionResultTApplierImpl(actionResultProtoApplier)
ActionResultTApplierImpl(actionResultProtoApplier)
).randomResults(SeededRandom(currentState.randomSeed)) ).randomResults(SeededRandom(currentState.randomSeed))
.newValue .newValue
.map(ActionResultProtoConverter.toProto) .map(ActionResultProtoConverter.toProto)
@@ -352,7 +347,7 @@ object RoundPhaseAdvancer {
case Unrecognized(x) => case Unrecognized(x) =>
throw new IllegalStateException(s"Unknown round phase $x") throw new IllegalStateException(s"Unknown round phase $x")
} }
val timeSpent = System.currentTimeMillis - startTime val timeSpent = System.currentTimeMillis - startTime
times(currentPhase) = times(currentPhase) + timeSpent times(currentPhase) = times(currentPhase) + timeSpent
validateResults(results, currentState, availableCommandsFactory) validateResults(results, currentState, availableCommandsFactory)
@@ -31,10 +31,7 @@ import net.eagle0.eagle.common.unaffiliated_hero_type.UnaffiliatedHeroType.{
import net.eagle0.eagle.internal.action_result.ActionResult import net.eagle0.eagle.internal.action_result.ActionResult
import net.eagle0.eagle.internal.army.{HostileArmyGroup, MovingArmy} import net.eagle0.eagle.internal.army.{HostileArmyGroup, MovingArmy}
import net.eagle0.eagle.internal.battalion.Battalion import net.eagle0.eagle.internal.battalion.Battalion
import net.eagle0.eagle.internal.changed_faction.{ import net.eagle0.eagle.internal.changed_faction.{ChangedFaction, TrustLevelUpdate}
ChangedFaction,
TrustLevelUpdate
}
import net.eagle0.eagle.internal.changed_hero.ChangedHero import net.eagle0.eagle.internal.changed_hero.ChangedHero
import net.eagle0.eagle.internal.changed_hero.ChangedHero.{Loyalty, Vigor} import net.eagle0.eagle.internal.changed_hero.ChangedHero.{Loyalty, Vigor}
import net.eagle0.eagle.internal.changed_province.ChangedProvince import net.eagle0.eagle.internal.changed_province.ChangedProvince
@@ -49,24 +46,19 @@ import net.eagle0.eagle.internal.province.Province
import net.eagle0.eagle.internal.run_status.RunStatus import net.eagle0.eagle.internal.run_status.RunStatus
import net.eagle0.eagle.internal.shardok_battle.ShardokBattle import net.eagle0.eagle.internal.shardok_battle.ShardokBattle
import net.eagle0.eagle.library.actions.impl.common.ActionWithResultingState import net.eagle0.eagle.library.actions.impl.common.ActionWithResultingState
import net.eagle0.eagle.library.settings.{ import net.eagle0.eagle.library.settings.{ExtraXpForStatBumpOver100, XpForStatBump}
ExtraXpForStatBumpOver100,
XpForStatBump
}
import net.eagle0.eagle.library.util.hero.LegacyHeroUtils import net.eagle0.eagle.library.util.hero.LegacyHeroUtils
import net.eagle0.eagle.library.util.validations.Validator import net.eagle0.eagle.library.util.validations.Validator
import net.eagle0.eagle.library.util.EagleRequire.internalRequire import net.eagle0.eagle.library.util.EagleRequire.internalRequire
import net.eagle0.eagle.library.util.ProvinceEventUtils import net.eagle0.eagle.library.util.ProvinceEventUtils
import net.eagle0.eagle.library.EagleInternalException import net.eagle0.eagle.library.EagleInternalException
class ActionResultProtoApplierImpl(validator: Validator) class ActionResultProtoApplierImpl(validator: Validator) extends ActionResultProtoApplier {
extends ActionResultProtoApplier {
import validator.validate import validator.validate
override def xpForStatBump(stat: Int): Int = override def xpForStatBump(stat: Int): Int =
if stat <= 99 then XpForStatBump.intValue if stat <= 99 then XpForStatBump.intValue
else else XpForStatBump.intValue + ExtraXpForStatBumpOver100.intValue * (stat - 99)
XpForStatBump.intValue + ExtraXpForStatBumpOver100.intValue * (stat - 99)
private val trustMax: Int = 100 private val trustMax: Int = 100
@@ -93,10 +85,11 @@ class ActionResultProtoApplierImpl(validator: Validator)
if changedProvinces.isEmpty then gameState if changedProvinces.isEmpty then gameState
else else
changedProvinces changedProvinces
.foldLeft(gameState) { case (gs, cp) => .foldLeft(gameState) {
val after = gs.applyChangedProvince(cp) case (gs, cp) =>
validate(after.provinces(cp.id), gs.currentPhase) val after = gs.applyChangedProvince(cp)
after validate(after.provinces(cp.id), gs.currentPhase)
after
} }
def applyChangedProvince(cp: ChangedProvince): GameState = { def applyChangedProvince(cp: ChangedProvince): GameState = {
@@ -134,7 +127,7 @@ class ActionResultProtoApplierImpl(validator: Validator)
_.incomingShipments.modify { is => _.incomingShipments.modify { is =>
val holdovers = val holdovers =
is.filterNot(sh => cp.removedIncomingShipmentIds.contains(sh.id)) is.filterNot(sh => cp.removedIncomingShipmentIds.contains(sh.id))
val nextId = holdovers val nextId = holdovers
.map(_.id) .map(_.id)
.reduceOption(_ max _) .reduceOption(_ max _)
.getOrElse(0) + 1 .getOrElse(0) + 1
@@ -149,9 +142,7 @@ class ActionResultProtoApplierImpl(validator: Validator)
da.map { nda => da.map { nda =>
nda.withUnits( nda.withUnits(
nda.units nda.units
.filterNot(u => .filterNot(u => cp.removedRulingPlayerHeroIds.contains(u.heroId))
cp.removedRulingPlayerHeroIds.contains(u.heroId)
)
.map { .map {
case CombatUnit( case CombatUnit(
pid, pid,
@@ -185,17 +176,11 @@ class ActionResultProtoApplierImpl(validator: Validator)
newF newF
}, },
_.priceIndex.setIfDefined(cp.newPriceIndex), _.priceIndex.setIfDefined(cp.newPriceIndex),
_.economy.modify(e => _.economy.modify(e => (e + cp.economyDelta.getOrElse(0.0)).max(0.0).min(100.0)),
(e + cp.economyDelta.getOrElse(0.0)).max(0.0).min(100.0)
),
_.agriculture _.agriculture
.modify(a => .modify(a => (a + cp.agricultureDelta.getOrElse(0.0)).max(0.0).min(100.0)),
(a + cp.agricultureDelta.getOrElse(0.0)).max(0.0).min(100.0)
),
_.infrastructure _.infrastructure
.modify(i => .modify(i => (i + cp.infrastructureDelta.getOrElse(0.0)).max(0.0).min(100.0)),
(i + cp.infrastructureDelta.getOrElse(0.0)).max(0.0).min(100.0)
),
_.economyDevastation.modify(d => _.economyDevastation.modify(d =>
(d + cp.economyDevastationDelta.getOrElse(0.0)) (d + cp.economyDevastationDelta.getOrElse(0.0))
.max(0.0) .max(0.0)
@@ -211,16 +196,12 @@ class ActionResultProtoApplierImpl(validator: Validator)
.max(0.0) .max(0.0)
.min(provinceBefore.infrastructure) .min(provinceBefore.infrastructure)
), ),
_.support.modify(s => _.support.modify(s => (s + cp.supportDelta.getOrElse(0.0)).max(0.0).min(100.0)),
(s + cp.supportDelta.getOrElse(0.0)).max(0.0).min(100.0)
),
_.hasActed.setIfDefined(cp.setHasActed), _.hasActed.setIfDefined(cp.setHasActed),
_.rulerIsTraveling.setIfDefined(cp.setRulerIsTraveling), _.rulerIsTraveling.setIfDefined(cp.setRulerIsTraveling),
_.unaffiliatedHeroes.modify(uhs => _.unaffiliatedHeroes.modify(uhs =>
uhs uhs
.filterNot(uh => .filterNot(uh => cp.removedUnaffiliatedHeroIds.contains(uh.heroId))
cp.removedUnaffiliatedHeroIds.contains(uh.heroId)
)
.filterNot(uh => .filterNot(uh =>
cp.changedUnaffiliatedHeroes cp.changedUnaffiliatedHeroes
.map(_.heroId) .map(_.heroId)
@@ -247,14 +228,14 @@ class ActionResultProtoApplierImpl(validator: Validator)
cp.newProvinceEvents.exists( cp.newProvinceEvents.exists(
_.newEvents.exists(ProvinceEventUtils.isBeastsEvent) _.newEvents.exists(ProvinceEventUtils.isBeastsEvent)
) )
) { gameState.currentDate.get } )(gameState.currentDate.get)
), ),
_.lastRiotDate.setIfDefined( _.lastRiotDate.setIfDefined(
Option.when( Option.when(
cp.newProvinceEvents.exists( cp.newProvinceEvents.exists(
_.newEvents.exists(ProvinceEventUtils.isImminentRiotEvent) _.newEvents.exists(ProvinceEventUtils.isImminentRiotEvent)
) )
) { gameState.currentDate.get } )(gameState.currentDate.get)
), ),
_.incomingEndTurnActions :++= cp.addedIncomingEndTurnActions, _.incomingEndTurnActions :++= cp.addedIncomingEndTurnActions,
_.incomingEndTurnActions.modify( _.incomingEndTurnActions.modify(
@@ -265,9 +246,7 @@ class ActionResultProtoApplierImpl(validator: Validator)
.map(idx => dcs.patch(idx, Nil, 1)) .map(idx => dcs.patch(idx, Nil, 1))
.getOrElse(dcs) ++ cp.addedDeferredChange.asNonEmpty .getOrElse(dcs) ++ cp.addedDeferredChange.asNonEmpty
), ),
_.battleRevelations.modify(brs => _.battleRevelations.modify(brs => brs.diff(cp.removedBattleRevelations) ++ cp.addedBattleRevelations)
brs.diff(cp.removedBattleRevelations) ++ cp.addedBattleRevelations
)
) )
val provinceFixedForRuler = val provinceFixedForRuler =
@@ -275,27 +254,27 @@ class ActionResultProtoApplierImpl(validator: Validator)
then then
provinceApplied.update( provinceApplied.update(
_.optionalRulingFactionId := None, _.optionalRulingFactionId := None,
_.optionalRulingHeroId := None, _.optionalRulingHeroId := None,
_.rulingFactionHeroIds := Vector.empty, _.rulingFactionHeroIds := Vector.empty,
_.support := 0, _.support := 0,
_.provinceOrders := ProvinceOrderType.UNKNOWN_ORDERS, _.provinceOrders := ProvinceOrderType.UNKNOWN_ORDERS,
_.unaffiliatedHeroes.modify(uhs => _.unaffiliatedHeroes.modify(uhs =>
uhs.map(uh => uhs.map(uh =>
uh.update( uh.update(
_.recruitmentInfo := RecruitmentInfo( _.recruitmentInfo := RecruitmentInfo(
status = uh.`type` match { status = uh.`type` match {
case UNAFFILIATED_HERO_PRISONER => case UNAFFILIATED_HERO_PRISONER =>
RECRUITMENT_STATUS_PRISONER RECRUITMENT_STATUS_PRISONER
case UNAFFILIATED_HERO_MOVING_PRISONER => case UNAFFILIATED_HERO_MOVING_PRISONER =>
RECRUITMENT_STATUS_MOVING_PRISONER RECRUITMENT_STATUS_MOVING_PRISONER
case UNAFFILIATED_HERO_RETURNING_PRISONER => case UNAFFILIATED_HERO_RETURNING_PRISONER =>
RECRUITMENT_STATUS_NO_RULER_IN_PROVINCE RECRUITMENT_STATUS_NO_RULER_IN_PROVINCE
case UNAFFILIATED_HERO_OUTLAW => RECRUITMENT_STATUS_OUTLAW case UNAFFILIATED_HERO_OUTLAW => RECRUITMENT_STATUS_OUTLAW
case UNAFFILIATED_HERO_TRAVELER => case UNAFFILIATED_HERO_TRAVELER =>
RECRUITMENT_STATUS_TRAVELER RECRUITMENT_STATUS_TRAVELER
case UNAFFILIATED_HERO_RESIDENT => case UNAFFILIATED_HERO_RESIDENT =>
RECRUITMENT_STATUS_NO_RULER_IN_PROVINCE RECRUITMENT_STATUS_NO_RULER_IN_PROVINCE
case UNAFFILIATED_HERO_UNKNOWN => case UNAFFILIATED_HERO_UNKNOWN =>
throw new EagleInternalException( throw new EagleInternalException(
"Unknown unaffiliated hero type" "Unknown unaffiliated hero type"
) )
@@ -325,12 +304,13 @@ class ActionResultProtoApplierImpl(validator: Validator)
): Vector[MovingArmy] = { ): Vector[MovingArmy] = {
val holdovers = val holdovers =
existingArmies.filterNot(ma => removedIds.contains(ma.id)) existingArmies.filterNot(ma => removedIds.contains(ma.id))
val nextId = holdovers val nextId = holdovers
.map(_.id) .map(_.id)
.reduceOption(_ max _) .reduceOption(_ max _)
.getOrElse(0) + 1 .getOrElse(0) + 1
holdovers.toVector ++ addedArmies.zipWithIndex.map { case (army, index) => holdovers.toVector ++ addedArmies.zipWithIndex.map {
army.withId(index + nextId) case (army, index) =>
army.withId(index + nextId)
} }
} }
@@ -339,7 +319,7 @@ class ActionResultProtoApplierImpl(validator: Validator)
removedFactionIds: Seq[FactionId], removedFactionIds: Seq[FactionId],
addedArmies: Seq[HostileArmyGroup], addedArmies: Seq[HostileArmyGroup],
statusChanges: Seq[HostileArmyGroupStatusChange] statusChanges: Seq[HostileArmyGroupStatusChange]
): Vector[HostileArmyGroup] = { ): Vector[HostileArmyGroup] =
statusChanges statusChanges
.foldLeft( .foldLeft(
existingGroups existingGroups
@@ -360,9 +340,8 @@ class ActionResultProtoApplierImpl(validator: Validator)
} }
} }
.toVector ++ addedArmies .toVector ++ addedArmies
}
def applyProvinceActed(actedProvince: Option[Int]): GameState = { def applyProvinceActed(actedProvince: Option[Int]): GameState = {
internalRequire( internalRequire(
!actedProvince.contains(0), !actedProvince.contains(0),
"Province acted has id 0" "Province acted has id 0"
@@ -396,20 +375,19 @@ class ActionResultProtoApplierImpl(validator: Validator)
): GameState = ): GameState =
if newBattalions.isEmpty then gameState if newBattalions.isEmpty then gameState
else else
provinceId provinceId.map { pid =>
.map { pid => val maxCurrentId =
val maxCurrentId = if gameState.battalions.isEmpty then 0
if gameState.battalions.isEmpty then 0 else gameState.battalions.keys.max
else gameState.battalions.keys.max val newIds =
val newIds = (maxCurrentId + 1) to (maxCurrentId + newBattalions.size)
(maxCurrentId + 1) to (maxCurrentId + newBattalions.size) gameState.update(
gameState.update( _.battalions :++= newIds.zip(newBattalions).map {
_.battalions :++= newIds.zip(newBattalions).map { case (id, batt) => id -> batt.withId(id)
case (id, batt) => id -> batt.withId(id) },
}, _.provinces(pid).battalionIds :++= newIds
_.provinces(pid).battalionIds :++= newIds )
) }
}
.getOrElse( .getOrElse(
gameState gameState
.update(_.battalions :++= newBattalions.map(b => b.id -> b)) .update(_.battalions :++= newBattalions.map(b => b.id -> b))
@@ -444,14 +422,15 @@ class ActionResultProtoApplierImpl(validator: Validator)
_.battalions := gameState.battalions -- destroyedBattalionIds, _.battalions := gameState.battalions -- destroyedBattalionIds,
_.destroyedBattalions :++= destroyedBattalionIds _.destroyedBattalions :++= destroyedBattalionIds
.filterNot(_ == -1) .filterNot(_ == -1)
.map { bid => bid -> gameState.battalions(bid) }, .map(bid => bid -> gameState.battalions(bid)),
_.provinces := gameState.provinces.map { case (pid, p) => _.provinces := gameState.provinces.map {
pid -> p.update( case (pid, p) =>
_.battalionIds := p.battalionIds pid -> p.update(
.filterNot(destroyedBattalionIds.contains), _.battalionIds := p.battalionIds
_.incomingArmies := p.incomingArmies .filterNot(destroyedBattalionIds.contains),
.map(a => movingArmyWithoutBattalions(a, destroyedBattalionIds)) _.incomingArmies := p.incomingArmies
) .map(a => movingArmyWithoutBattalions(a, destroyedBattalionIds))
)
} }
) )
@@ -484,14 +463,14 @@ class ActionResultProtoApplierImpl(validator: Validator)
def applyChangedHeroes( def applyChangedHeroes(
changedHeroes: Seq[ChangedHero], changedHeroes: Seq[ChangedHero],
date: Date date: Date
): GameState = { ): GameState =
if changedHeroes.isEmpty then gameState if changedHeroes.isEmpty then gameState
else else
changedHeroes changedHeroes
.foldLeft(gameState) { case (gs, ch) => .foldLeft(gameState) {
gs.applyChangedHero(ch, date) case (gs, ch) =>
gs.applyChangedHero(ch, date)
} }
}
private def statBump( private def statBump(
stat: Int, stat: Int,
@@ -633,9 +612,10 @@ class ActionResultProtoApplierImpl(validator: Validator)
) )
} }
def applyRemovedFactions(removedFactionIds: Set[FactionId]): GameState = { def applyRemovedFactions(removedFactionIds: Set[FactionId]): GameState =
gameState.factions.partition { case (fid, _) => gameState.factions.partition {
removedFactionIds(fid) case (fid, _) =>
removedFactionIds(fid)
} match { } match {
case (removed, remaining) => case (removed, remaining) =>
gameState.update( gameState.update(
@@ -643,16 +623,17 @@ class ActionResultProtoApplierImpl(validator: Validator)
_.factions := remaining _.factions := remaining
) )
} }
}
def applyRemovedHeroes(removedHeroIds: Set[HeroId]): GameState = def applyRemovedHeroes(removedHeroIds: Set[HeroId]): GameState =
gameState.heroes.partition { case (k, _) => gameState.heroes.partition {
removedHeroIds(k) case (k, _) =>
removedHeroIds(k)
} match { } match {
case (removed, remaining) => case (removed, remaining) =>
gameState.update( gameState.update(
_.killedHeroes :++= removed.map { case (hid, hero) => _.killedHeroes :++= removed.map {
(hid, hero.clearFactionId) case (hid, hero) =>
(hid, hero.clearFactionId)
}, },
_.heroes := remaining _.heroes := remaining
) )
@@ -670,12 +651,11 @@ class ActionResultProtoApplierImpl(validator: Validator)
actionResultType: ActionResultType, actionResultType: ActionResultType,
notification: Option[Notification] notification: Option[Notification]
): GameState = ): GameState =
notification notification.map { note =>
.map { note => gameState.update(
gameState.update( _.deferredNotifications :+= note
_.deferredNotifications :+= note )
) }
}
.getOrElse(gameState) .getOrElse(gameState)
def applyRemovedNotifications( def applyRemovedNotifications(
@@ -727,36 +707,34 @@ class ActionResultProtoApplierImpl(validator: Validator)
) )
) ++ cf.updatedReconnedProvinces ) ++ cf.updatedReconnedProvinces
}, },
_.factionRelationships _.factionRelationships.modify { (relationships: Seq[FactionRelationship]) =>
.modify { (relationships: Seq[FactionRelationship]) => cf.trustLevelUpdates
cf.trustLevelUpdates .foldLeft(relationships) {
.foldLeft(relationships) { (
( relationships: Seq[FactionRelationship],
relationships: Seq[FactionRelationship], update: TrustLevelUpdate
update: TrustLevelUpdate ) =>
) => val existingRelationship =
val existingRelationship = relationships
relationships .find(
.find( _.targetFactionId == update.targetFactionId
_.targetFactionId == update.targetFactionId )
.getOrElse(
FactionRelationship(
targetFactionId = update.targetFactionId,
relationshipLevel = FactionRelationship.RelationshipLevel.HOSTILE,
trustValue = 0
) )
.getOrElse( )
FactionRelationship( val newValue = Math.min(
targetFactionId = update.targetFactionId, trustMax,
relationshipLevel = update.delta + existingRelationship.trustValue
FactionRelationship.RelationshipLevel.HOSTILE, )
trustValue = 0 relationships.filterNot(
) _.targetFactionId == update.targetFactionId
) ) :+ existingRelationship.withTrustValue(newValue)
val newValue = Math.min( }
trustMax, },
update.delta + existingRelationship.trustValue
)
relationships.filterNot(
_.targetFactionId == update.targetFactionId
) :+ existingRelationship.withTrustValue(newValue)
}
},
_.lastActedProvinceIdThisRound.modify { lpid => _.lastActedProvinceIdThisRound.modify { lpid =>
if cf.clearLastActedProvinceId then 0 if cf.clearLastActedProvinceId then 0
else cf.newLastActedProvinceId.getOrElse(lpid) else cf.newLastActedProvinceId.getOrElse(lpid)
@@ -835,58 +813,53 @@ class ActionResultProtoApplierImpl(validator: Validator)
) )
def applyNewBattle(battle: Option[ShardokBattle]): GameState = def applyNewBattle(battle: Option[ShardokBattle]): GameState =
battle battle.map { b =>
.map { b => gameState.update(
gameState.update( _.outstandingBattles.modify(_ :+ b),
_.outstandingBattles.modify(_ :+ b), _.battleCounter.modify(_.max(b.battleIndex))
_.battleCounter.modify(_.max(b.battleIndex)) )
) }
}
.getOrElse(gameState) .getOrElse(gameState)
def applyResolvedBattle(shardokGameId: Option[ShardokGameId]): GameState = def applyResolvedBattle(shardokGameId: Option[ShardokGameId]): GameState =
gameState gameState
.withOutstandingBattles( .withOutstandingBattles(
gameState.outstandingBattles.filterNot(batt => gameState.outstandingBattles.filterNot(batt => shardokGameId.contains(batt.shardokGameId))
shardokGameId.contains(batt.shardokGameId)
)
) )
def applyNewSeed(newSeed: Option[Long]): GameState = def applyNewSeed(newSeed: Option[Long]): GameState =
newSeed.map { s => gameState.withRandomSeed(s) }.getOrElse(gameState) newSeed.map(s => gameState.withRandomSeed(s)).getOrElse(gameState)
def applyChronicleEntry( def applyChronicleEntry(
chronicleEntry: Option[ChronicleEntry] chronicleEntry: Option[ChronicleEntry]
): GameState = ): GameState =
chronicleEntry chronicleEntry.map { ce =>
.map { ce => gameState.update(
gameState.update( _.chronicleEntries.modify { ces =>
_.chronicleEntries.modify { ces => ces.indexWhere(_.date == ce.date) match {
ces.indexWhere(_.date == ce.date) match { case -1 => ces :+ ce
case -1 => ces :+ ce case idx => ces.updated(idx, ce)
case idx => ces.updated(idx, ce)
}
} }
) }
} )
}
.getOrElse(gameState) .getOrElse(gameState)
def applyCommandCountUpdate(actingFactionId: Option[FactionId]): GameState = def applyCommandCountUpdate(actingFactionId: Option[FactionId]): GameState =
actingFactionId actingFactionId.map { factionId =>
.map { factionId => val existingCount =
val existingCount = gameState.factionCommandCounts.getOrElse(factionId, 0)
gameState.factionCommandCounts.getOrElse(factionId, 0) gameState.update(
gameState.update( _.factionCommandCounts(factionId) := existingCount + 1
_.factionCommandCounts(factionId) := existingCount + 1 )
) }
}
.getOrElse(gameState) .getOrElse(gameState)
} }
override def applyActionResults( override def applyActionResults(
startingState: GameState, startingState: GameState,
results: Iterable[ActionResult] results: Iterable[ActionResult]
): Vector[ActionWithResultingState] = { ): Vector[ActionWithResultingState] =
results results
.foldLeft((startingState, Vector.empty[ActionWithResultingState])) { .foldLeft((startingState, Vector.empty[ActionWithResultingState])) {
case ((gameState, acc), result) => case ((gameState, acc), result) =>
@@ -896,7 +869,7 @@ class ActionResultProtoApplierImpl(validator: Validator)
} }
} }
._2 ._2
}.toVector .toVector
override def applyActionResult( override def applyActionResult(
startingState: GameState, startingState: GameState,
@@ -13,8 +13,7 @@ object ActionResultTApplierImpl {
new ActionResultTApplierImpl(protoApplier) new ActionResultTApplierImpl(protoApplier)
} }
class ActionResultTApplierImpl(protoApplier: ActionResultProtoApplier) class ActionResultTApplierImpl(protoApplier: ActionResultProtoApplier) extends ActionResultTApplier {
extends ActionResultTApplier {
override def xpForStatBump(stat: Int): Int = protoApplier.xpForStatBump(stat) override def xpForStatBump(stat: Int): Int = protoApplier.xpForStatBump(stat)
override def applyActionResults( override def applyActionResults(
@@ -26,11 +25,12 @@ class ActionResultTApplierImpl(protoApplier: ActionResultProtoApplier)
results.map(ActionResultProtoConverter.toProto) results.map(ActionResultProtoConverter.toProto)
) )
.zip(results) .zip(results)
.map { case (actionWithResultingState, actionResult) => .map {
ActionResultTWithResultingState( case (actionWithResultingState, actionResult) =>
actionResult = actionResult, ActionResultTWithResultingState(
resultingState = actionWithResultingState.gameState actionResult = actionResult,
) resultingState = actionWithResultingState.gameState
)
} }
override def applyActionResult( override def applyActionResult(
@@ -12,24 +12,21 @@ object AvailableAlmsCommandFactory extends AvailableCommandsFactoryForType {
.rulingFactionHeroIds .rulingFactionHeroIds
.toVector .toVector
.filter(hid => gs.heroes(hid).vigor >= MinVigorForAlms.doubleValue) .filter(hid => gs.heroes(hid).vigor >= MinVigorForAlms.doubleValue)
.sortBy(hid => .sortBy(hid => (!gs.heroes(hid).profession.isPaladin, -gs.heroes(hid).vigor))
(!gs.heroes(hid).profession.isPaladin, -gs.heroes(hid).vigor)
)
override def availableCommand( override def availableCommand(
gameState: GameState, gameState: GameState,
factionId: FactionId, factionId: FactionId,
provinceId: ProvinceId provinceId: ProvinceId
): Option[AlmsAvailableCommand] = { ): Option[AlmsAvailableCommand] = {
val hids = availableHeroIds(gameState, provinceId) val hids = availableHeroIds(gameState, provinceId)
val province = gameState.provinces(provinceId) val province = gameState.provinces(provinceId)
Option.when(hids.nonEmpty && province.food > 0) { Option.when(hids.nonEmpty && province.food > 0) {
AlmsAvailableCommand( AlmsAvailableCommand(
availableHeroIds = hids, availableHeroIds = hids,
actingProvinceId = provinceId, actingProvinceId = provinceId,
foodAvailable = foodAvailable = Math.min(MaxAlmsFood.intValue, gameState.provinces(provinceId).food)
Math.min(MaxAlmsFood.intValue, gameState.provinces(provinceId).food)
) )
} }
} }
@@ -1,10 +1,7 @@
package net.eagle0.eagle.library.actions.availability package net.eagle0.eagle.library.actions.availability
import net.eagle0.eagle.{FactionId, ProvinceId} import net.eagle0.eagle.{FactionId, ProvinceId}
import net.eagle0.eagle.api.available_command.{ import net.eagle0.eagle.api.available_command.{ApprehendOutlawAvailableCommand, ResidentOutlaw}
ApprehendOutlawAvailableCommand,
ResidentOutlaw
}
import net.eagle0.eagle.common.unaffiliated_hero_type.UnaffiliatedHeroType.UNAFFILIATED_HERO_OUTLAW import net.eagle0.eagle.common.unaffiliated_hero_type.UnaffiliatedHeroType.UNAFFILIATED_HERO_OUTLAW
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.hero.Hero import net.eagle0.eagle.internal.hero.Hero
@@ -13,8 +10,7 @@ import net.eagle0.eagle.internal.unaffiliated_hero.UnaffiliatedHero
import net.eagle0.eagle.library.settings.MinVigorForApprehendOutlaw import net.eagle0.eagle.library.settings.MinVigorForApprehendOutlaw
import net.eagle0.eagle.library.util.view_filters.HeroViewFilter import net.eagle0.eagle.library.util.view_filters.HeroViewFilter
object AvailableApprehendOutlawCommandFactory object AvailableApprehendOutlawCommandFactory extends AvailableCommandsFactoryForType {
extends AvailableCommandsFactoryForType {
private def availableToCapture(province: Province): Vector[UnaffiliatedHero] = private def availableToCapture(province: Province): Vector[UnaffiliatedHero] =
province.unaffiliatedHeroes province.unaffiliatedHeroes
@@ -36,8 +32,8 @@ object AvailableApprehendOutlawCommandFactory
provinceId: ProvinceId provinceId: ProvinceId
): Option[ApprehendOutlawAvailableCommand] = { ): Option[ApprehendOutlawAvailableCommand] = {
val province = gameState.provinces(provinceId) val province = gameState.provinces(provinceId)
val outlaws = availableToCapture(province) val outlaws = availableToCapture(province)
val actors = availableActors(gameState, province) val actors = availableActors(gameState, province)
Option.when(outlaws.nonEmpty && actors.nonEmpty) { Option.when(outlaws.nonEmpty && actors.nonEmpty) {
ApprehendOutlawAvailableCommand( ApprehendOutlawAvailableCommand(
@@ -1,16 +1,12 @@
package net.eagle0.eagle.library.actions.availability package net.eagle0.eagle.library.actions.availability
import net.eagle0.eagle.{FactionId, ProvinceId} import net.eagle0.eagle.{FactionId, ProvinceId}
import net.eagle0.eagle.api.available_command.{ import net.eagle0.eagle.api.available_command.{ArmTroopsAvailableCommand, ArmamentCost}
ArmTroopsAvailableCommand,
ArmamentCost
}
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
import net.eagle0.eagle.library.util.LegacyBattalionUtils import net.eagle0.eagle.library.util.LegacyBattalionUtils
object AvailableArmTroopsCommandFactory object AvailableArmTroopsCommandFactory extends AvailableCommandsFactoryForType {
extends AvailableCommandsFactoryForType {
override def availableCommand( override def availableCommand(
gameState: GameState, gameState: GameState,
factionId: FactionId, factionId: FactionId,
@@ -59,8 +55,7 @@ object AvailableArmTroopsCommandFactory
actingProvinceId = provinceId, actingProvinceId = provinceId,
availableBattalions = affordableBattalions.map(_.id), availableBattalions = affordableBattalions.map(_.id),
armamentCosts = armamentCosts, armamentCosts = armamentCosts,
maxArmament = maxArmament = LegacyProvinceUtils.effectiveInfrastructure(province).floor.toInt,
LegacyProvinceUtils.effectiveInfrastructure(province).floor.toInt,
availableBattalionTypeIds = gameState.battalionTypes.map(_.typeId) availableBattalionTypeIds = gameState.battalionTypes.map(_.typeId)
) )
) )
@@ -19,8 +19,7 @@ import net.eagle0.eagle.library.util.{IncomingArmyUtils, ProvinceDistances}
import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils
import net.eagle0.eagle.library.util.EagleRequire.internalRequire import net.eagle0.eagle.library.util.EagleRequire.internalRequire
object AvailableAttackDecisionCommandFactory object AvailableAttackDecisionCommandFactory extends AvailableCommandsFactoryForType {
extends AvailableCommandsFactoryForType {
// We need to include the armies that have already withdrawn or advanced, in a consistent sort order, // We need to include the armies that have already withdrawn or advanced, in a consistent sort order,
// so that the player doesn't learn what others have done by the shifts. // so that the player doesn't learn what others have done by the shifts.
@@ -72,8 +71,7 @@ object AvailableAttackDecisionCommandFactory
factionId = defenderFid, factionId = defenderFid,
heroCount = heroCount, heroCount = heroCount,
troopCount = troopCount, troopCount = troopCount,
hostility = hostility = LegacyFactionUtils.hostilityStatus(defenderFid, attackerFid, gs),
LegacyFactionUtils.hostilityStatus(defenderFid, attackerFid, gs),
originProvinceId = pid originProvinceId = pid
) )
} }
@@ -219,24 +217,21 @@ object AvailableAttackDecisionCommandFactory
s"Incoming armies in attack decision phase are not mutually allied in province $provinceId" s"Incoming armies in attack decision phase are not mutually allied in province $provinceId"
) )
myDecidingHostileArmies(provinceId, factionId, gameState).flatMap { myDecidingHostileArmies(provinceId, factionId, gameState).flatMap { hostileArmyGroup =>
hostileArmyGroup => decisionOptions(gameState, provinceId, factionId) match {
decisionOptions(gameState, provinceId, factionId) match { case items if items.isEmpty => None
case items if items.isEmpty => None case nonemptyDecisionOptions =>
case nonemptyDecisionOptions => Some(
Some( AttackDecisionAvailableCommand(
AttackDecisionAvailableCommand( actingProvinceId = provinceId,
actingProvinceId = provinceId, armies = armyStats(factionId, provinceId, gameState),
armies = armyStats(factionId, provinceId, gameState), actingUnits = hostileArmyGroup.armies
actingUnits = hostileArmyGroup.armies .flatMap(_.getArmy.units)
.flatMap(_.getArmy.units) .map(cu => ExpandedCombatUnitUtils.expandedCombatUnit(cu, gameState)),
.map(cu => availableDecisions = nonemptyDecisionOptions
ExpandedCombatUnitUtils.expandedCombatUnit(cu, gameState)
),
availableDecisions = nonemptyDecisionOptions
)
) )
} )
}
} }
} }
} }
@@ -38,7 +38,7 @@ object AvailableCommandsFactory {
case (_: OrganizeTroopsSelectedCommand, _) => false case (_: OrganizeTroopsSelectedCommand, _) => false
case (_: ExileVassalSelectedCommand, _) => false case (_: ExileVassalSelectedCommand, _) => false
case (_: SwearBrotherhoodSelectedCommand, _) => false case (_: SwearBrotherhoodSelectedCommand, _) => false
case (last, next) => AvailableCommandTypeMap.matches(next, last) case (last, next) => AvailableCommandTypeMap.matches(next, last)
} }
def suggestedProvinceId( def suggestedProvinceId(
@@ -74,22 +74,20 @@ object AvailableCommandsFactory {
} }
class AvailableCommandsFactory( class AvailableCommandsFactory(
private val travelingFactories: Vector[AvailableCommandsFactoryForType] = private val travelingFactories: Vector[AvailableCommandsFactoryForType] = Vector(
Vector( AvailableRecruitHeroesCommandFactory,
AvailableRecruitHeroesCommandFactory, AvailableDeclineQuestCommandsFactory,
AvailableDeclineQuestCommandsFactory, AvailableDivineCommandsFactory,
AvailableDivineCommandsFactory, AvailableArmTroopsCommandFactory,
AvailableArmTroopsCommandFactory, AvailableTradeCommandFactory,
AvailableTradeCommandFactory, AvailableManagePrisonersCommandFactory,
AvailableManagePrisonersCommandFactory, AvailableReturnCommandsFactory
AvailableReturnCommandsFactory ),
), private val handleRiotFactories: Vector[AvailableCommandsFactoryForType] = Vector(
private val handleRiotFactories: Vector[AvailableCommandsFactoryForType] = AvailableHandleRiotCrackDownCommandFactory,
Vector( AvailableHandleRiotDoNothingCommandFactory,
AvailableHandleRiotCrackDownCommandFactory, AvailableHandleRiotGiveCommandFactory
AvailableHandleRiotDoNothingCommandFactory, ),
AvailableHandleRiotGiveCommandFactory
),
private val freeForAllDecisionPhaseFactories: Vector[ private val freeForAllDecisionPhaseFactories: Vector[
AvailableCommandsFactoryForType AvailableCommandsFactoryForType
] = Vector( ] = Vector(
@@ -100,33 +98,31 @@ class AvailableCommandsFactory(
] = Vector( ] = Vector(
AvailableAttackDecisionCommandFactory AvailableAttackDecisionCommandFactory
), ),
private val defensePhaseFactories: Vector[AvailableCommandsFactoryForType] = private val defensePhaseFactories: Vector[AvailableCommandsFactoryForType] = Vector(
Vector( AvailableResolveTributeCommandsFactory,
AvailableResolveTributeCommandsFactory, AvailableDefendCommandsFactory
AvailableDefendCommandsFactory ),
), private val commandPhaseFactories: Vector[AvailableCommandsFactoryForType] = Vector(
private val commandPhaseFactories: Vector[AvailableCommandsFactoryForType] = AvailableApprehendOutlawCommandFactory,
Vector( AvailableControlWeatherCommandsFactory,
AvailableApprehendOutlawCommandFactory, AvailableDiplomacyCommandsFactory,
AvailableControlWeatherCommandsFactory, AvailableExileVassalCommandFactory,
AvailableDiplomacyCommandsFactory, AvailableFeastCommandFactory,
AvailableExileVassalCommandFactory, AvailableHeroGiftCommandFactory,
AvailableFeastCommandFactory, AvailableImproveCommandsFactory,
AvailableHeroGiftCommandFactory, AvailableMarchCommandFactory,
AvailableImproveCommandsFactory, AvailableIssueOrdersCommandFactory,
AvailableMarchCommandFactory, AvailableOrganizeTroopsCommandsFactory,
AvailableIssueOrdersCommandFactory, AvailableAlmsCommandFactory,
AvailableOrganizeTroopsCommandsFactory, AvailableReconCommandFactory,
AvailableAlmsCommandFactory, AvailableRestCommandsFactory,
AvailableReconCommandFactory, AvailableSendSuppliesCommandFactory,
AvailableRestCommandsFactory, AvailableStartEpidemicCommandFactory,
AvailableSendSuppliesCommandFactory, AvailableSuppressBeastsCommandFactory,
AvailableStartEpidemicCommandFactory, AvailableSwearBrotherhoodCommandFactory,
AvailableSuppressBeastsCommandFactory, AvailableTrainCommandsFactory,
AvailableSwearBrotherhoodCommandFactory, AvailableTravelCommandsFactory
AvailableTrainCommandsFactory, )
AvailableTravelCommandsFactory
)
) { ) {
private def allCommands( private def allCommands(
factories: Vector[AvailableCommandsFactoryForType], factories: Vector[AvailableCommandsFactoryForType],
@@ -181,13 +177,13 @@ class AvailableCommandsFactory(
pid: ProvinceId, pid: ProvinceId,
commands: Vector[AvailableCommand] commands: Vector[AvailableCommand]
): Int = ): Int =
commands.zipWithIndex commands.zipWithIndex.find {
.find { case (ac, _) => case (ac, _) =>
gs.provinces gs.provinces
.get(pid) .get(pid)
.map(_.lastCommand) .map(_.lastCommand)
.forall(last => AvailableCommandsFactory.shouldFollow(last, ac)) .forall(last => AvailableCommandsFactory.shouldFollow(last, ac))
} }
.map(_._2) .map(_._2)
.getOrElse(0) .getOrElse(0)
@@ -220,8 +216,7 @@ class AvailableCommandsFactory(
gs = gs, gs = gs,
pid = pid, pid = pid,
commands = commands =
if gs.provinces(pid).rulerIsTraveling then if gs.provinces(pid).rulerIsTraveling then allCommands(travelingFactories, gs, fid(gs, pid).get, pid)
allCommands(travelingFactories, gs, fid(gs, pid).get, pid)
else else
allCommands( allCommands(
commandPhaseFactories, commandPhaseFactories,
@@ -235,8 +230,7 @@ class AvailableCommandsFactory(
gs: GameState, gs: GameState,
pid: ProvinceId pid: ProvinceId
): Boolean = ): Boolean =
if gs.provinces(pid).rulerIsTraveling then if gs.provinces(pid).rulerIsTraveling then hasCommand(travelingFactories, gs, fid(gs, pid).get, pid)
hasCommand(travelingFactories, gs, fid(gs, pid).get, pid)
else else
hasCommand( hasCommand(
commandPhaseFactories, commandPhaseFactories,
@@ -440,27 +434,19 @@ class AvailableCommandsFactory(
gs: GameState, gs: GameState,
fid: FactionId fid: FactionId
): Boolean = ): Boolean =
factionLeaderProvinces(gs, fid).exists(p => factionLeaderProvinces(gs, fid).exists(p => defensePhaseCommandsForProvince(gs, p.id).isDefined)
defensePhaseCommandsForProvince(gs, p.id).isDefined
)
def hasAvailableHandleRiotPhaseCommands(gs: GameState): Boolean = def hasAvailableHandleRiotPhaseCommands(gs: GameState): Boolean =
gs.factions.keys.exists(fid => gs.factions.keys.exists(fid => handleRiotPhaseCommandsForFactionLeader(gs, fid).nonEmpty)
handleRiotPhaseCommandsForFactionLeader(gs, fid).nonEmpty
)
def hasAvailablePlayerCommandsPhaseCommands(gs: GameState): Boolean = def hasAvailablePlayerCommandsPhaseCommands(gs: GameState): Boolean =
gs.factions.keys.exists(fid => hasCommandsPhaseCommandsForPlayer(gs, fid)) gs.factions.keys.exists(fid => hasCommandsPhaseCommandsForPlayer(gs, fid))
def hasAvailableFreeForAllDecisionPhaseCommands(gs: GameState): Boolean = def hasAvailableFreeForAllDecisionPhaseCommands(gs: GameState): Boolean =
gs.factions.keys.exists(fid => gs.factions.keys.exists(fid => freeForAllDecisionPhasePlayerCommands(gs, fid).nonEmpty)
freeForAllDecisionPhasePlayerCommands(gs, fid).nonEmpty
)
def hasAvailableAttackDecisionPhaseCommands(gs: GameState): Boolean = def hasAvailableAttackDecisionPhaseCommands(gs: GameState): Boolean =
gs.factions.keys.exists(fid => gs.factions.keys.exists(fid => attackDecisionPhasePlayerCommands(gs, fid).nonEmpty)
attackDecisionPhasePlayerCommands(gs, fid).nonEmpty
)
def hasAvailablePlayerDefenseCommands(gs: GameState): Boolean = def hasAvailablePlayerDefenseCommands(gs: GameState): Boolean =
gs.factions.keys.exists(fid => hasPlayerDefenseCommandsForPlayer(gs, fid)) gs.factions.keys.exists(fid => hasPlayerDefenseCommandsForPlayer(gs, fid))
@@ -471,30 +457,30 @@ class AvailableCommandsFactory(
): SortedMap[ProvinceId, OneProvinceAvailableCommands] = ): SortedMap[ProvinceId, OneProvinceAvailableCommands] =
( (
for { for {
faction <- gs.factions.get(fid) faction <- gs.factions.get(fid)
diplomacyCommand <- AvailableResolveInvitationCommandFactory diplomacyCommand <- AvailableResolveInvitationCommandFactory
.availableCommand(gs, faction.id) .availableCommand(gs, faction.id)
.orElse( .orElse(
AvailableResolveTruceOfferCommandFactory AvailableResolveTruceOfferCommandFactory
.availableCommand(gs, faction.id) .availableCommand(gs, faction.id)
) )
.orElse( .orElse(
AvailableResolveRansomOfferCommandFactory AvailableResolveRansomOfferCommandFactory
.availableCommand(gs, faction.id) .availableCommand(gs, faction.id)
) )
.orElse( .orElse(
AvailableResolveAllianceOfferCommandFactory AvailableResolveAllianceOfferCommandFactory
.availableCommand(gs, faction.id) .availableCommand(gs, faction.id)
) )
.orElse( .orElse(
AvailableResolveBreakAllianceCommandFactory AvailableResolveBreakAllianceCommandFactory
.availableCommand(gs, faction.id) .availableCommand(gs, faction.id)
) )
oneProvinceAvailableCommand <- optionalOneProvinceAvailableCommands( oneProvinceAvailableCommand <- optionalOneProvinceAvailableCommands(
gs = gs, gs = gs,
0, 0,
Vector(diplomacyCommand) Vector(diplomacyCommand)
) )
} yield SortedMap(0 -> oneProvinceAvailableCommand) } yield SortedMap(0 -> oneProvinceAvailableCommand)
).getOrElse(SortedMap.empty) ).getOrElse(SortedMap.empty)
@@ -504,14 +490,14 @@ class AvailableCommandsFactory(
): SortedMap[ProvinceId, OneProvinceAvailableCommands] = ): SortedMap[ProvinceId, OneProvinceAvailableCommands] =
( (
for { for {
faction <- gs.factions.get(fid) faction <- gs.factions.get(fid)
pleaseRecruitMeCommand <- AvailablePleaseRecruitMeCommandFactory pleaseRecruitMeCommand <- AvailablePleaseRecruitMeCommandFactory
.availableCommand(gs, faction.id) .availableCommand(gs, faction.id)
oneProvinceAvailableCommand <- optionalOneProvinceAvailableCommands( oneProvinceAvailableCommand <- optionalOneProvinceAvailableCommands(
gs = gs, gs = gs,
0, 0,
Vector(pleaseRecruitMeCommand) Vector(pleaseRecruitMeCommand)
) )
} yield SortedMap(0 -> oneProvinceAvailableCommand) } yield SortedMap(0 -> oneProvinceAvailableCommand)
).getOrElse(SortedMap.empty) ).getOrElse(SortedMap.empty)
@@ -527,9 +513,7 @@ class AvailableCommandsFactory(
LegacyFactionUtils LegacyFactionUtils
.provinces(fid, gs) .provinces(fid, gs)
.map(_.id) .map(_.id)
.flatMap(pid => .flatMap(pid => availableAftermathCommandsForProvince(gs, pid).map(pid -> _))*
availableAftermathCommandsForProvince(gs, pid).map(pid -> _)
)*
) )
def availablePlayerCommands( def availablePlayerCommands(
@@ -537,42 +521,41 @@ class AvailableCommandsFactory(
fid: FactionId fid: FactionId
): SortedMap[ProvinceId, OneProvinceAvailableCommands] = ): SortedMap[ProvinceId, OneProvinceAvailableCommands] =
gs.currentPhase match { gs.currentPhase match {
case HANDLE_RIOT => handleRiotPhaseCommandsForFactionLeader(gs, fid) case HANDLE_RIOT => handleRiotPhaseCommandsForFactionLeader(gs, fid)
case PLEASE_RECRUIT_ME => pleaseRecruitMePhasePlayerCommands(gs, fid) case PLEASE_RECRUIT_ME => pleaseRecruitMePhasePlayerCommands(gs, fid)
case PLAYER_COMMANDS => commandsPhasePlayerCommands(gs, fid) case PLAYER_COMMANDS => commandsPhasePlayerCommands(gs, fid)
case FREE_FOR_ALL_DECISION => case FREE_FOR_ALL_DECISION =>
freeForAllDecisionPhasePlayerCommands(gs, fid) freeForAllDecisionPhasePlayerCommands(gs, fid)
case ATTACK_DECISION => attackDecisionPhasePlayerCommands(gs, fid) case ATTACK_DECISION => attackDecisionPhasePlayerCommands(gs, fid)
case DEFENSE_DECISION => defensePhasePlayerCommands(gs, fid) case DEFENSE_DECISION => defensePhasePlayerCommands(gs, fid)
case BATTLE_AFTERMATH => availableAftermathPhasePlayerCommands(gs, fid) case BATTLE_AFTERMATH => availableAftermathPhasePlayerCommands(gs, fid)
case DIPLOMACY_RESOLUTION => case DIPLOMACY_RESOLUTION =>
diplomacyResolutionPhasePlayerCommands(gs, fid) diplomacyResolutionPhasePlayerCommands(gs, fid)
case _ => SortedMap.empty[ProvinceId, OneProvinceAvailableCommands] case _ => SortedMap.empty[ProvinceId, OneProvinceAvailableCommands]
} }
def hasAvailablePlayerCommands(gs: GameState): Boolean = { def hasAvailablePlayerCommands(gs: GameState): Boolean =
gs.currentPhase match { gs.currentPhase match {
case DIPLOMACY_RESOLUTION => case DIPLOMACY_RESOLUTION =>
hasAvailableDiplomacyResolutionPhaseCommands(gs) hasAvailableDiplomacyResolutionPhaseCommands(gs)
case HANDLE_RIOT => case HANDLE_RIOT =>
hasAvailableHandleRiotPhaseCommands(gs) hasAvailableHandleRiotPhaseCommands(gs)
case PLEASE_RECRUIT_ME => case PLEASE_RECRUIT_ME =>
hasAvailablePleaseRecruitMePhaseCommands(gs) hasAvailablePleaseRecruitMePhaseCommands(gs)
case DEFENSE_DECISION => case DEFENSE_DECISION =>
hasAvailablePlayerDefenseCommands(gs) hasAvailablePlayerDefenseCommands(gs)
case ATTACK_DECISION => case ATTACK_DECISION =>
hasAvailableAttackDecisionPhaseCommands(gs) hasAvailableAttackDecisionPhaseCommands(gs)
case FREE_FOR_ALL_DECISION => case FREE_FOR_ALL_DECISION =>
hasAvailableFreeForAllDecisionPhaseCommands(gs) hasAvailableFreeForAllDecisionPhaseCommands(gs)
case PLAYER_COMMANDS => case PLAYER_COMMANDS =>
gs.factions.keys.exists { fid => gs.factions.keys.exists { fid =>
hasCommandsPhaseCommandsForPlayer(gs, fid) hasCommandsPhaseCommandsForPlayer(gs, fid)
} }
case BATTLE_AFTERMATH => case BATTLE_AFTERMATH =>
gs.factions.keys.exists { fid => gs.factions.keys.exists { fid =>
availableAftermathPhasePlayerCommands(gs, fid).nonEmpty availableAftermathPhasePlayerCommands(gs, fid).nonEmpty
} }
case _ => false case _ => false
} }
}
} }
@@ -14,8 +14,7 @@ import net.eagle0.eagle.internal.province.Province
import net.eagle0.eagle.library.settings.MinVigorForControlWeather import net.eagle0.eagle.library.settings.MinVigorForControlWeather
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
object AvailableControlWeatherCommandsFactory object AvailableControlWeatherCommandsFactory extends AvailableCommandsFactoryForType {
extends AvailableCommandsFactoryForType {
private def makeControlWeatherCommand( private def makeControlWeatherCommand(
gameState: GameState, gameState: GameState,
@@ -34,12 +33,10 @@ object AvailableControlWeatherCommandsFactory
TargetProvinceOptions( TargetProvinceOptions(
provinceId = pid, provinceId = pid,
controlWeatherTypes = ( controlWeatherTypes = (
if LegacyProvinceUtils.hasBlizzard(gameState.provinces(pid)) then if LegacyProvinceUtils.hasBlizzard(gameState.provinces(pid)) then Vector(CONTROL_WEATHER_END_BLIZZARD)
Vector(CONTROL_WEATHER_END_BLIZZARD)
else Vector(CONTROL_WEATHER_START_BLIZZARD) else Vector(CONTROL_WEATHER_START_BLIZZARD)
) ++ ( ) ++ (
if LegacyProvinceUtils.hasDrought(gameState.provinces(pid)) then if LegacyProvinceUtils.hasDrought(gameState.provinces(pid)) then Vector(CONTROL_WEATHER_END_DROUGHT)
Vector(CONTROL_WEATHER_END_DROUGHT)
else Vector(CONTROL_WEATHER_START_DROUGHT) else Vector(CONTROL_WEATHER_START_DROUGHT)
) )
) )
@@ -8,8 +8,7 @@ import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.library.actions.availability.ExpandedUnaffiliatedHeroUtils.expandedUnaffiliatedHero import net.eagle0.eagle.library.actions.availability.ExpandedUnaffiliatedHeroUtils.expandedUnaffiliatedHero
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
object AvailableDeclineQuestCommandsFactory object AvailableDeclineQuestCommandsFactory extends AvailableCommandsFactoryForType {
extends AvailableCommandsFactoryForType {
override def availableCommand( override def availableCommand(
gameState: GameState, gameState: GameState,
factionId: FactionId, factionId: FactionId,
@@ -27,9 +26,7 @@ object AvailableDeclineQuestCommandsFactory
Option.when(withQuests.nonEmpty) { Option.when(withQuests.nonEmpty) {
DeclineQuestAvailableCommand( DeclineQuestAvailableCommand(
actingProvinceId = provinceId, actingProvinceId = provinceId,
declinableHeroes = withQuests.map(uh => declinableHeroes = withQuests.map(uh => expandedUnaffiliatedHero(gs = gameState, uh = uh))
expandedUnaffiliatedHero(gs = gameState, uh = uh)
)
) )
} }
} }
@@ -6,14 +6,8 @@ import net.eagle0.eagle.api.command.util.battalion_with_food_cost.BattalionWithF
import net.eagle0.eagle.internal.army.Attacking import net.eagle0.eagle.internal.army.Attacking
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.province.Province import net.eagle0.eagle.internal.province.Province
import net.eagle0.eagle.library.settings.{ import net.eagle0.eagle.library.settings.{MaxCombatUnitCountPerSide, MinVigorForDefend}
MaxCombatUnitCountPerSide, import net.eagle0.eagle.library.util.{BattalionSuitability, LegacyBattalionUtils}
MinVigorForDefend
}
import net.eagle0.eagle.library.util.{
BattalionSuitability,
LegacyBattalionUtils
}
import net.eagle0.eagle.library.util.command_choice_helpers.CombatUnitSelector import net.eagle0.eagle.library.util.command_choice_helpers.CombatUnitSelector
import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils
@@ -52,7 +46,7 @@ object AvailableDefendCommandsFactory extends AvailableCommandsFactoryForType {
army.status match { army.status match {
case Attacking(_) => case Attacking(_) =>
true true
case _ => false case _ => false
} }
} }
@@ -64,7 +58,7 @@ object AvailableDefendCommandsFactory extends AvailableCommandsFactoryForType {
.filter(_.vigor >= MinVigorForDefend.doubleValue) .filter(_.vigor >= MinVigorForDefend.doubleValue)
if availableHeroes.isEmpty then return None if availableHeroes.isEmpty then return None
val availableBattalions = province.battalionIds.map(gameState.battalions) val availableBattalions = province.battalionIds.map(gameState.battalions)
val availableBattalionsWithCosts = availableBattalions.map(batt => val availableBattalionsWithCosts = availableBattalions.map(batt =>
BattalionWithFoodCost( BattalionWithFoodCost(
battalionId = batt.id, battalionId = batt.id,
@@ -76,13 +70,12 @@ object AvailableDefendCommandsFactory extends AvailableCommandsFactoryForType {
) )
val recommendedUnits = CombatUnitSelector.selectedCombatBattalions( val recommendedUnits = CombatUnitSelector.selectedCombatBattalions(
desiredCount = desiredCount = availableHeroes.size.min(MaxCombatUnitCountPerSide.intValue),
availableHeroes.size.min(MaxCombatUnitCountPerSide.intValue),
heroes = availableHeroes.toVector, heroes = availableHeroes.toVector,
battalions = availableBattalions.toVector, battalions = availableBattalions.toVector,
battalionTypes = gameState.battalionTypes.toVector battalionTypes = gameState.battalionTypes.toVector
) )
val hostileUnits = hostileArmies val hostileUnits = hostileArmies
.flatMap(_.armies) .flatMap(_.armies)
.flatMap(_.army) .flatMap(_.army)
.flatMap(_.units) .flatMap(_.units)
@@ -93,15 +86,13 @@ object AvailableDefendCommandsFactory extends AvailableCommandsFactoryForType {
.toVector .toVector
.map(_.id), .map(_.id),
actingProvinceId = province.id, actingProvinceId = province.id,
suitableBattalionsForHeroes = suitableBattalionsForHeroes = BattalionSuitability.suitableBattalionsForHeroes(
BattalionSuitability.suitableBattalionsForHeroes( hs = availableHeroes.toVector,
hs = availableHeroes.toVector, bs = availableBattalions.toVector,
bs = availableBattalions.toVector, bts = gameState.battalionTypes.toVector
bts = gameState.battalionTypes.toVector ),
),
availableBattalions = availableBattalionsWithCosts, availableBattalions = availableBattalionsWithCosts,
availableFleeProvinceIds = availableFleeProvinceIds = sortedFleeProvinces(from = province, gs = gameState),
sortedFleeProvinces(from = province, gs = gameState),
recommendedUnits = recommendedUnits, recommendedUnits = recommendedUnits,
hostileFactionIds = hostileArmies.toVector.map(_.factionId).distinct, hostileFactionIds = hostileArmies.toVector.map(_.factionId).distinct,
hostileHeroCount = hostileUnits.size, hostileHeroCount = hostileUnits.size,
@@ -31,8 +31,7 @@ import net.eagle0.eagle.library.util.hero.LegacyHeroUtils
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
import net.eagle0.eagle.library.util.ProvinceDistances import net.eagle0.eagle.library.util.ProvinceDistances
object AvailableDiplomacyCommandsFactory object AvailableDiplomacyCommandsFactory extends AvailableCommandsFactoryForType {
extends AvailableCommandsFactoryForType {
def availableHeroIds(gs: GameState, pid: ProvinceId): Vector[HeroId] = def availableHeroIds(gs: GameState, pid: ProvinceId): Vector[HeroId] =
gs.provinces(pid) gs.provinces(pid)
@@ -42,9 +41,7 @@ object AvailableDiplomacyCommandsFactory
def recommendedHeroId(gs: GameState, availableHids: Seq[HeroId]): HeroId = def recommendedHeroId(gs: GameState, availableHids: Seq[HeroId]): HeroId =
availableHids availableHids
.sortBy(hid => .sortBy(hid => (LegacyHeroUtils.fatigue(gs.heroes(hid)), -gs.heroes(hid).vigor)) match {
(LegacyHeroUtils.fatigue(gs.heroes(hid)), -gs.heroes(hid).vigor)
) match {
case sortedHeroes => case sortedHeroes =>
sortedHeroes sortedHeroes
.find(hid => !LegacyFactionUtils.isFactionHead(hid, gs)) .find(hid => !LegacyFactionUtils.isFactionHead(hid, gs))
@@ -123,38 +120,36 @@ object AvailableDiplomacyCommandsFactory
pid: ProvinceId, pid: ProvinceId,
actingFid: FactionId, actingFid: FactionId,
targetFid: FactionId targetFid: FactionId
): Boolean = { ): Boolean =
LegacyFactionUtils.provinces(targetFid, gs).headOption.exists { LegacyFactionUtils.provinces(targetFid, gs).headOption.exists { targetProvince =>
targetProvince => val eligibleNeighbors: ProvinceId => Vector[ProvinceId] =
val eligibleNeighbors: ProvinceId => Vector[ProvinceId] = p =>
p => gs.provinces(p)
gs.provinces(p) .neighbors
.neighbors .map(_.provinceId)
.map(_.provinceId) .map(gs.provinces)
.map(gs.provinces) .filter(neighborProvince =>
.filter(neighborProvince => // inviting across empty province
// inviting across empty province neighborProvince.rulingFactionId.isEmpty ||
neighborProvince.rulingFactionId.isEmpty || // inviting across your own province
// inviting across your own province neighborProvince.rulingFactionId.contains(actingFid) ||
neighborProvince.rulingFactionId.contains(actingFid) || // inviting across the target's province
// inviting across the target's province neighborProvince.rulingFactionId.contains(targetFid) ||
neighborProvince.rulingFactionId.contains(targetFid) || // inviting across your ally's province
// inviting across your ally's province LegacyFactionUtils
LegacyFactionUtils .hasAlliance(
.hasAlliance( neighborProvince.rulingFactionId.get,
neighborProvince.rulingFactionId.get, actingFid,
actingFid, gs
gs )
) )
) .map(_.id)
.map(_.id) .toVector
.toVector
ProvinceDistances ProvinceDistances
.distance(pid, targetProvince.id, eligibleNeighbors) .distance(pid, targetProvince.id, eligibleNeighbors)
.isDefined .isDefined
} }
}
private def inviteOptionForTargetFaction( private def inviteOptionForTargetFaction(
gs: GameState, gs: GameState,
@@ -191,8 +186,8 @@ object AvailableDiplomacyCommandsFactory
targetFid: FactionId targetFid: FactionId
): Vector[DiplomacyOption] = { ): Vector[DiplomacyOption] = {
val prisonersAvailableToOffer = availablePrisoners(gs.provinces(pid)) val prisonersAvailableToOffer = availablePrisoners(gs.provinces(pid))
val hostagesAvailableToOffer = availableHostages(gs, pid) val hostagesAvailableToOffer = availableHostages(gs, pid)
val goldAvailableToOffer = gs.provinces(pid).gold val goldAvailableToOffer = gs.provinces(pid).gold
if prisonersAvailableToOffer.nonEmpty || hostagesAvailableToOffer.nonEmpty || goldAvailableToOffer > 0 if prisonersAvailableToOffer.nonEmpty || hostagesAvailableToOffer.nonEmpty || goldAvailableToOffer > 0
then then
@@ -200,20 +195,19 @@ object AvailableDiplomacyCommandsFactory
gs, gs,
factionId = actingFid, factionId = actingFid,
targetFactionId = targetFid targetFactionId = targetFid
) ).map { prisonerToBeRansomed =>
.map { prisonerToBeRansomed => RansomOfferOption(
RansomOfferOption( targetFactionId = targetFid,
targetFactionId = targetFid, ransomOffer = Some(
ransomOffer = Some( RansomOfferDetails(
RansomOfferDetails( prisonerToBeRansomed = Some(prisonerToBeRansomed),
prisonerToBeRansomed = Some(prisonerToBeRansomed), prisonersOffered = prisonersAvailableToOffer,
prisonersOffered = prisonersAvailableToOffer, hostagesOffered = hostagesAvailableToOffer,
hostagesOffered = hostagesAvailableToOffer, goldOffered = goldAvailableToOffer
goldOffered = goldAvailableToOffer
)
) )
) )
} )
}
else Vector() else Vector()
end if end if
} }
@@ -264,10 +258,9 @@ object AvailableDiplomacyCommandsFactory
gameState: GameState, gameState: GameState,
factionId: FactionId, factionId: FactionId,
provinceId: ProvinceId provinceId: ProvinceId
): Option[DiplomacyAvailableCommand] = { ): Option[DiplomacyAvailableCommand] =
if availableHeroIds(gameState, provinceId).isEmpty then None if availableHeroIds(gameState, provinceId).isEmpty then None
else if gameState.provinces(provinceId).rulingFactionHeroIds.size < 2 then else if gameState.provinces(provinceId).rulingFactionHeroIds.size < 2 then None
None
else { else {
gameState.factions.keys gameState.factions.keys
.filterNot(_ == factionId) .filterNot(_ == factionId)
@@ -289,7 +282,7 @@ object AvailableDiplomacyCommandsFactory
.toVector .toVector
.sortBy(opt => .sortBy(opt =>
LegacyFactionUtils.sortKey(gameState.factions(opt match { LegacyFactionUtils.sortKey(gameState.factions(opt match {
case TruceOption(targetFactionId, _, _ /* unknownFieldSet */ ) => case TruceOption(targetFactionId, _, _ /* unknownFieldSet */ ) =>
targetFactionId targetFactionId
case InvitationOption( case InvitationOption(
targetFactionId, targetFactionId,
@@ -311,7 +304,7 @@ object AvailableDiplomacyCommandsFactory
_ /* unknownFieldSet */ _ /* unknownFieldSet */
) => ) =>
targetFactionId targetFactionId
case DiplomacyOption.Empty => ??? case DiplomacyOption.Empty => ???
})) }))
) match { ) match {
case items if items.isEmpty => None case items if items.isEmpty => None
@@ -328,7 +321,6 @@ object AvailableDiplomacyCommandsFactory
) )
} }
} }
}
private def availablePrisoners( private def availablePrisoners(
province: Province province: Province
@@ -365,19 +357,18 @@ object AvailableDiplomacyCommandsFactory
gameState: GameState, gameState: GameState,
factionId: FactionId, factionId: FactionId,
targetFactionId: FactionId targetFactionId: FactionId
): Vector[PrisonerToBeRansomed] = { ): Vector[PrisonerToBeRansomed] =
for { for {
province <- gameState.provinces.values province <- gameState.provinces.values
.filter(_.rulingFactionId.contains(targetFactionId)) .filter(_.rulingFactionId.contains(targetFactionId))
.toVector .toVector
prisoner <- province.unaffiliatedHeroes.filter( prisoner <- province.unaffiliatedHeroes.filter(
_.`type` == UNAFFILIATED_HERO_PRISONER _.`type` == UNAFFILIATED_HERO_PRISONER
) )
if prisoner.lastFaction.contains(factionId) if prisoner.lastFaction.contains(factionId)
if LegacyFactionUtils.isFactionLeader(prisoner.heroId, gameState) if LegacyFactionUtils.isFactionLeader(prisoner.heroId, gameState)
} yield PrisonerToBeRansomed( } yield PrisonerToBeRansomed(
prisonerHeroId = prisoner.heroId, prisonerHeroId = prisoner.heroId,
provinceIdForPrisoner = province.id provinceIdForPrisoner = province.id
) )
}
} }
@@ -7,8 +7,7 @@ import net.eagle0.eagle.internal.province.Province
import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
object AvailableExileVassalCommandFactory object AvailableExileVassalCommandFactory extends AvailableCommandsFactoryForType {
extends AvailableCommandsFactoryForType {
def exilableHeroIds( def exilableHeroIds(
gameState: GameState, gameState: GameState,
@@ -3,22 +3,13 @@ package net.eagle0.eagle.library.actions.availability
import net.eagle0.eagle.{FactionId, ProvinceId} import net.eagle0.eagle.{FactionId, ProvinceId}
import net.eagle0.eagle.api.available_command.FreeForAllDecisionAvailableCommand import net.eagle0.eagle.api.available_command.FreeForAllDecisionAvailableCommand
import net.eagle0.eagle.api.command.util.army_stats.ArmyStats import net.eagle0.eagle.api.command.util.army_stats.ArmyStats
import net.eagle0.eagle.api.command.util.attack_decision_type.{ import net.eagle0.eagle.api.command.util.attack_decision_type.{AdvanceDecision, AttackDecisionType, WithdrawDecision}
AdvanceDecision,
AttackDecisionType,
WithdrawDecision
}
import net.eagle0.eagle.common.round_phase.RoundPhase import net.eagle0.eagle.common.round_phase.RoundPhase
import net.eagle0.eagle.internal.army.{ import net.eagle0.eagle.internal.army.{AwaitingDecision, HostileArmyGroup, MovingArmy}
AwaitingDecision,
HostileArmyGroup,
MovingArmy
}
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.library.util.IncomingArmyUtils import net.eagle0.eagle.library.util.IncomingArmyUtils
object AvailableFreeForAllDecisionCommandFactory object AvailableFreeForAllDecisionCommandFactory extends AvailableCommandsFactoryForType {
extends AvailableCommandsFactoryForType {
private def decisionOptions( private def decisionOptions(
armies: Seq[MovingArmy] armies: Seq[MovingArmy]
@@ -58,35 +49,29 @@ object AvailableFreeForAllDecisionCommandFactory
pid: ProvinceId, pid: ProvinceId,
gs: GameState gs: GameState
): Vector[ArmyStats] = ): Vector[ArmyStats] =
relevantArmies(pid, gs).map(ia => relevantArmies(pid, gs).map(ia => IncomingArmyUtils.stats(toFid, ia.getArmy, gs, ia.originProvince))
IncomingArmyUtils.stats(toFid, ia.getArmy, gs, ia.originProvince)
)
override def availableCommand( override def availableCommand(
gameState: GameState, gameState: GameState,
factionId: FactionId, factionId: FactionId,
provinceId: ProvinceId provinceId: ProvinceId
): Option[FreeForAllDecisionAvailableCommand] = { ): Option[FreeForAllDecisionAvailableCommand] =
myHostileArmies(provinceId, factionId, gameState).flatMap { myHostileArmies(provinceId, factionId, gameState).flatMap { hostileArmyGroup =>
hostileArmyGroup => Option.when(
Option.when( gameState.currentPhase == RoundPhase.FREE_FOR_ALL_DECISION
gameState.currentPhase == RoundPhase.FREE_FOR_ALL_DECISION && !IncomingArmyUtils.incomingArmiesAreMutuallyAllied(
&& !IncomingArmyUtils.incomingArmiesAreMutuallyAllied( gameState.provinces(provinceId),
gameState.provinces(provinceId), gameState
gameState
)
)(
FreeForAllDecisionAvailableCommand(
provinceId = provinceId,
armies = armyStats(factionId, provinceId, gameState),
actingUnits = hostileArmyGroup.armies
.flatMap(_.getArmy.units)
.map(cu =>
ExpandedCombatUnitUtils.expandedCombatUnit(cu, gameState)
),
availableDecisions = decisionOptions(hostileArmyGroup.armies)
) )
)(
FreeForAllDecisionAvailableCommand(
provinceId = provinceId,
armies = armyStats(factionId, provinceId, gameState),
actingUnits = hostileArmyGroup.armies
.flatMap(_.getArmy.units)
.map(cu => ExpandedCombatUnitUtils.expandedCombatUnit(cu, gameState)),
availableDecisions = decisionOptions(hostileArmyGroup.armies)
) )
)
} }
}
} }
@@ -1,10 +1,7 @@
package net.eagle0.eagle.library.actions.availability package net.eagle0.eagle.library.actions.availability
import net.eagle0.eagle.{FactionId, ProvinceId} import net.eagle0.eagle.{FactionId, ProvinceId}
import net.eagle0.eagle.api.available_command.{ import net.eagle0.eagle.api.available_command.{ExpandedCapturedHero, HandleCapturedHeroAvailableCommand}
ExpandedCapturedHero,
HandleCapturedHeroAvailableCommand
}
import net.eagle0.eagle.api.command.util.captured_hero_option.CapturedHeroOption.{ import net.eagle0.eagle.api.command.util.captured_hero_option.CapturedHeroOption.{
EXECUTE_CAPTURED_HERO_OPTION, EXECUTE_CAPTURED_HERO_OPTION,
EXILE_CAPTURED_HERO_OPTION, EXILE_CAPTURED_HERO_OPTION,
@@ -17,17 +14,15 @@ import net.eagle0.eagle.internal.unaffiliated_hero.CapturedHero
import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils
import net.eagle0.eagle.library.util.view_filters.HeroViewFilter import net.eagle0.eagle.library.util.view_filters.HeroViewFilter
object AvailableHandleCapturedHeroCommandFactory object AvailableHandleCapturedHeroCommandFactory extends AvailableCommandsFactoryForType {
extends AvailableCommandsFactoryForType {
private val optionsWithoutRecruit = { private val optionsWithoutRecruit =
Vector( Vector(
IMPRISON_CAPTURED_HERO_OPTION, IMPRISON_CAPTURED_HERO_OPTION,
EXILE_CAPTURED_HERO_OPTION, EXILE_CAPTURED_HERO_OPTION,
EXECUTE_CAPTURED_HERO_OPTION EXECUTE_CAPTURED_HERO_OPTION
) )
} private val optionsWithRecruit =
private val optionsWithRecruit =
RECRUIT_CAPTURED_HERO_OPTION +: optionsWithoutRecruit RECRUIT_CAPTURED_HERO_OPTION +: optionsWithoutRecruit
private val optionsForFactionLeader = Vector( private val optionsForFactionLeader = Vector(
IMPRISON_CAPTURED_HERO_OPTION, IMPRISON_CAPTURED_HERO_OPTION,
@@ -72,14 +67,11 @@ object AvailableHandleCapturedHeroCommandFactory
provinceId: ProvinceId provinceId: ProvinceId
): Option[HandleCapturedHeroAvailableCommand] = ): Option[HandleCapturedHeroAvailableCommand] =
capturedHeroes capturedHeroes
.find(ch => .find(ch => ch.recruitmentRefusedMessageId.nonEmpty || ch.messageId.nonEmpty)
ch.recruitmentRefusedMessageId.nonEmpty || ch.messageId.nonEmpty
)
.map { capturedHero => .map { capturedHero =>
HandleCapturedHeroAvailableCommand( HandleCapturedHeroAvailableCommand(
actingProvinceId = provinceId, actingProvinceId = provinceId,
availableHeroes = availableHeroes = Vector(expandedCapturedHero(gameState, capturedHero))
Vector(expandedCapturedHero(gameState, capturedHero))
) )
} }
@@ -87,15 +79,15 @@ object AvailableHandleCapturedHeroCommandFactory
gameState: GameState, gameState: GameState,
factionId: FactionId, factionId: FactionId,
provinceId: ProvinceId provinceId: ProvinceId
): Option[HandleCapturedHeroAvailableCommand] = { ): Option[HandleCapturedHeroAvailableCommand] =
// If we've already attempted to recruit one of the heroes, only show available commands for that one // If we've already attempted to recruit one of the heroes, only show available commands for that one
gameState gameState
.provinces(provinceId) .provinces(provinceId)
.capturedHeroes .capturedHeroes
.partition(_.recruitmentAttempted) match { .partition(_.recruitmentAttempted) match {
case (Vector(), Vector()) => None case (Vector(), Vector()) => None
// If the top hero in either set doesn't have a message, return nothing, otherwise return just the one option // If the top hero in either set doesn't have a message, return nothing, otherwise return just the one option
case (Vector(), notRecruited) => case (Vector(), notRecruited) =>
firstOption( firstOption(
gameState = gameState, gameState = gameState,
capturedHeroes = notRecruited.toVector, capturedHeroes = notRecruited.toVector,
@@ -108,5 +100,4 @@ object AvailableHandleCapturedHeroCommandFactory
provinceId = provinceId provinceId = provinceId
) )
} }
}
} }
@@ -7,17 +7,14 @@ import net.eagle0.eagle.internal.province.Province
import net.eagle0.eagle.library.settings.MinVigorForCrackDown import net.eagle0.eagle.library.settings.MinVigorForCrackDown
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
object AvailableHandleRiotCrackDownCommandFactory object AvailableHandleRiotCrackDownCommandFactory extends AvailableCommandsFactoryForType {
extends AvailableCommandsFactoryForType {
def crackDownHeroIds( def crackDownHeroIds(
province: Province, province: Province,
gameState: GameState gameState: GameState
): Vector[HeroId] = ): Vector[HeroId] =
province.rulingFactionHeroIds province.rulingFactionHeroIds
.filter(hid => .filter(hid => gameState.heroes(hid).vigor >= MinVigorForCrackDown.doubleValue)
gameState.heroes(hid).vigor >= MinVigorForCrackDown.doubleValue
)
.toVector .toVector
override def availableCommand( override def availableCommand(
@@ -26,7 +23,7 @@ object AvailableHandleRiotCrackDownCommandFactory
provinceId: ProvinceId provinceId: ProvinceId
): Option[HandleRiotCrackDownAvailableCommand] = { ): Option[HandleRiotCrackDownAvailableCommand] = {
val province = gameState.provinces(provinceId) val province = gameState.provinces(provinceId)
val heroIds = crackDownHeroIds(province, gameState) val heroIds = crackDownHeroIds(province, gameState)
Option.when( Option.when(
LegacyProvinceUtils LegacyProvinceUtils
@@ -5,8 +5,7 @@ import net.eagle0.eagle.api.available_command.HandleRiotDoNothingAvailableComman
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
object AvailableHandleRiotDoNothingCommandFactory object AvailableHandleRiotDoNothingCommandFactory extends AvailableCommandsFactoryForType {
extends AvailableCommandsFactoryForType {
override def availableCommand( override def availableCommand(
gameState: GameState, gameState: GameState,
factionId: FactionId, factionId: FactionId,
@@ -6,8 +6,7 @@ import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.library.settings.{RiotMaxFood, RiotMaxGold} import net.eagle0.eagle.library.settings.{RiotMaxFood, RiotMaxGold}
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
object AvailableHandleRiotGiveCommandFactory object AvailableHandleRiotGiveCommandFactory extends AvailableCommandsFactoryForType {
extends AvailableCommandsFactoryForType {
override def availableCommand( override def availableCommand(
gameState: GameState, gameState: GameState,
@@ -1,10 +1,7 @@
package net.eagle0.eagle.library.actions.availability package net.eagle0.eagle.library.actions.availability
import net.eagle0.eagle.{FactionId, ProvinceId} import net.eagle0.eagle.{FactionId, ProvinceId}
import net.eagle0.eagle.api.available_command.{ import net.eagle0.eagle.api.available_command.{EligibleGift, HeroGiftAvailableCommand}
EligibleGift,
HeroGiftAvailableCommand
}
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.province.Province import net.eagle0.eagle.internal.province.Province
import net.eagle0.eagle.library.settings.MaxGiftGold import net.eagle0.eagle.library.settings.MaxGiftGold
@@ -31,7 +28,7 @@ object AvailableHeroGiftCommandFactory extends AvailableCommandsFactoryForType {
gameState: GameState, gameState: GameState,
factionId: FactionId, factionId: FactionId,
provinceId: ProvinceId provinceId: ProvinceId
): Option[HeroGiftAvailableCommand] = { ): Option[HeroGiftAvailableCommand] =
consideredProvinces(gameState, factionId, provinceId) consideredProvinces(gameState, factionId, provinceId)
.filterNot(_.gold == 0) .filterNot(_.gold == 0)
.flatMap(p => eligibleRecipients(p, gameState)) match { .flatMap(p => eligibleRecipients(p, gameState)) match {
@@ -44,5 +41,4 @@ object AvailableHeroGiftCommandFactory extends AvailableCommandsFactoryForType {
) )
) )
} }
}
} }
@@ -2,12 +2,7 @@ package net.eagle0.eagle.library.actions.availability
import net.eagle0.eagle.{FactionId, ProvinceId} import net.eagle0.eagle.{FactionId, ProvinceId}
import net.eagle0.eagle.api.available_command.ImproveAvailableCommand import net.eagle0.eagle.api.available_command.ImproveAvailableCommand
import net.eagle0.eagle.common.improvement_type.ImprovementType.{ import net.eagle0.eagle.common.improvement_type.ImprovementType.{AGRICULTURE, DEVASTATION, ECONOMY, INFRASTRUCTURE}
AGRICULTURE,
DEVASTATION,
ECONOMY,
INFRASTRUCTURE
}
import net.eagle0.eagle.common.profession.Profession.ENGINEER import net.eagle0.eagle.common.profession.Profession.ENGINEER
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.hero.Hero import net.eagle0.eagle.internal.hero.Hero
@@ -42,14 +37,14 @@ object AvailableImproveCommandsFactory extends AvailableCommandsFactoryForType {
.filterNot(_.vigor < MinVigorForImprove.doubleValue) .filterNot(_.vigor < MinVigorForImprove.doubleValue)
val availableTypes = val availableTypes =
Option.when(province.economy < IMPROVEMENT_MAX) { ECONOMY } ++ Option.when(province.economy < IMPROVEMENT_MAX)(ECONOMY) ++
Option.when(province.agriculture < IMPROVEMENT_MAX) { AGRICULTURE } ++ Option.when(province.agriculture < IMPROVEMENT_MAX)(AGRICULTURE) ++
Option.when(province.infrastructure < IMPROVEMENT_MAX) { Option.when(province.infrastructure < IMPROVEMENT_MAX) {
INFRASTRUCTURE INFRASTRUCTURE
} ++ } ++
Option.when( Option.when(
province.economyDevastation > 0 || province.agricultureDevastation > 0 || province.infrastructureDevastation > 0 province.economyDevastation > 0 || province.agricultureDevastation > 0 || province.infrastructureDevastation > 0
) { DEVASTATION } )(DEVASTATION)
Option.when(availableHeroes.nonEmpty && availableTypes.nonEmpty) { Option.when(availableHeroes.nonEmpty && availableTypes.nonEmpty) {
ImproveAvailableCommand( ImproveAvailableCommand(
@@ -7,8 +7,7 @@ import net.eagle0.eagle.common.province_order_type.ProvinceOrderType.*
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils
object AvailableIssueOrdersCommandFactory object AvailableIssueOrdersCommandFactory extends AvailableCommandsFactoryForType {
extends AvailableCommandsFactoryForType {
override def availableCommand( override def availableCommand(
gameState: GameState, gameState: GameState,
@@ -28,9 +27,7 @@ object AvailableIssueOrdersCommandFactory
.filter(_.rulingFactionId.contains(factionId)) .filter(_.rulingFactionId.contains(factionId))
.toVector .toVector
.sortBy(_.name) .sortBy(_.name)
.map(p => .map(p => ProvinceOrders(provinceId = p.id, orders = p.provinceOrders)),
ProvinceOrders(provinceId = p.id, orders = p.provinceOrders)
),
availableOrders = Vector(DEVELOP, MOBILIZE, EXPAND, ENTRUST) availableOrders = Vector(DEVELOP, MOBILIZE, EXPAND, ENTRUST)
) )
} }
@@ -1,10 +1,7 @@
package net.eagle0.eagle.library.actions.availability package net.eagle0.eagle.library.actions.availability
import net.eagle0.common.MoreOption import net.eagle0.common.MoreOption
import net.eagle0.eagle.{FactionId, ProvinceId} import net.eagle0.eagle.{FactionId, ProvinceId}
import net.eagle0.eagle.api.available_command.{ import net.eagle0.eagle.api.available_command.{ManagePrisonersAvailableCommand, PrisonerToManage}
ManagePrisonersAvailableCommand,
PrisonerToManage
}
import net.eagle0.eagle.api.command.util.prisoner_management_type.{ import net.eagle0.eagle.api.command.util.prisoner_management_type.{
PrisonerManagementOption, PrisonerManagementOption,
PrisonerManagementOptionExecute, PrisonerManagementOptionExecute,
@@ -17,8 +14,7 @@ import net.eagle0.eagle.common.unaffiliated_hero_type.UnaffiliatedHeroType.UNAFF
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.unaffiliated_hero.UnaffiliatedHero import net.eagle0.eagle.internal.unaffiliated_hero.UnaffiliatedHero
object AvailableManagePrisonersCommandFactory object AvailableManagePrisonersCommandFactory extends AvailableCommandsFactoryForType {
extends AvailableCommandsFactoryForType {
private def availableMoveToProvinceIds( private def availableMoveToProvinceIds(
fid: FactionId, fid: FactionId,
@@ -51,9 +47,7 @@ object AvailableManagePrisonersCommandFactory
_.rulingFactionId.contains(factionWithThisPrisonerAsLeader.get.id) _.rulingFactionId.contains(factionWithThisPrisonerAsLeader.get.id)
) )
)( )(
PrisonerManagementOptionReturn(toFactionId = PrisonerManagementOptionReturn(toFactionId = factionWithThisPrisonerAsLeader.get.id)
factionWithThisPrisonerAsLeader.get.id
)
) )
.toVector .toVector
} else { } else {
@@ -10,19 +10,9 @@ import net.eagle0.eagle.api.available_command.{
import net.eagle0.eagle.api.command.util.battalion_with_food_cost.BattalionWithFoodCost import net.eagle0.eagle.api.command.util.battalion_with_food_cost.BattalionWithFoodCost
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.province.Province import net.eagle0.eagle.internal.province.Province
import net.eagle0.eagle.library.settings.{ import net.eagle0.eagle.library.settings.{MaxCombatUnitCountPerSide, MinVigorForMarch}
MaxCombatUnitCountPerSide, import net.eagle0.eagle.library.util.{BattalionSuitability, LegacyBattalionUtils, ShardokMapInfo}
MinVigorForMarch import net.eagle0.eagle.library.util.command_choice_helpers.{CombatUnitSelector, MarchSuppliesHelpers}
}
import net.eagle0.eagle.library.util.{
BattalionSuitability,
LegacyBattalionUtils,
ShardokMapInfo
}
import net.eagle0.eagle.library.util.command_choice_helpers.{
CombatUnitSelector,
MarchSuppliesHelpers
}
import net.eagle0.eagle.library.util.hero.LegacyHeroUtils import net.eagle0.eagle.library.util.hero.LegacyHeroUtils
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
@@ -42,9 +32,9 @@ object AvailableMarchCommandFactory extends AvailableCommandsFactoryForType {
.get .get
.startingPositionIndex .startingPositionIndex
val mapInfo = ShardokMapInfo.shardokMapInfos(destinationProvince.id) val mapInfo = ShardokMapInfo.shardokMapInfos(destinationProvince.id)
val destinationCastleCount = mapInfo.castleCount val destinationCastleCount = mapInfo.castleCount
val requiresRiverCrossing = mapInfo.waterCrossingRequirementsByPosition( val requiresRiverCrossing = mapInfo.waterCrossingRequirementsByPosition(
startingPositionIndex startingPositionIndex
) >= 10 ) >= 10
@@ -73,7 +63,7 @@ object AvailableMarchCommandFactory extends AvailableCommandsFactoryForType {
.min(availableBattalions.size) .min(availableBattalions.size)
.min(originProvince.rulingFactionHeroIds.size - 2) .min(originProvince.rulingFactionHeroIds.size - 2)
.min(MaxCombatUnitCountPerSide.intValue) .min(MaxCombatUnitCountPerSide.intValue)
val recommendedConfig = val recommendedConfig =
Option.when(recommendedSendCount > 0) { Option.when(recommendedSendCount > 0) {
val units = CombatUnitSelector.selectedCombatBattalions( val units = CombatUnitSelector.selectedCombatBattalions(
desiredCount = recommendedSendCount, desiredCount = recommendedSendCount,
@@ -128,9 +118,7 @@ object AvailableMarchCommandFactory extends AvailableCommandsFactoryForType {
): Option[MarchAvailableCommand] = ): Option[MarchAvailableCommand] =
LegacyHeroUtils LegacyHeroUtils
.consideredProvinces(gameState, factionId, provinceId) .consideredProvinces(gameState, factionId, provinceId)
.flatMap(p => .flatMap(p => oneOriginProvinceCommand(gameState = gameState, originProvince = p)) match {
oneOriginProvinceCommand(gameState = gameState, originProvince = p)
) match {
case items if items.isEmpty => None case items if items.isEmpty => None
case opcs => case opcs =>
Some( Some(
@@ -1,16 +1,12 @@
package net.eagle0.eagle.library.actions.availability package net.eagle0.eagle.library.actions.availability
import net.eagle0.eagle.{FactionId, ProvinceId} import net.eagle0.eagle.{FactionId, ProvinceId}
import net.eagle0.eagle.api.available_command.{ import net.eagle0.eagle.api.available_command.{OrganizeTroopsAvailableCommand, TroopCost}
OrganizeTroopsAvailableCommand,
TroopCost
}
import net.eagle0.eagle.api.available_command.OrganizeTroopsAvailableCommand.BattalionTypeStatus import net.eagle0.eagle.api.available_command.OrganizeTroopsAvailableCommand.BattalionTypeStatus
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
object AvailableOrganizeTroopsCommandsFactory object AvailableOrganizeTroopsCommandsFactory extends AvailableCommandsFactoryForType {
extends AvailableCommandsFactoryForType {
val maxBattalions = 10 // FIXME val maxBattalions = 10 // FIXME
override def availableCommand( override def availableCommand(
@@ -20,20 +16,19 @@ object AvailableOrganizeTroopsCommandsFactory
): Option[OrganizeTroopsAvailableCommand] = { ): Option[OrganizeTroopsAvailableCommand] = {
val province = gameState.provinces(provinceId) val province = gameState.provinces(provinceId)
val availableTypeStatuses = gameState.battalionTypes.toVector val availableTypeStatuses = gameState.battalionTypes.toVector.map { bt =>
.map { bt => BattalionTypeStatus(
BattalionTypeStatus( typeId = bt.typeId,
typeId = bt.typeId, minimumAgriculture = bt.minimumAgriculture,
minimumAgriculture = bt.minimumAgriculture, minimumEconomy = bt.minimumEconomy,
minimumEconomy = bt.minimumEconomy, meetsRequirements = LegacyProvinceUtils
meetsRequirements = LegacyProvinceUtils .availableBattalionTypeIds(
.availableBattalionTypeIds( province,
province, gameState.battalionTypes.toVector
gameState.battalionTypes.toVector )
) .contains(bt.typeId)
.contains(bt.typeId) )
) }
}
.sortBy(_.typeId.value) .sortBy(_.typeId.value)
val troopCosts = gameState.battalionTypes.toVector.map(t => val troopCosts = gameState.battalionTypes.toVector.map(t =>
@@ -44,8 +39,7 @@ object AvailableOrganizeTroopsCommandsFactory
) )
if troopCosts.isEmpty then return None if troopCosts.isEmpty then return None
if troopCosts.map(_.costPerTroop).min.ceil.toInt > province.gold then if troopCosts.map(_.costPerTroop).min.ceil.toInt > province.gold then return None
return None
val existingEligibleBattalions = province.battalionIds val existingEligibleBattalions = province.battalionIds
.map(bid => gameState.battalions(bid)) .map(bid => gameState.battalions(bid))
@@ -1,9 +1,6 @@
package net.eagle0.eagle.library.actions.availability package net.eagle0.eagle.library.actions.availability
import net.eagle0.eagle.api.available_command.{ import net.eagle0.eagle.api.available_command.{OneProvincePleaseRecruitMe, PleaseRecruitMeAvailableCommand}
OneProvincePleaseRecruitMe,
PleaseRecruitMeAvailableCommand
}
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.province.Province import net.eagle0.eagle.internal.province.Province
import net.eagle0.eagle.library.settings.MinSupportForTaxes import net.eagle0.eagle.library.settings.MinSupportForTaxes
@@ -19,20 +16,19 @@ object AvailablePleaseRecruitMeCommandFactory {
): Option[OneProvincePleaseRecruitMe] = { ): Option[OneProvincePleaseRecruitMe] = {
val availableHeroes = for { val availableHeroes = for {
targetUH <- p.unaffiliatedHeroes.filter(uh => targetUH <- p.unaffiliatedHeroes.filter(uh =>
LegacyUnaffiliatedHeroUtils.willPleaseRecruitMe( LegacyUnaffiliatedHeroUtils.willPleaseRecruitMe(
gameState = gs, gameState = gs,
factionId = fid, factionId = fid,
unaffiliatedHero = uh unaffiliatedHero = uh
) )
) )
} yield { } yield
// FIXME: We should be generating the LLM request and its textID here instead of earlier in the round // FIXME: We should be generating the LLM request and its textID here instead of earlier in the round
ExpandedUnaffiliatedHeroUtils ExpandedUnaffiliatedHeroUtils
.expandedUnaffiliatedHero( .expandedUnaffiliatedHero(
gs = gs, gs = gs,
uh = targetUH uh = targetUH
) )
}
Option.when(availableHeroes.nonEmpty)( Option.when(availableHeroes.nonEmpty)(
OneProvincePleaseRecruitMe( OneProvincePleaseRecruitMe(
@@ -8,8 +8,7 @@ import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
import net.eagle0.eagle.library.util.recruitment_odds.LegacyRecruitmentOdds import net.eagle0.eagle.library.util.recruitment_odds.LegacyRecruitmentOdds
object AvailableRecruitHeroesCommandFactory object AvailableRecruitHeroesCommandFactory extends AvailableCommandsFactoryForType {
extends AvailableCommandsFactoryForType {
override def availableCommand( override def availableCommand(
gameState: GameState, gameState: GameState,
@@ -21,8 +20,7 @@ object AvailableRecruitHeroesCommandFactory
if !province.rulerIsTraveling then return None if !province.rulerIsTraveling then return None
val faction = gameState.factions(factionId) val faction = gameState.factions(factionId)
if !LegacyProvinceUtils.ruledByFactionLeader(province, gameState) then if !LegacyProvinceUtils.ruledByFactionLeader(province, gameState) then return None
return None
val expandedRecruitable = province.unaffiliatedHeroes val expandedRecruitable = province.unaffiliatedHeroes
.filterNot(uh => LegacyFactionUtils.isFactionLeader(uh.heroId, gameState)) .filterNot(uh => LegacyFactionUtils.isFactionLeader(uh.heroId, gameState))
@@ -1,9 +1,6 @@
package net.eagle0.eagle.library.actions.availability package net.eagle0.eagle.library.actions.availability
import net.eagle0.eagle.api.available_command.ResolveAllianceOfferAvailableCommand import net.eagle0.eagle.api.available_command.ResolveAllianceOfferAvailableCommand
import net.eagle0.eagle.common.diplomacy_offer.{ import net.eagle0.eagle.common.diplomacy_offer.{AllianceOfferDetails, DiplomacyOffer}
AllianceOfferDetails,
DiplomacyOffer
}
import net.eagle0.eagle.common.diplomacy_offer_status.DiplomacyOfferStatus.DIPLOMACY_OFFER_STATUS_UNRESOLVED import net.eagle0.eagle.common.diplomacy_offer_status.DiplomacyOfferStatus.DIPLOMACY_OFFER_STATUS_UNRESOLVED
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.FactionId import net.eagle0.eagle.FactionId
@@ -52,12 +49,11 @@ object AvailableResolveAllianceOfferCommandFactory {
messengerHeroId = messengerHeroId, messengerHeroId = messengerHeroId,
messengerOriginProvinceId = messengerOriginProvinceId, messengerOriginProvinceId = messengerOriginProvinceId,
status = status, status = status,
eligibleStatuses = eligibleStatuses = EligibleDiplomacyStatuses.acceptRejectStatuses ++ EligibleDiplomacyStatuses
EligibleDiplomacyStatuses.acceptRejectStatuses ++ EligibleDiplomacyStatuses .maybeImprisonStatus(
.maybeImprisonStatus( actingFactionId = factionId,
actingFactionId = factionId, gameState = gameState
gameState = gameState ),
),
offerTextId = offerTextId offerTextId = offerTextId
) )
} }
@@ -1,9 +1,6 @@
package net.eagle0.eagle.library.actions.availability package net.eagle0.eagle.library.actions.availability
import net.eagle0.eagle.api.available_command.ResolveBreakAllianceAvailableCommand import net.eagle0.eagle.api.available_command.ResolveBreakAllianceAvailableCommand
import net.eagle0.eagle.common.diplomacy_offer.{ import net.eagle0.eagle.common.diplomacy_offer.{BreakAllianceDetails, DiplomacyOffer}
BreakAllianceDetails,
DiplomacyOffer
}
import net.eagle0.eagle.common.diplomacy_offer_status.DiplomacyOfferStatus import net.eagle0.eagle.common.diplomacy_offer_status.DiplomacyOfferStatus
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_ACCEPTED,
@@ -1,9 +1,6 @@
package net.eagle0.eagle.library.actions.availability package net.eagle0.eagle.library.actions.availability
import net.eagle0.eagle.api.available_command.ResolveInvitationAvailableCommand import net.eagle0.eagle.api.available_command.ResolveInvitationAvailableCommand
import net.eagle0.eagle.common.diplomacy_offer.{ import net.eagle0.eagle.common.diplomacy_offer.{DiplomacyOffer, InvitationDetails}
DiplomacyOffer,
InvitationDetails
}
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_ACCEPTED,
DIPLOMACY_OFFER_STATUS_UNRESOLVED DIPLOMACY_OFFER_STATUS_UNRESOLVED
@@ -1,10 +1,6 @@
package net.eagle0.eagle.library.actions.availability package net.eagle0.eagle.library.actions.availability
import net.eagle0.eagle.api.available_command.ResolveRansomOfferAvailableCommand import net.eagle0.eagle.api.available_command.ResolveRansomOfferAvailableCommand
import net.eagle0.eagle.common.diplomacy_offer.{ import net.eagle0.eagle.common.diplomacy_offer.{DiplomacyOffer, InvitationDetails, RansomOfferDetails}
DiplomacyOffer,
InvitationDetails,
RansomOfferDetails
}
import net.eagle0.eagle.common.diplomacy_offer_status.DiplomacyOfferStatus.{ import net.eagle0.eagle.common.diplomacy_offer_status.DiplomacyOfferStatus.{
DIPLOMACY_OFFER_STATUS_REJECTED, DIPLOMACY_OFFER_STATUS_REJECTED,
DIPLOMACY_OFFER_STATUS_UNRESOLVED DIPLOMACY_OFFER_STATUS_UNRESOLVED
@@ -1,17 +1,13 @@
package net.eagle0.eagle.library.actions.availability package net.eagle0.eagle.library.actions.availability
import net.eagle0.eagle.{FactionId, ProvinceId} import net.eagle0.eagle.{FactionId, ProvinceId}
import net.eagle0.eagle.api.available_command.{ import net.eagle0.eagle.api.available_command.{ResolveTributeAvailableCommand, TributeAndFaction}
ResolveTributeAvailableCommand,
TributeAndFaction
}
import net.eagle0.eagle.internal.army.{HostileArmyGroup, HostileArmyGroupStatus} import net.eagle0.eagle.internal.army.{HostileArmyGroup, HostileArmyGroupStatus}
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.internal.province.Province import net.eagle0.eagle.internal.province.Province
import net.eagle0.eagle.library.util.ArmyUtils import net.eagle0.eagle.library.util.ArmyUtils
object AvailableResolveTributeCommandsFactory object AvailableResolveTributeCommandsFactory extends AvailableCommandsFactoryForType {
extends AvailableCommandsFactoryForType {
private def makeResolveTributeCommand( private def makeResolveTributeCommand(
province: Province, province: Province,
@@ -25,22 +21,20 @@ object AvailableResolveTributeCommandsFactory
Option.when(incomingTributeDemands.nonEmpty) { Option.when(incomingTributeDemands.nonEmpty) {
ResolveTributeAvailableCommand( ResolveTributeAvailableCommand(
actingProvinceId = province.id, actingProvinceId = province.id,
demands = incomingTributeDemands demands = incomingTributeDemands.map {
.map { case ag @ HostileArmyGroup(
case ag @ HostileArmyGroup( fid: FactionId,
fid: FactionId, _,
_, status: HostileArmyGroupStatus,
status: HostileArmyGroupStatus, _ /* unknownFieldSet */
_ /* unknownFieldSet */ ) =>
) => TributeAndFaction(
TributeAndFaction( demandingFactionId = fid,
demandingFactionId = fid, tributeDemanded = status.asMessage.getTributeDemanded.tributeAmount,
tributeDemanded = heroCount = ArmyUtils.heroCount(ag),
status.asMessage.getTributeDemanded.tributeAmount, troopCount = ArmyUtils.troopCount(ag, gs)
heroCount = ArmyUtils.heroCount(ag), )
troopCount = ArmyUtils.troopCount(ag, gs) },
)
},
availableGold = province.gold, availableGold = province.gold,
availableFood = province.food availableFood = province.food
) )
@@ -1,9 +1,6 @@
package net.eagle0.eagle.library.actions.availability package net.eagle0.eagle.library.actions.availability
import net.eagle0.eagle.api.available_command.ResolveTruceOfferAvailableCommand import net.eagle0.eagle.api.available_command.ResolveTruceOfferAvailableCommand
import net.eagle0.eagle.common.diplomacy_offer.{ import net.eagle0.eagle.common.diplomacy_offer.{DiplomacyOffer, TruceOfferDetails}
DiplomacyOffer,
TruceOfferDetails
}
import net.eagle0.eagle.common.diplomacy_offer_status.DiplomacyOfferStatus.DIPLOMACY_OFFER_STATUS_UNRESOLVED import net.eagle0.eagle.common.diplomacy_offer_status.DiplomacyOfferStatus.DIPLOMACY_OFFER_STATUS_UNRESOLVED
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.library.settings.TruceMonths import net.eagle0.eagle.library.settings.TruceMonths
@@ -59,13 +56,12 @@ object AvailableResolveTruceOfferCommandFactory {
messengerHeroId = messengerHeroId, messengerHeroId = messengerHeroId,
messengerOriginProvinceId = messengerOriginProvinceId, messengerOriginProvinceId = messengerOriginProvinceId,
status = status, status = status,
eligibleStatuses = eligibleStatuses = (EligibleDiplomacyStatuses.acceptRejectStatuses ++ EligibleDiplomacyStatuses
(EligibleDiplomacyStatuses.acceptRejectStatuses ++ EligibleDiplomacyStatuses .maybeImprisonStatus(
.maybeImprisonStatus( actingFactionId = factionId,
actingFactionId = factionId, gameState = gameState
gameState = gameState )
) .toVector).toVector,
.toVector).toVector,
offerTextId = offerTextId offerTextId = offerTextId
) )
}.toVector }.toVector
@@ -7,8 +7,7 @@ import net.eagle0.eagle.library.settings.MinVigorForSendSupplies
import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
object AvailableSendSuppliesCommandFactory object AvailableSendSuppliesCommandFactory extends AvailableCommandsFactoryForType {
extends AvailableCommandsFactoryForType {
override def availableCommand( override def availableCommand(
gameState: GameState, gameState: GameState,
factionId: FactionId, factionId: FactionId,
@@ -8,8 +8,7 @@ import net.eagle0.eagle.internal.province.Province
import net.eagle0.eagle.library.settings.MinVigorForStartEpidemic import net.eagle0.eagle.library.settings.MinVigorForStartEpidemic
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
object AvailableStartEpidemicCommandFactory object AvailableStartEpidemicCommandFactory extends AvailableCommandsFactoryForType {
extends AvailableCommandsFactoryForType {
private def makeStartEpidemicCommand( private def makeStartEpidemicCommand(
gameState: GameState, gameState: GameState,
@@ -38,9 +37,7 @@ object AvailableStartEpidemicCommandFactory
availableHeroIds = necromancersWithSufficientVigor availableHeroIds = necromancersWithSufficientVigor
.sortBy(-_.vigor) .sortBy(-_.vigor)
.map(_.id), .map(_.id),
options = targetProvinceIds.map(pid => options = targetProvinceIds.map(pid => StartEpidemicOptions(targetProvinceId = pid)),
StartEpidemicOptions(targetProvinceId = pid)
),
actingProvinceId = province.id actingProvinceId = province.id
) )
} }
@@ -7,17 +7,14 @@ import net.eagle0.eagle.internal.province.Province
import net.eagle0.eagle.library.settings.MinVigorForSuppressBeasts import net.eagle0.eagle.library.settings.MinVigorForSuppressBeasts
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
object AvailableSuppressBeastsCommandFactory object AvailableSuppressBeastsCommandFactory extends AvailableCommandsFactoryForType {
extends AvailableCommandsFactoryForType {
private def makeSuppressBeastsCommand( private def makeSuppressBeastsCommand(
gameState: GameState, gameState: GameState,
province: Province province: Province
): Option[SuppressBeastsAvailableCommand] = { ): Option[SuppressBeastsAvailableCommand] = {
val availableHeroIds = province.rulingFactionHeroIds val availableHeroIds = province.rulingFactionHeroIds
.filter(hid => .filter(hid => gameState.heroes(hid).vigor >= MinVigorForSuppressBeasts.doubleValue)
gameState.heroes(hid).vigor >= MinVigorForSuppressBeasts.doubleValue
)
Option.when( Option.when(
LegacyProvinceUtils.hasBeasts(province) && availableHeroIds.nonEmpty LegacyProvinceUtils.hasBeasts(province) && availableHeroIds.nonEmpty
@@ -4,13 +4,9 @@ import net.eagle0.eagle.{FactionId, HeroId, ProvinceId}
import net.eagle0.eagle.api.available_command.SwearBrotherhoodAvailableCommand import net.eagle0.eagle.api.available_command.SwearBrotherhoodAvailableCommand
import net.eagle0.eagle.api.available_command.SwearBrotherhoodAvailableCommand.HeroAndBackstory import net.eagle0.eagle.api.available_command.SwearBrotherhoodAvailableCommand.HeroAndBackstory
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.library.settings.{ import net.eagle0.eagle.library.settings.{MaximumFactionLeaders, MinimumLoyaltyForSwearBrotherhood}
MaximumFactionLeaders,
MinimumLoyaltyForSwearBrotherhood
}
object AvailableSwearBrotherhoodCommandFactory object AvailableSwearBrotherhoodCommandFactory extends AvailableCommandsFactoryForType {
extends AvailableCommandsFactoryForType {
def canHaveMoreLeaders(gameState: GameState, factionId: FactionId): Boolean = def canHaveMoreLeaders(gameState: GameState, factionId: FactionId): Boolean =
gameState gameState
@@ -23,7 +23,7 @@ object AvailableTrainCommandsFactory extends AvailableCommandsFactoryForType {
factionId: FactionId, factionId: FactionId,
provinceId: ProvinceId provinceId: ProvinceId
): Option[TrainAvailableCommand] = { ): Option[TrainAvailableCommand] = {
val province = gameState.provinces(provinceId) val province = gameState.provinces(provinceId)
val availableHeroes = province.rulingFactionHeroIds val availableHeroes = province.rulingFactionHeroIds
.map(heroId => gameState.heroes(heroId)) .map(heroId => gameState.heroes(heroId))
.filter(_.vigor >= MinVigorForTrain.doubleValue) .filter(_.vigor >= MinVigorForTrain.doubleValue)
@@ -3,10 +3,7 @@ package net.eagle0.eagle.library.actions.availability
import net.eagle0.eagle.api.command.util.expanded_combat_unit.ExpandedCombatUnit import net.eagle0.eagle.api.command.util.expanded_combat_unit.ExpandedCombatUnit
import net.eagle0.eagle.common.combat_unit.CombatUnit import net.eagle0.eagle.common.combat_unit.CombatUnit
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.library.util.view_filters.{ import net.eagle0.eagle.library.util.view_filters.{HeroViewFilter, LegacyBattalionViewFilter}
HeroViewFilter,
LegacyBattalionViewFilter
}
object ExpandedCombatUnitUtils { object ExpandedCombatUnitUtils {
def expandedCombatUnit( def expandedCombatUnit(
@@ -34,21 +34,21 @@ package object availability {
.toMap .toMap
val professionOrdering: Map[Profession, Int] = Map( val professionOrdering: Map[Profession, Int] = Map(
PALADIN -> 1, PALADIN -> 1,
ENGINEER -> 2, ENGINEER -> 2,
RANGER -> 3, RANGER -> 3,
MAGE -> 4, MAGE -> 4,
NECROMANCER -> 5, NECROMANCER -> 5,
CHAMPION -> 6, CHAMPION -> 6,
NO_PROFESSION -> 7, NO_PROFESSION -> 7,
UNKNOWN_PROFESSION -> 8 UNKNOWN_PROFESSION -> 8
) )
val battalionTypeOrdering: Map[BattalionTypeId, Int] = Map( val battalionTypeOrdering: Map[BattalionTypeId, Int] = Map(
HEAVY_CAVALRY -> 1, HEAVY_CAVALRY -> 1,
HEAVY_INFANTRY -> 2, HEAVY_INFANTRY -> 2,
LONGBOWMEN -> 3, LONGBOWMEN -> 3,
LIGHT_CAVALRY -> 4, LIGHT_CAVALRY -> 4,
LIGHT_INFANTRY -> 5 LIGHT_INFANTRY -> 5
) )
} }
@@ -7,11 +7,7 @@ import net.eagle0.eagle.library.util.province.ProvinceUtils
import net.eagle0.eagle.library.util.ransom_validity.RansomValidity import net.eagle0.eagle.library.util.ransom_validity.RansomValidity
import net.eagle0.eagle.library.util.ReturningHeroes import net.eagle0.eagle.library.util.ReturningHeroes
import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedProvinceC import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedProvinceC
import net.eagle0.eagle.model.action_result.concrete.{ import net.eagle0.eagle.model.action_result.concrete.{ActionResultC, ChangedFactionC, ChangedHeroC}
ActionResultC,
ChangedFactionC,
ChangedHeroC
}
import net.eagle0.eagle.model.action_result.types.{ import net.eagle0.eagle.model.action_result.types.{
FactionDestroyedResultType, FactionDestroyedResultType,
FactionLeaderRemovedResultType, FactionLeaderRemovedResultType,
@@ -25,10 +21,7 @@ import net.eagle0.eagle.model.state.faction.concrete.FactionC
import net.eagle0.eagle.model.state.faction.FactionT import net.eagle0.eagle.model.state.faction.FactionT
import net.eagle0.eagle.model.state.hero.HeroT import net.eagle0.eagle.model.state.hero.HeroT
import net.eagle0.eagle.model.state.province.ProvinceT import net.eagle0.eagle.model.state.province.ProvinceT
import net.eagle0.eagle.model.state.unaffiliated_hero.{ import net.eagle0.eagle.model.state.unaffiliated_hero.{RecruitmentInfo, UnaffiliatedHeroT}
RecruitmentInfo,
UnaffiliatedHeroT
}
import net.eagle0.eagle.model.state.unaffiliated_hero.concrete.UnaffiliatedHeroC import net.eagle0.eagle.model.state.unaffiliated_hero.concrete.UnaffiliatedHeroC
import net.eagle0.eagle.model.state.unaffiliated_hero.UnaffiliatedHeroType.Traveler import net.eagle0.eagle.model.state.unaffiliated_hero.UnaffiliatedHeroType.Traveler
import net.eagle0.eagle.model.state.Army import net.eagle0.eagle.model.state.Army
@@ -95,10 +88,11 @@ case class CheckForFactionChangesAction(
case (faction: FactionC, fr) => case (faction: FactionC, fr) =>
applyRemovedFactionResult(faction, fr) applyRemovedFactionResult(faction, fr)
} }
.continue { case (results, fr) => .continue {
maybeRansomInvalidResults(notDestroyed, fr).map { rirs => case (results, fr) =>
results ++ rirs maybeRansomInvalidResults(notDestroyed, fr).map { rirs =>
} results ++ rirs
}
} }
.map { results => .map { results =>
results ++ results ++
@@ -114,15 +108,15 @@ case class CheckForFactionChangesAction(
functionalRandom: FunctionalRandom functionalRandom: FunctionalRandom
): RandomState[Vector[ReturningHeroes.ReturningResult]] = ): RandomState[Vector[ReturningHeroes.ReturningResult]] =
functionalRandom functionalRandom
.nextMap(destroyedFaction.incomingDiplomacyOffers) { case (offer, fr) => .nextMap(destroyedFaction.incomingDiplomacyOffers) {
ReturningHeroes.heroesReturningToFaction( case (offer, fr) =>
hids = Vector(offer.messengerHeroId), ReturningHeroes.heroesReturningToFaction(
factionId = offer.originatingFactionId, hids = Vector(offer.messengerHeroId),
originProvince = factionId = offer.originatingFactionId,
provinces.find(_.id == offer.messengerOriginProvinceId).get, originProvince = provinces.find(_.id == offer.messengerOriginProvinceId).get,
provinces = provinces, provinces = provinces,
functionalRandom = fr functionalRandom = fr
) )
} }
private def applyRemovedFactionResult( private def applyRemovedFactionResult(
@@ -140,13 +134,14 @@ case class CheckForFactionChangesAction(
provinceId = p.id, provinceId = p.id,
removedIncomingArmyIds = removedFactionArmies.map(_.id) removedIncomingArmyIds = removedFactionArmies.map(_.id)
) )
) { case (cp, ma) => ) {
cp.withNewUnaffiliatedHeroes( case (cp, ma) =>
armyToUnaffiliatedHeroes(ma.army) cp.withNewUnaffiliatedHeroes(
).copy( armyToUnaffiliatedHeroes(ma.army)
foodDelta = deltaAugment(cp.foodDelta, ma.supplies.food), ).copy(
goldDelta = deltaAugment(cp.goldDelta, ma.supplies.gold) foodDelta = deltaAugment(cp.foodDelta, ma.supplies.food),
) goldDelta = deltaAugment(cp.goldDelta, ma.supplies.gold)
)
} }
) )
@@ -193,9 +188,7 @@ case class CheckForFactionChangesAction(
): Option[ChangedFactionC] = ): Option[ChangedFactionC] =
Option.when( Option.when(
faction.factionRelationships.exists(_.targetFactionId == otherFid) faction.factionRelationships.exists(_.targetFactionId == otherFid)
|| faction.incomingDiplomacyOffers.exists(ioff => || faction.incomingDiplomacyOffers.exists(ioff => ioff.originatingFactionId == otherFid)
ioff.originatingFactionId == otherFid
)
)( )(
ChangedFactionC( ChangedFactionC(
factionId = faction.id, factionId = faction.id,
@@ -272,32 +265,30 @@ case class CheckForFactionChangesAction(
functionalRandom: FunctionalRandom functionalRandom: FunctionalRandom
): RandomState[Vector[ActionResultT]] = ): RandomState[Vector[ActionResultT]] =
functionalRandom functionalRandom
.nextFlatMap(notDestroyedFactions.toVector) { case (faction, fr) => .nextFlatMap(notDestroyedFactions.toVector) {
fr.nextMap( case (faction, fr) =>
faction.incomingDiplomacyOffers fr.nextMap(
.collect { case diploOffer: RansomOffer => faction.incomingDiplomacyOffers.collect {
diploOffer case diploOffer: RansomOffer =>
diploOffer
} }
.filterNot(ransomOffer => .filterNot(ransomOffer => RansomValidity.isStillValid(ransomOffer, provinces))
RansomValidity.isStillValid(ransomOffer, provinces) ) {
) case (ransomOffer, innerFr) =>
) { case (ransomOffer, innerFr) => // Ransom offers don't remove the hero from the province
// Ransom offers don't remove the hero from the province RandomState(
RandomState( newValue = ActionResultC(
newValue = ActionResultC( actionResultType = RansomInvalidatedResultType,
actionResultType = RansomInvalidatedResultType, changedFactions = Vector(
changedFactions = Vector( ChangedFactionC(
ChangedFactionC( factionId = faction.id,
factionId = faction.id, removedIncomingDiplomacyOfferFactionIds = Vector(ransomOffer.originatingFactionId),
removedIncomingDiplomacyOfferFactionIds = newIncomingDiplomacyOffers = Vector(ransomOffer.withStatus(Invalidated))
Vector(ransomOffer.originatingFactionId), )
newIncomingDiplomacyOffers = )
Vector(ransomOffer.withStatus(Invalidated)) ),
) nextRandom = innerFr
) )
), }
nextRandom = innerFr
)
}
} }
} }
@@ -54,10 +54,9 @@ case class CheckForFailedQuestsAction(
// check for the province being owned by a different faction // check for the province being owned by a different faction
else if imprisonedProvince.rulingFactionId != questFactionId then true else if imprisonedProvince.rulingFactionId != questFactionId then true
// check for the hero being no longer a prisoner in the province // check for the hero being no longer a prisoner in the province
else if !imprisonedProvince.unaffiliatedHeroes else if !imprisonedProvince.unaffiliatedHeroes.exists { uh =>
.exists { uh => uh.heroId == prisonerHeroId && uh.unaffiliatedHeroType == Prisoner
uh.heroId == prisonerHeroId && uh.unaffiliatedHeroType == Prisoner }
}
then true then true
else false else false
} }
@@ -67,7 +66,7 @@ case class CheckForFailedQuestsAction(
province: ProvinceT province: ProvinceT
): Boolean = ): Boolean =
quest match { quest match {
case DefeatFactionQuest(targetFactionId) => case DefeatFactionQuest(targetFactionId) =>
// Fails if the faction no longer exists, or if the ruling faction of this province has a truce with it // Fails if the faction no longer exists, or if the ruling faction of this province has a truce with it
!factions.exists(_.id == targetFactionId) || province.rulingFactionId !factions.exists(_.id == targetFactionId) || province.rulingFactionId
.exists(rulingFid => .exists(rulingFid =>
@@ -78,13 +77,13 @@ case class CheckForFailedQuestsAction(
factions = factions factions = factions
) )
) )
case TruceWithFactionQuest(targetFactionId) => case TruceWithFactionQuest(targetFactionId) =>
// Fails if the faction no longer exists // Fails if the faction no longer exists
!factions.exists(_.id == targetFactionId) !factions.exists(_.id == targetFactionId)
case TruceCountQuest(truceCount) => case TruceCountQuest(truceCount) =>
// Fails if there are no longer enough factions to succeed // Fails if there are no longer enough factions to succeed
factions.size <= truceCount factions.size <= truceCount
case DismissSpecificVassalQuest(targetHeroId) => case DismissSpecificVassalQuest(targetHeroId) =>
// Fails if you swear brotherhood with the hero // Fails if you swear brotherhood with the hero
FactionUtils.isFactionLeader(targetHeroId, factions) FactionUtils.isFactionLeader(targetHeroId, factions)
case ExecutePrisonerQuest(prisonerHeroId, prisonerProvinceId) => case ExecutePrisonerQuest(prisonerHeroId, prisonerProvinceId) =>
@@ -93,7 +92,7 @@ case class CheckForFailedQuestsAction(
prisonerHeroId = prisonerHeroId, prisonerHeroId = prisonerHeroId,
prisonerProvinceId = prisonerProvinceId prisonerProvinceId = prisonerProvinceId
) )
case ExilePrisonerQuest(prisonerHeroId, prisonerProvinceId) => case ExilePrisonerQuest(prisonerHeroId, prisonerProvinceId) =>
isPrisonerManagementQuestFailed( isPrisonerManagementQuestFailed(
questFactionId = province.rulingFactionId, questFactionId = province.rulingFactionId,
prisonerHeroId = prisonerHeroId, prisonerHeroId = prisonerHeroId,
@@ -117,7 +116,7 @@ case class CheckForFailedQuestsAction(
prisonerHeroId = prisonerHeroId, prisonerHeroId = prisonerHeroId,
prisonerProvinceId = prisonerProvinceId prisonerProvinceId = prisonerProvinceId
) )
case _ => false case _ => false
} }
} }
@@ -1,29 +1,16 @@
package net.eagle0.eagle.library.actions.impl.action package net.eagle0.eagle.library.actions.impl.action
import net.eagle0.eagle.{ import net.eagle0.eagle.{BattalionId, FactionId, GameId, HeroId, ProvinceId, RoundId}
BattalionId,
FactionId,
GameId,
HeroId,
ProvinceId,
RoundId
}
import net.eagle0.eagle.common.battalion_type.BattalionType import net.eagle0.eagle.common.battalion_type.BattalionType
import net.eagle0.eagle.library.actions.impl.common.ProtolessSequentialResultsAction import net.eagle0.eagle.library.actions.impl.common.ProtolessSequentialResultsAction
import net.eagle0.eagle.library.settings.{ import net.eagle0.eagle.library.settings.{MinSupportForTaxes, PortionOfCapacityForUpgradeBattalionQuest}
MinSupportForTaxes,
PortionOfCapacityForUpgradeBattalionQuest
}
import net.eagle0.eagle.library.util.province.ProvinceUtils import net.eagle0.eagle.library.util.province.ProvinceUtils
import net.eagle0.eagle.library.util.quest_fulfillment.QuestFulfillmentUtils import net.eagle0.eagle.library.util.quest_fulfillment.QuestFulfillmentUtils
import net.eagle0.eagle.library.util.BattalionTypeFinder import net.eagle0.eagle.library.util.BattalionTypeFinder
import net.eagle0.eagle.model.action_result.ActionResultT import net.eagle0.eagle.model.action_result.ActionResultT
import net.eagle0.eagle.model.state.battalion.BattalionT import net.eagle0.eagle.model.state.battalion.BattalionT
import net.eagle0.eagle.model.state.date.Date import net.eagle0.eagle.model.state.date.Date
import net.eagle0.eagle.model.state.faction.FactionRelationship.RelationshipLevel.{ import net.eagle0.eagle.model.state.faction.FactionRelationship.RelationshipLevel.{Ally, Hostile}
Ally,
Hostile
}
import net.eagle0.eagle.model.state.faction.FactionT import net.eagle0.eagle.model.state.faction.FactionT
import net.eagle0.eagle.model.state.hero.HeroT import net.eagle0.eagle.model.state.hero.HeroT
import net.eagle0.eagle.model.state.province.ProvinceT import net.eagle0.eagle.model.state.province.ProvinceT
@@ -93,24 +80,24 @@ case class CheckForFulfilledQuestsAction(
province: ProvinceT province: ProvinceT
): Boolean = ): Boolean =
quest match { quest match {
case q: ComponentQuest => case q: ComponentQuest =>
q.componentsFulfilled >= q.componentCount q.componentsFulfilled >= q.componentCount
case _: DefeatFactionQuest => case _: DefeatFactionQuest =>
false // action quests false // action quests
case DismissSpecificVassalQuest(targetHeroId) => case DismissSpecificVassalQuest(targetHeroId) =>
!getHero(targetHeroId).exists( !getHero(targetHeroId).exists(
_.factionId _.factionId
.contains(province.rulingFactionId.get) .contains(province.rulingFactionId.get)
) )
case _: ExecutePrisonerQuest => case _: ExecutePrisonerQuest =>
false // action quests false // action quests
case _: ExilePrisonerQuest => case _: ExilePrisonerQuest =>
false // action quests false // action quests
case _: ReleasePrisonerQuest => case _: ReleasePrisonerQuest =>
false // action quests false // action quests
case ImproveAgricultureQuest(_: ProvinceId, desiredValue: Double) => case ImproveAgricultureQuest(_: ProvinceId, desiredValue: Double) =>
ProvinceUtils.effectiveAgriculture(province) >= desiredValue ProvinceUtils.effectiveAgriculture(province) >= desiredValue
case ImproveEconomyQuest(_: ProvinceId, desiredValue: Double) => case ImproveEconomyQuest(_: ProvinceId, desiredValue: Double) =>
ProvinceUtils.effectiveEconomy(province) >= desiredValue ProvinceUtils.effectiveEconomy(province) >= desiredValue
case ImproveInfrastructureQuest( case ImproveInfrastructureQuest(
_: ProvinceId, _: ProvinceId,
@@ -133,7 +120,7 @@ case class CheckForFulfilledQuestsAction(
battalionTypeId battalionTypeId
).capacity ).capacity
) )
case GrandArmyQuest(totalTroopCount) => case GrandArmyQuest(totalTroopCount) =>
province.battalionIds province.battalionIds
.flatMap(battalionWithId) .flatMap(battalionWithId)
.map(_.size) .map(_.size)
@@ -147,21 +134,19 @@ case class CheckForFulfilledQuestsAction(
expansionProvince.rulingFactionId == province.rulingFactionId && expansionProvince.rulingFactionId == province.rulingFactionId &&
expansionProvince.support >= MinSupportForTaxes.doubleValue expansionProvince.support >= MinSupportForTaxes.doubleValue
} }
case WealthQuest(gold, food) => case WealthQuest(gold, food) =>
province.gold >= gold && province.food >= food province.gold >= gold && province.food >= food
case AllianceQuest => case AllianceQuest =>
factionWithId(province.getRulingFactionId).get.factionRelationships factionWithId(province.getRulingFactionId).get.factionRelationships
.exists(fr => fr.relationshipLevel == Ally) .exists(fr => fr.relationshipLevel == Ally)
case TruceWithFactionQuest(targetFactionId) => case TruceWithFactionQuest(targetFactionId) =>
factionWithId(province.getRulingFactionId).get.factionRelationships factionWithId(province.getRulingFactionId).get.factionRelationships
.exists(fr => .exists(fr => fr.targetFactionId == targetFactionId && fr.relationshipLevel != Hostile)
fr.targetFactionId == targetFactionId && fr.relationshipLevel != Hostile case TruceCountQuest(truceCount) =>
)
case TruceCountQuest(truceCount) =>
factionWithId(province.getRulingFactionId).get.factionRelationships factionWithId(province.getRulingFactionId).get.factionRelationships
.count(_.relationshipLevel != Hostile) >= truceCount .count(_.relationshipLevel != Hostile) >= truceCount
case _ => case _ =>
throw new IllegalArgumentException(s"Unknown quest type: $quest") throw new IllegalArgumentException(s"Unknown quest type: $quest")
} }
} }
@@ -357,13 +357,13 @@ object ChronicleEventGenerator {
factionId = factionId, factionId = factionId,
provinceId = provinceId, provinceId = provinceId,
reason = reason match { reason = reason match {
case SHATTERED_ARMY_REASON_BLIZZARD => case SHATTERED_ARMY_REASON_BLIZZARD =>
SHATTERED_ARMY_EVENT_REASON_BLIZZARD SHATTERED_ARMY_EVENT_REASON_BLIZZARD
case SHATTERED_ARMY_REASON_UNKNOWN_UNSPECIFIED => case SHATTERED_ARMY_REASON_UNKNOWN_UNSPECIFIED =>
SHATTERED_ARMY_EVENT_REASON_UNKNOWN_UNSPECIFIED SHATTERED_ARMY_EVENT_REASON_UNKNOWN_UNSPECIFIED
case SHATTERED_ARMY_REASON_WITHDRAW => case SHATTERED_ARMY_REASON_WITHDRAW =>
SHATTERED_ARMY_EVENT_REASON_WITHDRAW SHATTERED_ARMY_EVENT_REASON_WITHDRAW
case Unrecognized(value) => case Unrecognized(value) =>
ShatteredArmyEvent.Reason.Unrecognized(value) ShatteredArmyEvent.Reason.Unrecognized(value)
} }
) )
@@ -407,9 +407,8 @@ object ChronicleEventGenerator {
def eventTextEntries( def eventTextEntries(
gameHistory: GameHistory, gameHistory: GameHistory,
since: Date since: Date
): Vector[EventForChronicle] = { ): Vector[EventForChronicle] =
gameHistory.sinceDate(since).flatMap { (awrs: ActionWithResultingState) => gameHistory.sinceDate(since).flatMap { (awrs: ActionWithResultingState) =>
notificationEntries(awrs) ++ basicActionResultEntries(awrs) notificationEntries(awrs) ++ basicActionResultEntries(awrs)
} }
}
} }
@@ -43,18 +43,19 @@ case class EndAttackDecisionPhaseAction(
} }
private val incomingAttacks = for { private val incomingAttacks = for {
province <- provinces province <- provinces
defendingFactionId <- province.rulingFactionId.toVector defendingFactionId <- province.rulingFactionId.toVector
attackingArmy <- province.hostileArmies attackingArmy <- province.hostileArmies
} yield (defendingFactionId, attackingArmy.factionId) } yield (defendingFactionId, attackingArmy.factionId)
private def isFailedTruceQuest(q: QuestT, p: ProvinceT): Boolean = private def isFailedTruceQuest(q: QuestT, p: ProvinceT): Boolean =
q match { q match {
case TruceWithFactionQuest(targetFactionId) => case TruceWithFactionQuest(targetFactionId) =>
incomingAttacks.exists { case (defendingFid, attackingFid) => incomingAttacks.exists {
defendingFid == targetFactionId && p.rulingFactionId.contains( case (defendingFid, attackingFid) =>
attackingFid defendingFid == targetFactionId && p.rulingFactionId.contains(
) attackingFid
)
} }
case _ => false case _ => false
@@ -26,10 +26,7 @@ import net.eagle0.eagle.internal.unaffiliated_hero.UnaffiliatedHero
import net.eagle0.eagle.library.actions.applier.ActionResultTApplier import net.eagle0.eagle.library.actions.applier.ActionResultTApplier
import net.eagle0.eagle.library.actions.generated_text_request_generators.captured_hero_helpers.CapturedHeroPleaGenerator import net.eagle0.eagle.library.actions.generated_text_request_generators.captured_hero_helpers.CapturedHeroPleaGenerator
import net.eagle0.eagle.library.actions.impl.action.EndBattleAftermathPhaseAction.RevelationChange import net.eagle0.eagle.library.actions.impl.action.EndBattleAftermathPhaseAction.RevelationChange
import net.eagle0.eagle.library.actions.impl.common.{ import net.eagle0.eagle.library.actions.impl.common.{ProtolessRandomSequentialResultsAction, RandomStateTSequencer}
ProtolessRandomSequentialResultsAction,
RandomStateTSequencer
}
import net.eagle0.eagle.library.settings.{ import net.eagle0.eagle.library.settings.{
FactionBiasAgainstFormerOnExile, FactionBiasAgainstFormerOnExile,
FactionBiasFromExile, FactionBiasFromExile,
@@ -41,22 +38,10 @@ import net.eagle0.eagle.library.util.unaffiliated_hero.LegacyUnaffiliatedHeroUti
import net.eagle0.eagle.library.util.view_filters.ProvinceViewFilter import net.eagle0.eagle.library.util.view_filters.ProvinceViewFilter
import net.eagle0.eagle.library.util.EagleRequire.internalRequire import net.eagle0.eagle.library.util.EagleRequire.internalRequire
import net.eagle0.eagle.library.EagleInternalException import net.eagle0.eagle.library.EagleInternalException
import net.eagle0.eagle.model.action_result.{ import net.eagle0.eagle.model.action_result.{ActionResultT, NotificationDetails, NotificationT}
ActionResultT,
NotificationDetails,
NotificationT
}
import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedProvinceC import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedProvinceC
import net.eagle0.eagle.model.action_result.concrete.{ import net.eagle0.eagle.model.action_result.concrete.{ActionResultC, ChangedFactionC, ChangedHeroC, NotificationC}
ActionResultC, import net.eagle0.eagle.model.action_result.types.{CapturedHeroResolvedResultType, EndAftermathPhaseResultType}
ChangedFactionC,
ChangedHeroC,
NotificationC
}
import net.eagle0.eagle.model.action_result.types.{
CapturedHeroResolvedResultType,
EndAftermathPhaseResultType
}
import net.eagle0.eagle.model.proto_converters.{ import net.eagle0.eagle.model.proto_converters.{
BattleRevelationConverter, BattleRevelationConverter,
NotificationConverter, NotificationConverter,
@@ -74,11 +59,7 @@ import net.eagle0.eagle.model.state.hero.{
CapturedHeroReturnedBackstoryEvent, CapturedHeroReturnedBackstoryEvent,
EventForHeroBackstoryT EventForHeroBackstoryT
} }
import net.eagle0.eagle.model.state.BattleRevelationType.{ import net.eagle0.eagle.model.state.BattleRevelationType.{DidBattle, Unknown, Withdrew}
DidBattle,
Unknown,
Withdrew
}
object EndBattleAftermathPhaseAction { object EndBattleAftermathPhaseAction {
case class RevelationChange( case class RevelationChange(
@@ -112,15 +93,13 @@ object EndBattleAftermathPhaseAction {
`type` = uhType, `type` = uhType,
lastFaction = gameState.heroes(capturedHeroId).factionId, lastFaction = gameState.heroes(capturedHeroId).factionId,
factionBiases = ( factionBiases = (
newFactionBias.map(b => actingFactionId -> b) ++ oldFactionBias.map( newFactionBias.map(b => actingFactionId -> b) ++ oldFactionBias.map(b => capturedHeroFactionId -> b)
b => capturedHeroFactionId -> b
)
).toMap ).toMap
), ),
hero = gameState.heroes(capturedHeroId), hero = gameState.heroes(capturedHeroId),
functionalRandom = functionalRandom functionalRandom = functionalRandom
) )
.map { UnaffiliatedHeroConverter.fromProto } .map(UnaffiliatedHeroConverter.fromProto)
.map { uh => .map { uh =>
ActionResultC( ActionResultC(
actionResultType = CapturedHeroResolvedResultType, actionResultType = CapturedHeroResolvedResultType,
@@ -129,8 +108,7 @@ object EndBattleAftermathPhaseAction {
changedHeroes = Vector( changedHeroes = Vector(
ChangedHeroC( ChangedHeroC(
heroId = capturedHeroId, heroId = capturedHeroId,
newEventsForHeroBackstory = newEventsForHeroBackstory = Vector(newEventForHeroBackstoryDetails)
Vector(newEventForHeroBackstoryDetails)
) )
), ),
changedProvinces = Vector( changedProvinces = Vector(
@@ -274,13 +252,12 @@ object EndBattleAftermathPhaseAction {
oldFactionBias = None, oldFactionBias = None,
gameState = gameState, gameState = gameState,
functionalRandom = functionalRandom, functionalRandom = functionalRandom,
newEventForHeroBackstoryDetails = newEventForHeroBackstoryDetails = CapturedHeroImprisonedBackstoryEvent(
CapturedHeroImprisonedBackstoryEvent( date = DateConverter.fromProto(gameState.currentDate),
date = DateConverter.fromProto(gameState.currentDate), capturingFactionId = imprisoningFactionId,
capturingFactionId = imprisoningFactionId, capturingHeroId = imprisoningHeroId,
capturingHeroId = imprisoningHeroId, provinceId = provinceId
provinceId = provinceId )
)
).map { ar => ).map { ar =>
ar.withNewGeneratedTextRequest(notificationLlmRequest) ar.withNewGeneratedTextRequest(notificationLlmRequest)
.withNotification( .withNotification(
@@ -352,8 +329,7 @@ object EndBattleAftermathPhaseAction {
factions = allFactionTs factions = allFactionTs
) )
.copy( .copy(
relationshipLevel = relationshipLevel = FactionRelationship.RelationshipLevel.Truce,
FactionRelationship.RelationshipLevel.Truce,
resetDate = Some(truceEndDate) resetDate = Some(truceEndDate)
) )
) )
@@ -368,8 +344,7 @@ object EndBattleAftermathPhaseAction {
factions = allFactionTs factions = allFactionTs
) )
.copy( .copy(
relationshipLevel = relationshipLevel = FactionRelationship.RelationshipLevel.Truce,
FactionRelationship.RelationshipLevel.Truce,
resetDate = Some(truceEndDate) resetDate = Some(truceEndDate)
) )
) )
@@ -394,9 +369,8 @@ object EndBattleAftermathPhaseAction {
throw new EagleInternalException("Empty deferred change") throw new EagleInternalException("Empty deferred change")
// the rest are not for this phase // the rest are not for this phase
case _: EpidemicStarted | _: DroughtStarted | _: DroughtEnded | case _: EpidemicStarted | _: DroughtStarted | _: DroughtEnded | _: PrisonerMoved | _: PrisonerReturned |
_: PrisonerMoved | _: PrisonerReturned | _: BlizzardStarted | _: BlizzardStarted | _: BlizzardEnded =>
_: BlizzardEnded =>
throw new EagleInternalException( throw new EagleInternalException(
"Event should not be present in EndBattleAftermathPhaseAction" "Event should not be present in EndBattleAftermathPhaseAction"
) )
@@ -412,9 +386,9 @@ case class EndBattleAftermathPhaseAction(
): RevelationChange = ): RevelationChange =
RevelationChange( RevelationChange(
changedFaction = battleRevelation.revelationType match { changedFaction = battleRevelation.revelationType match {
case Unknown => case Unknown =>
throw new EagleInternalException("UNKNOWN revelation type") throw new EagleInternalException("UNKNOWN revelation type")
case Withdrew => case Withdrew =>
ChangedFactionC( ChangedFactionC(
factionId = battleRevelation.revealedToFactionId, factionId = battleRevelation.revealedToFactionId,
updatedReconnedProvinces = Vector( updatedReconnedProvinces = Vector(
@@ -450,7 +424,7 @@ case class EndBattleAftermathPhaseAction(
def revelationChanges(gs: GameState): Vector[RevelationChange] = def revelationChanges(gs: GameState): Vector[RevelationChange] =
for { for {
province <- gs.provinces.values.toVector province <- gs.provinces.values.toVector
revelation <- province.battleRevelations revelation <- province.battleRevelations
if gs.factions.contains(revelation.revealedToFactionId) if gs.factions.contains(revelation.revealedToFactionId)
} yield revelationChange(BattleRevelationConverter.fromProto(revelation)) } yield revelationChange(BattleRevelationConverter.fromProto(revelation))
@@ -466,9 +440,7 @@ case class EndBattleAftermathPhaseAction(
functionalRandom = functionalRandom functionalRandom = functionalRandom
) )
.foldIn( .foldIn(
EndBattleAftermathPhaseAction.allDeferredChanges(gameState = EndBattleAftermathPhaseAction.allDeferredChanges(gameState = initialState)
initialState
)
)( )(
EndBattleAftermathPhaseAction.deferredChangeAR EndBattleAftermathPhaseAction.deferredChangeAR
) )
@@ -495,37 +467,30 @@ case class EndBattleAftermathPhaseAction(
.withRandomActionResults((gs, fr) => .withRandomActionResults((gs, fr) =>
CheckForFactionChangesAction( CheckForFactionChangesAction(
gameId = gs.gameId, gameId = gs.gameId,
factions = factions = gs.factions.values.toVector.map(FactionConverter.fromProto),
gs.factions.values.toVector.map(FactionConverter.fromProto), provinces = gs.provinces.values.toVector.map(ProvinceConverter.fromProto),
provinces =
gs.provinces.values.toVector.map(ProvinceConverter.fromProto),
heroes = gs.heroes.values.toVector.map(HeroConverter.fromProto), heroes = gs.heroes.values.toVector.map(HeroConverter.fromProto),
killedHeroIds = gs.killedHeroes.keys.toVector killedHeroIds = gs.killedHeroes.keys.toVector
).randomResults(fr) ).randomResults(fr)
) )
.withProtolessSequentialResultsAction(gs => .withProtolessSequentialResultsAction(gs => HeroBackstoryUpdateActionGenerator.fromGameState(gs))
HeroBackstoryUpdateActionGenerator.fromGameState(gs) .withRandomActionResult {
) case (gs, fr) =>
.withRandomActionResult { case (gs, fr) => RandomState(
RandomState( ActionResultC(
ActionResultC( actionResultType = EndAftermathPhaseResultType,
actionResultType = EndAftermathPhaseResultType, changedFactions = revelationChanges(gs).map(_.changedFaction),
changedFactions = revelationChanges(gs).map(_.changedFaction), changedProvinces = revelationChanges(gs).map(_.changedProvince),
changedProvinces = revelationChanges(gs).map(_.changedProvince), newRoundPhase = Some(RoundPhase.DiplomacyResolution),
newRoundPhase = Some(RoundPhase.DiplomacyResolution), removedNotifications = gs.deferredNotifications
removedNotifications = gs.deferredNotifications .map(note => NotificationConverter.fromProto(note, deferred = true))
.map(note => .toVector,
NotificationConverter.fromProto(note, deferred = true) newNotifications = gs.deferredNotifications
) .map(note => NotificationConverter.fromProto(note, deferred = false))
.toVector, .toVector
newNotifications = gs.deferredNotifications ),
.map(note => fr
NotificationConverter.fromProto(note, deferred = false) )
)
.toVector
),
fr
)
} }
.actionResults .actionResults
} }
@@ -9,12 +9,12 @@ import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.library.actions.impl.common.DeterministicSingleResultAction import net.eagle0.eagle.library.actions.impl.common.DeterministicSingleResultAction
import net.eagle0.eagle.library.util.EagleRequire.internalRequire import net.eagle0.eagle.library.util.EagleRequire.internalRequire
case class EndBattleRequestPhaseAction(gameState: GameState) case class EndBattleRequestPhaseAction(gameState: GameState) extends DeterministicSingleResultAction(gameState) {
extends DeterministicSingleResultAction(gameState) {
override def immediateExecute: ActionResult = { override def immediateExecute: ActionResult = {
internalRequire( internalRequire(
gameState.provinces.forall { case (_, province) => gameState.provinces.forall {
province.hostileArmies.isEmpty case (_, province) =>
province.hostileArmies.isEmpty
}, },
s"Province ${gameState.provinces.find(_._2.hostileArmies.nonEmpty).get._2.name} still has attacking armies!" s"Province ${gameState.provinces.find(_._2.hostileArmies.nonEmpty).get._2.name} still has attacking armies!"
) )
@@ -7,8 +7,7 @@ import net.eagle0.eagle.internal.action_result.ActionResult
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.library.actions.impl.common.DeterministicSingleResultAction import net.eagle0.eagle.library.actions.impl.common.DeterministicSingleResultAction
case class EndBattleResolutionPhaseAction(gameState: GameState) case class EndBattleResolutionPhaseAction(gameState: GameState) extends DeterministicSingleResultAction(gameState) {
extends DeterministicSingleResultAction(gameState) {
override def immediateExecute: ActionResult = override def immediateExecute: ActionResult =
ActionResult( ActionResult(
`type` = END_RESOLUTION_PHASE, `type` = END_RESOLUTION_PHASE,
@@ -30,21 +30,19 @@ case class EndDefenseDecisionPhaseAction(
) )
val attackerCps = for { val attackerCps = for {
ag <- tributePaidArmies ag <- tributePaidArmies
army <- ag.armies army <- ag.armies
} yield { } yield ChangedProvince(
ChangedProvince( id = army.originProvince,
id = army.originProvince, addedIncomingArmies = Vector(
addedIncomingArmies = Vector( army.update(
army.update( _.destinationProvince := army.originProvince,
_.destinationProvince := army.originProvince, _.originProvince := payingProvince.id,
_.originProvince := payingProvince.id, _.army.optionalFleeProvinceId := None,
_.army.optionalFleeProvinceId := None, _.arrivalRound := gameState.currentRoundId + 1
_.arrivalRound := gameState.currentRoundId + 1
)
) )
) )
} )
if attackerCps.isEmpty then Vector.empty if attackerCps.isEmpty then Vector.empty
else else
@@ -10,47 +10,23 @@ import net.eagle0.eagle.library.actions.impl.action.diplomacy_helpers.{
RansomResolutionHelpers, RansomResolutionHelpers,
TruceResolutionHelpers TruceResolutionHelpers
} }
import net.eagle0.eagle.library.actions.impl.common.{ import net.eagle0.eagle.library.actions.impl.common.{ProtolessRandomSequentialResultsAction, RandomStateTSequencer}
ProtolessRandomSequentialResultsAction,
RandomStateTSequencer
}
import net.eagle0.eagle.library.util.faction_utils.FactionUtils import net.eagle0.eagle.library.util.faction_utils.FactionUtils
import net.eagle0.eagle.library.util.quest_fulfillment.QuestFulfillmentChecker import net.eagle0.eagle.library.util.quest_fulfillment.QuestFulfillmentChecker
import net.eagle0.eagle.library.util.ransom_validity.RansomValidity import net.eagle0.eagle.library.util.ransom_validity.RansomValidity
import net.eagle0.eagle.library.EagleInternalException import net.eagle0.eagle.library.EagleInternalException
import net.eagle0.eagle.model.action_result.concrete.{ import net.eagle0.eagle.model.action_result.concrete.{ActionResultC, ChangedFactionC}
ActionResultC, import net.eagle0.eagle.model.action_result.types.{EndDiplomacyResolutionPhaseResultType, RansomInvalidatedResultType}
ChangedFactionC
}
import net.eagle0.eagle.model.action_result.types.{
EndDiplomacyResolutionPhaseResultType,
RansomInvalidatedResultType
}
import net.eagle0.eagle.model.action_result.ActionResultT import net.eagle0.eagle.model.action_result.ActionResultT
import net.eagle0.eagle.model.proto_converters.{ import net.eagle0.eagle.model.proto_converters.{BattalionConverter, NotificationConverter}
BattalionConverter,
NotificationConverter
}
import net.eagle0.eagle.model.proto_converters.date.DateConverter import net.eagle0.eagle.model.proto_converters.date.DateConverter
import net.eagle0.eagle.model.proto_converters.faction.FactionConverter import net.eagle0.eagle.model.proto_converters.faction.FactionConverter
import net.eagle0.eagle.model.proto_converters.hero.HeroConverter import net.eagle0.eagle.model.proto_converters.hero.HeroConverter
import net.eagle0.eagle.model.proto_converters.province.ProvinceConverter import net.eagle0.eagle.model.proto_converters.province.ProvinceConverter
import net.eagle0.eagle.model.state.battalion.BattalionT import net.eagle0.eagle.model.state.battalion.BattalionT
import net.eagle0.eagle.model.state.date.Date import net.eagle0.eagle.model.state.date.Date
import net.eagle0.eagle.model.state.diplomacy_offer.{ import net.eagle0.eagle.model.state.diplomacy_offer.{AllianceOffer, BreakAlliance, Invitation, RansomOffer, TruceOffer}
AllianceOffer, import net.eagle0.eagle.model.state.diplomacy_offer.status.{Accepted, Imprisoned, Invalidated, Rejected, Unresolved}
BreakAlliance,
Invitation,
RansomOffer,
TruceOffer
}
import net.eagle0.eagle.model.state.diplomacy_offer.status.{
Accepted,
Imprisoned,
Invalidated,
Rejected,
Unresolved
}
import net.eagle0.eagle.model.state.faction.FactionT import net.eagle0.eagle.model.state.faction.FactionT
import net.eagle0.eagle.model.state.hero.HeroT import net.eagle0.eagle.model.state.hero.HeroT
import net.eagle0.eagle.model.state.province.ProvinceT import net.eagle0.eagle.model.state.province.ProvinceT
@@ -120,58 +96,50 @@ case class EndDiplomacyResolutionPhaseAction(
} }
/** Companion object for EndDiplomacyResolutionPhaseAction containing helper /**
* methods. * Companion object for EndDiplomacyResolutionPhaseAction containing helper methods.
* *
* IMPORTANT: This is intentionally named differently from the case class to * IMPORTANT: This is intentionally named differently from the case class to avoid Scala naming conflicts that prevent
* avoid Scala naming conflicts that prevent the class from being imported in * the class from being imported in other packages. When a case class and its companion object share the same name, the
* other packages. When a case class and its companion object share the same * compiler can become confused about which one is being referenced during imports.
* name, the compiler can become confused about which one is being referenced *
* during imports. * All helper methods take explicit parameters for game state components to ensure they always operate on the current
* * state as provided by RandomStateTSequencer, preventing use of stale data from the initial game state.
* All helper methods take explicit parameters for game state components to */
* ensure they always operate on the current state as provided by
* RandomStateTSequencer, preventing use of stale data from the initial game
* state.
*/
private object EndDiplomacyResolutionPhaseActionHelpers { private object EndDiplomacyResolutionPhaseActionHelpers {
// Methods that use the updated game state from the sequencer // Methods that use the updated game state from the sequencer
def invalidationResultsForState( def invalidationResultsForState(
currentGameState: GameState currentGameState: GameState
): Vector[ActionResultT] = { ): Vector[ActionResultT] = {
val factions = val factions =
currentGameState.factions.values.map(FactionConverter.fromProto).toVector currentGameState.factions.values.map(FactionConverter.fromProto).toVector
val provinces = currentGameState.provinces.values val provinces = currentGameState.provinces.values
.map(ProvinceConverter.fromProto) .map(ProvinceConverter.fromProto)
.toVector .toVector
for { for {
faction <- factions faction <- factions
diploOffer <- faction.incomingDiplomacyOffers.filter( diploOffer <- faction.incomingDiplomacyOffers.filter(
_.status == Unresolved _.status == Unresolved
) )
} yield { } yield diploOffer match {
diploOffer match { case ro: RansomOffer if !RansomValidity.isStillValid(diploOffer, provinces) =>
case ro: RansomOffer ActionResultC(
if !RansomValidity.isStillValid(diploOffer, provinces) => actionResultType = RansomInvalidatedResultType,
ActionResultC( changedFactions = Vector(
actionResultType = RansomInvalidatedResultType, ChangedFactionC(
changedFactions = Vector( factionId = faction.id,
ChangedFactionC( removedIncomingDiplomacyOfferFactionIds = Vector(ro.originatingFactionId),
factionId = faction.id, newIncomingDiplomacyOffers = Vector(ro.withStatus(Invalidated))
removedIncomingDiplomacyOfferFactionIds =
Vector(ro.originatingFactionId),
newIncomingDiplomacyOffers = Vector(ro.withStatus(Invalidated))
)
) )
) )
case _ => )
// This shouldn't be possible case _ =>
throw new EagleInternalException( // This shouldn't be possible
s"Unresolved offer $diploOffer should not still be possible" throw new EagleInternalException(
) s"Unresolved offer $diploOffer should not still be possible"
} )
} }
} }
@@ -198,7 +166,7 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
resolver: A => Vector[ActionResultT] resolver: A => Vector[ActionResultT]
)(factions: Vector[FactionT]): Vector[ActionResultT] = )(factions: Vector[FactionT]): Vector[ActionResultT] =
(for { (for {
faction <- factions faction <- factions
outgoingDiplo <- getter(faction) outgoingDiplo <- getter(faction)
} yield resolver(outgoingDiplo)).flatten } yield resolver(outgoingDiplo)).flatten
@@ -207,10 +175,12 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
resolver: (A, FunctionalRandom) => RandomState[Vector[ActionResultT]], resolver: (A, FunctionalRandom) => RandomState[Vector[ActionResultT]],
functionalRandom: FunctionalRandom functionalRandom: FunctionalRandom
)(factions: Vector[FactionT]): RandomState[Vector[ActionResultT]] = )(factions: Vector[FactionT]): RandomState[Vector[ActionResultT]] =
functionalRandom.nextFlatMap(factions) { case (faction, fr) => functionalRandom.nextFlatMap(factions) {
fr.nextMap(getter(faction)) { case (outgoingDiplo, innerFr) => case (faction, fr) =>
resolver(outgoingDiplo, innerFr) fr.nextMap(getter(faction)) {
}.map(_.flatten) case (outgoingDiplo, innerFr) =>
resolver(outgoingDiplo, innerFr)
}.map(_.flatten)
} }
def truceResolutionsForState( def truceResolutionsForState(
@@ -221,7 +191,7 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
randomResolutionsForType( randomResolutionsForType(
_.incomingDiplomacyOffers.collect { case to: TruceOffer => to }, _.incomingDiplomacyOffers.collect { case to: TruceOffer => to },
(to: TruceOffer, fr: FunctionalRandom) => { (to: TruceOffer, fr: FunctionalRandom) => {
val provinces = currentGameState.provinces.values val provinces = currentGameState.provinces.values
.map(ProvinceConverter.fromProto) .map(ProvinceConverter.fromProto)
.toVector .toVector
val currentDate = DateConverter.fromProto(currentGameState.currentDate) val currentDate = DateConverter.fromProto(currentGameState.currentDate)
@@ -246,10 +216,10 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
randomResolutionsForType( randomResolutionsForType(
_.incomingDiplomacyOffers.collect { case ao: AllianceOffer => ao }, _.incomingDiplomacyOffers.collect { case ao: AllianceOffer => ao },
(ao: AllianceOffer, fr: FunctionalRandom) => { (ao: AllianceOffer, fr: FunctionalRandom) => {
val provinces = currentGameState.provinces.values val provinces = currentGameState.provinces.values
.map(ProvinceConverter.fromProto) .map(ProvinceConverter.fromProto)
.toVector .toVector
val heroes = val heroes =
currentGameState.heroes.values.map(HeroConverter.fromProto).toVector currentGameState.heroes.values.map(HeroConverter.fromProto).toVector
val currentDate = DateConverter.fromProto(currentGameState.currentDate) val currentDate = DateConverter.fromProto(currentGameState.currentDate)
resultsForAllianceOffer( resultsForAllianceOffer(
@@ -273,7 +243,7 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
randomResolutionsForType( randomResolutionsForType(
_.incomingDiplomacyOffers.collect { case ba: BreakAlliance => ba }, _.incomingDiplomacyOffers.collect { case ba: BreakAlliance => ba },
(ba: BreakAlliance, fr: FunctionalRandom) => { (ba: BreakAlliance, fr: FunctionalRandom) => {
val provinces = currentGameState.provinces.values val provinces = currentGameState.provinces.values
.map(ProvinceConverter.fromProto) .map(ProvinceConverter.fromProto)
.toVector .toVector
val currentDate = DateConverter.fromProto(currentGameState.currentDate) val currentDate = DateConverter.fromProto(currentGameState.currentDate)
@@ -291,10 +261,10 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
randomResolutionsForType( randomResolutionsForType(
_.incomingDiplomacyOffers.collect { case id: Invitation => id }, _.incomingDiplomacyOffers.collect { case id: Invitation => id },
(id: Invitation, fr: FunctionalRandom) => { (id: Invitation, fr: FunctionalRandom) => {
val provinces = currentGameState.provinces.values val provinces = currentGameState.provinces.values
.map(ProvinceConverter.fromProto) .map(ProvinceConverter.fromProto)
.toVector .toVector
val battalions = currentGameState.battalions.values val battalions = currentGameState.battalions.values
.map(BattalionConverter.fromProto) .map(BattalionConverter.fromProto)
.toVector .toVector
val currentDate = DateConverter.fromProto(currentGameState.currentDate) val currentDate = DateConverter.fromProto(currentGameState.currentDate)
@@ -337,7 +307,7 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
provinces: Vector[ProvinceT], provinces: Vector[ProvinceT],
gameState: GameState gameState: GameState
): Vector[ActionResultT] = ransomOffer.status match { ): Vector[ActionResultT] = ransomOffer.status match {
case Accepted => case Accepted =>
RansomResolutionHelpers.acceptedRansomResults( RansomResolutionHelpers.acceptedRansomResults(
ransomOffer, ransomOffer,
currentDate, currentDate,
@@ -346,19 +316,18 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
gameId = gameState.gameId, gameId = gameState.gameId,
currentDate = currentDate, currentDate = currentDate,
currentRoundId = gameState.currentRoundId, currentRoundId = gameState.currentRoundId,
previousBackstoryTextIdLookup = previousBackstoryTextIdLookup = hid => gameState.heroes(hid).backstoryVersions.toVector.last.textId
hid => gameState.heroes(hid).backstoryVersions.toVector.last.textId
) )
) )
case Rejected => case Rejected =>
Vector( Vector(
RansomResolutionHelpers.rejectedRansomResult(ransomOffer) RansomResolutionHelpers.rejectedRansomResult(ransomOffer)
) )
case Unresolved => case Unresolved =>
throw new EagleInternalException( throw new EagleInternalException(
"UNRESOLVED ransom offer at the end of the round" "UNRESOLVED ransom offer at the end of the round"
) )
case Imprisoned => case Imprisoned =>
throw new EagleInternalException( throw new EagleInternalException(
s"IMPRISONED is not valid for a ransom offer" s"IMPRISONED is not valid for a ransom offer"
) )
@@ -377,7 +346,7 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
gameState: GameState gameState: GameState
): RandomState[Vector[ActionResultT]] = ): RandomState[Vector[ActionResultT]] =
truceOffer.status match { truceOffer.status match {
case Accepted => case Accepted =>
TruceResolutionHelpers TruceResolutionHelpers
.acceptedTruceResult( .acceptedTruceResult(
truceOffer = truceOffer, truceOffer = truceOffer,
@@ -387,7 +356,7 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
functionalRandom = functionalRandom functionalRandom = functionalRandom
) )
.map(Vector(_)) .map(Vector(_))
case Rejected => case Rejected =>
TruceResolutionHelpers TruceResolutionHelpers
.rejectedTruceResults( .rejectedTruceResults(
truceOffer = truceOffer, truceOffer = truceOffer,
@@ -396,7 +365,7 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
eagleGameId = gameState.gameId, eagleGameId = gameState.gameId,
functionalRandom = functionalRandom functionalRandom = functionalRandom
) )
case Imprisoned => case Imprisoned =>
RandomState( RandomState(
TruceResolutionHelpers.imprisonedAmbassadorResults( TruceResolutionHelpers.imprisonedAmbassadorResults(
truceOffer = truceOffer, truceOffer = truceOffer,
@@ -416,7 +385,7 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
eagleGameId = gameState.gameId, eagleGameId = gameState.gameId,
functionalRandom = functionalRandom functionalRandom = functionalRandom
) )
case Unresolved => case Unresolved =>
throw new EagleInternalException( throw new EagleInternalException(
"UNRESOLVED truce offer at the end of the round" "UNRESOLVED truce offer at the end of the round"
) )
@@ -429,9 +398,9 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
factions: Vector[FactionT], factions: Vector[FactionT],
heroes: Vector[HeroT], heroes: Vector[HeroT],
currentDate: Date currentDate: Date
): RandomState[Vector[ActionResultT]] = { ): RandomState[Vector[ActionResultT]] =
allianceOffer.status match { allianceOffer.status match {
case Accepted => case Accepted =>
AllianceResolutionHelpers AllianceResolutionHelpers
.acceptedAllianceResult( .acceptedAllianceResult(
allianceOffer = allianceOffer, allianceOffer = allianceOffer,
@@ -441,7 +410,7 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
factions = factions, factions = factions,
functionalRandom = functionalRandom functionalRandom = functionalRandom
) )
case Rejected => case Rejected =>
AllianceResolutionHelpers AllianceResolutionHelpers
.rejectedAllianceResult( .rejectedAllianceResult(
allianceOffer = allianceOffer, allianceOffer = allianceOffer,
@@ -450,7 +419,7 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
functionalRandom = functionalRandom functionalRandom = functionalRandom
) )
.map(Vector(_)) .map(Vector(_))
case Imprisoned => case Imprisoned =>
RandomState( RandomState(
AllianceResolutionHelpers AllianceResolutionHelpers
.imprisonedAmbassadorResult( .imprisonedAmbassadorResult(
@@ -469,12 +438,11 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
functionalRandom = functionalRandom functionalRandom = functionalRandom
) )
.map(Vector(_)) .map(Vector(_))
case Unresolved => case Unresolved =>
throw new EagleInternalException( throw new EagleInternalException(
"UNRESOLVED alliance offer at the end of the round" "UNRESOLVED alliance offer at the end of the round"
) )
} }
}
private def resultsForBreakAlliance( private def resultsForBreakAlliance(
breakAllianceOffer: BreakAlliance, breakAllianceOffer: BreakAlliance,
@@ -482,9 +450,9 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
provinces: Vector[ProvinceT], provinces: Vector[ProvinceT],
factions: Vector[FactionT], factions: Vector[FactionT],
currentDate: Date currentDate: Date
): RandomState[Vector[ActionResultT]] = { ): RandomState[Vector[ActionResultT]] =
breakAllianceOffer.status match { breakAllianceOffer.status match {
case Accepted => case Accepted =>
BreakAllianceResolutionHelpers BreakAllianceResolutionHelpers
.acceptedResult( .acceptedResult(
breakAllianceOffer = breakAllianceOffer, breakAllianceOffer = breakAllianceOffer,
@@ -494,7 +462,7 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
functionalRandom = functionalRandom functionalRandom = functionalRandom
) )
.map(Vector(_)) .map(Vector(_))
case Rejected => case Rejected =>
// This one shouldn't be possible, but we'll resolve in the helper // This one shouldn't be possible, but we'll resolve in the helper
BreakAllianceResolutionHelpers BreakAllianceResolutionHelpers
.rejectedResult( .rejectedResult(
@@ -502,7 +470,7 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
functionalRandom = functionalRandom functionalRandom = functionalRandom
) )
.map(Vector(_)) .map(Vector(_))
case Imprisoned => case Imprisoned =>
RandomState( RandomState(
BreakAllianceResolutionHelpers BreakAllianceResolutionHelpers
.imprisonedAmbassadorResult( .imprisonedAmbassadorResult(
@@ -513,7 +481,7 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
), ),
functionalRandom functionalRandom
).map(Vector(_)) ).map(Vector(_))
case Unresolved => case Unresolved =>
throw new EagleInternalException( throw new EagleInternalException(
"UNRESOLVED break alliance at the end of the round" "UNRESOLVED break alliance at the end of the round"
) )
@@ -526,7 +494,6 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
) )
.map(Vector(_)) .map(Vector(_))
} }
}
private def resultsForInvitation( private def resultsForInvitation(
invitation: Invitation, invitation: Invitation,
@@ -536,7 +503,7 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
battalions: Vector[BattalionT], battalions: Vector[BattalionT],
currentDate: Date currentDate: Date
): RandomState[Vector[ActionResultT]] = invitation.status match { ): RandomState[Vector[ActionResultT]] = invitation.status match {
case Accepted => case Accepted =>
InvitationResolutionHelpers.acceptedInvitationResults( InvitationResolutionHelpers.acceptedInvitationResults(
acceptedInvitation = invitation, acceptedInvitation = invitation,
currentDate = currentDate, currentDate = currentDate,
@@ -545,7 +512,7 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
battalions = battalions, battalions = battalions,
functionalRandom = functionalRandom functionalRandom = functionalRandom
) )
case Rejected => case Rejected =>
InvitationResolutionHelpers InvitationResolutionHelpers
.rejectedInvitationResult( .rejectedInvitationResult(
rejectedInvitation = invitation, rejectedInvitation = invitation,
@@ -554,7 +521,7 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
functionalRandom = functionalRandom functionalRandom = functionalRandom
) )
.map(Vector(_)) .map(Vector(_))
case Imprisoned => case Imprisoned =>
RandomState( RandomState(
Vector( Vector(
InvitationResolutionHelpers.imprisonedAmbassadorResult( InvitationResolutionHelpers.imprisonedAmbassadorResult(
@@ -566,7 +533,7 @@ private object EndDiplomacyResolutionPhaseActionHelpers {
), ),
functionalRandom functionalRandom
) )
case Unresolved => case Unresolved =>
throw new EagleInternalException( throw new EagleInternalException(
"UNRESOLVED invitation at the end of the round" "UNRESOLVED invitation at the end of the round"
) )
@@ -12,7 +12,6 @@ case class EndFreeForAllBattleRequestPhaseAction(gameState: GameState)
override def immediateExecute: ActionResult = override def immediateExecute: ActionResult =
ActionResult( ActionResult(
`type` = END_FREE_FOR_ALL_BATTLE_REQUEST_PHASE, `type` = END_FREE_FOR_ALL_BATTLE_REQUEST_PHASE,
newRoundPhase = newRoundPhase = Some(NewRoundPhase(value = FREE_FOR_ALL_BATTLE_RESOLUTION))
Some(NewRoundPhase(value = FREE_FOR_ALL_BATTLE_RESOLUTION))
) )
} }
@@ -8,8 +8,7 @@ import net.eagle0.eagle.model.action_result.ActionResultT
import net.eagle0.eagle.model.proto_converters.province.ProvinceConverter import net.eagle0.eagle.model.proto_converters.province.ProvinceConverter
import net.eagle0.eagle.model.state.RoundPhase import net.eagle0.eagle.model.state.RoundPhase
case class EndFreeForAllDecisionPhaseAction(gameState: GameState) case class EndFreeForAllDecisionPhaseAction(gameState: GameState) extends ProtolessSequentialResultsAction {
extends ProtolessSequentialResultsAction {
override def results: Vector[ActionResultT] = override def results: Vector[ActionResultT] =
WithdrawnArmiesReturnHomeAction( WithdrawnArmiesReturnHomeAction(
@@ -9,14 +9,8 @@ import net.eagle0.eagle.internal.action_result.ActionResult
import net.eagle0.eagle.internal.changed_province.ChangedProvince import net.eagle0.eagle.internal.changed_province.ChangedProvince
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.library.actions.applier.ActionResultProtoApplier import net.eagle0.eagle.library.actions.applier.ActionResultProtoApplier
import net.eagle0.eagle.library.actions.impl.command.{ import net.eagle0.eagle.library.actions.impl.command.{CommandFactory, LegacyHandleRiotUtils}
CommandFactory, import net.eagle0.eagle.library.actions.impl.common.{RandomSequentialResultsAction, RandomStateProtoSequencer}
LegacyHandleRiotUtils
}
import net.eagle0.eagle.library.actions.impl.common.{
RandomSequentialResultsAction,
RandomStateProtoSequencer
}
import net.eagle0.eagle.library.util.command_choice_helpers.CommandChoiceHelpers import net.eagle0.eagle.library.util.command_choice_helpers.CommandChoiceHelpers
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
@@ -34,29 +28,31 @@ case class EndHandleRiotsPhaseAction(
ars.lastStateProto.provinces.values ars.lastStateProto.provinces.values
.filter(LegacyProvinceUtils.hasImminentRiot) .filter(LegacyProvinceUtils.hasImminentRiot)
.filterNot(_.hasActed) .filterNot(_.hasActed)
.foldLeft(ars) { case (sequencer, p) => .foldLeft(ars) {
commandsForProvince(p.id).map { opac => case (sequencer, p) =>
sequencer.withRandomAction { case (gs, fr) => commandsForProvince(p.id).map { opac =>
CommandChoiceHelpers sequencer.withRandomAction {
.handleRiotSelectedCommand( case (gs, fr) =>
actingFactionId = p.getRulingFactionId, CommandChoiceHelpers
gameState = gs, .handleRiotSelectedCommand(
availableCommands = opac.commands.toVector, actingFactionId = p.getRulingFactionId,
functionalRandom = fr gameState = gs,
) availableCommands = opac.commands.toVector,
.map { optCS => functionalRandom = fr
optCS.map { cs => )
commandFactory .map { optCS =>
.makeCommand( optCS.map { cs =>
actingFactionId = p.getRulingFactionId, commandFactory
gameState = GameStateConverter.fromProto(gs), .makeCommand(
availableCommand = cs.available, actingFactionId = p.getRulingFactionId,
selectedCommand = cs.selected gameState = GameStateConverter.fromProto(gs),
) availableCommand = cs.available,
}.get selectedCommand = cs.selected
} )
} }.get
}.get }
}
}.get
} }
private def riotOccurredResults( private def riotOccurredResults(
@@ -64,14 +60,15 @@ case class EndHandleRiotsPhaseAction(
): RandomStateProtoSequencer = ): RandomStateProtoSequencer =
ars.lastStateProto.provinces.values ars.lastStateProto.provinces.values
.filter(LegacyProvinceUtils.hasImminentRiot) .filter(LegacyProvinceUtils.hasImminentRiot)
.foldLeft(ars) { case (ars, p) => .foldLeft(ars) {
ars.withActionResult(_ => case (ars, p) =>
LegacyHandleRiotUtils ars.withActionResult(_ =>
.riotOccurredAr( LegacyHandleRiotUtils
p.getRulingFactionId, .riotOccurredAr(
ProvinceConverter.fromProto(p) p.getRulingFactionId,
) ProvinceConverter.fromProto(p)
) )
)
} }
def endPhaseResult( def endPhaseResult(
@@ -17,11 +17,7 @@ import net.eagle0.eagle.library.util.ProvinceEventUtils
import net.eagle0.eagle.library.EagleInternalException import net.eagle0.eagle.library.EagleInternalException
import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedProvinceC import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedProvinceC
import net.eagle0.eagle.model.action_result.changed_province.ChangedProvinceT import net.eagle0.eagle.model.action_result.changed_province.ChangedProvinceT
import net.eagle0.eagle.model.action_result.concrete.{ import net.eagle0.eagle.model.action_result.concrete.{ActionResultC, ChangedFactionC, ChangedHeroC}
ActionResultC,
ChangedFactionC,
ChangedHeroC
}
import net.eagle0.eagle.model.action_result.types.{ import net.eagle0.eagle.model.action_result.types.{
EndPlayerCommandsPhaseResultType, EndPlayerCommandsPhaseResultType,
EpidemicTookEffectResultType, EpidemicTookEffectResultType,
@@ -30,27 +26,14 @@ import net.eagle0.eagle.model.action_result.types.{
WeatherTookEffectResultType WeatherTookEffectResultType
} }
import net.eagle0.eagle.model.action_result.ActionResultT import net.eagle0.eagle.model.action_result.ActionResultT
import net.eagle0.eagle.model.proto_converters.{ import net.eagle0.eagle.model.proto_converters.{NotificationConverter, UnaffiliatedHeroConverter}
NotificationConverter,
UnaffiliatedHeroConverter
}
import net.eagle0.eagle.model.proto_converters.date.DateConverter import net.eagle0.eagle.model.proto_converters.date.DateConverter
import net.eagle0.eagle.model.proto_converters.faction.FactionConverter import net.eagle0.eagle.model.proto_converters.faction.FactionConverter
import net.eagle0.eagle.model.proto_converters.hero.HeroConverter import net.eagle0.eagle.model.proto_converters.hero.HeroConverter
import net.eagle0.eagle.model.proto_converters.province.{ import net.eagle0.eagle.model.proto_converters.province.{ProvinceConverter, ProvinceEventConverter}
ProvinceConverter, import net.eagle0.eagle.model.state.province.{BlizzardEvent, DroughtEvent, EpidemicEvent}
ProvinceEventConverter
}
import net.eagle0.eagle.model.state.province.{
BlizzardEvent,
DroughtEvent,
EpidemicEvent
}
import net.eagle0.eagle.model.state.unaffiliated_hero.RecruitmentInfo import net.eagle0.eagle.model.state.unaffiliated_hero.RecruitmentInfo
import net.eagle0.eagle.model.state.unaffiliated_hero.UnaffiliatedHeroType.{ import net.eagle0.eagle.model.state.unaffiliated_hero.UnaffiliatedHeroType.{Outlaw, Prisoner}
Outlaw,
Prisoner
}
import net.eagle0.eagle.model.state.RoundPhase import net.eagle0.eagle.model.state.RoundPhase
import net.eagle0.eagle.ProvinceId import net.eagle0.eagle.ProvinceId
@@ -148,8 +131,7 @@ case class EndPlayerCommandsPhaseAction(
actionResultType = PrisonerMoveTookEffectResultType, actionResultType = PrisonerMoveTookEffectResultType,
provinceId = Some(pid), provinceId = Some(pid),
actingFactionId = Some(prisonerReturned.fromFactionId), actingFactionId = Some(prisonerReturned.fromFactionId),
affectedFactionIds = affectedFactionIds = Vector(prisonerReturned.fromFactionId, prisonerReturned.toFactionId),
Vector(prisonerReturned.fromFactionId, prisonerReturned.toFactionId),
changedHeroes = Vector( changedHeroes = Vector(
ChangedHeroC( ChangedHeroC(
heroId = prisonerReturned.heroId, heroId = prisonerReturned.heroId,
@@ -181,13 +163,12 @@ case class EndPlayerCommandsPhaseAction(
VigorXPApplier.withVigorXp( VigorXPApplier.withVigorXp(
ActionResultC( ActionResultC(
actionResultType = deferredChange match { actionResultType = deferredChange match {
case _: EpidemicStarted => EpidemicTookEffectResultType case _: EpidemicStarted => EpidemicTookEffectResultType
case _: BlizzardEnded => WeatherTookEffectResultType case _: BlizzardEnded => WeatherTookEffectResultType
case _: BlizzardStarted => WeatherTookEffectResultType case _: BlizzardStarted => WeatherTookEffectResultType
case _: DroughtStarted => WeatherTookEffectResultType case _: DroughtStarted => WeatherTookEffectResultType
case _: DroughtEnded => WeatherTookEffectResultType case _: DroughtEnded => WeatherTookEffectResultType
case _: PrisonerMoved | _: PrisonerReturned | case _: PrisonerMoved | _: PrisonerReturned | _: CapturedHeroImprisoned | _: CapturedHeroExecuted |
_: CapturedHeroImprisoned | _: CapturedHeroExecuted |
_: CapturedHeroExiled | _: CapturedHeroReturned => _: CapturedHeroExiled | _: CapturedHeroReturned =>
throw new EagleInternalException( throw new EagleInternalException(
"Prisoner management changes should not be here" "Prisoner management changes should not be here"
@@ -284,11 +265,11 @@ case class EndPlayerCommandsPhaseAction(
fr: FunctionalRandom fr: FunctionalRandom
): RandomState[ActionResultT] = ): RandomState[ActionResultT] =
deferredChange match { deferredChange match {
case prisonerMoved: PrisonerMoved => case prisonerMoved: PrisonerMoved =>
onePrisonerMovedChange(pid, prisonerMoved, gs, fr) onePrisonerMovedChange(pid, prisonerMoved, gs, fr)
case prisonerReturned: PrisonerReturned => case prisonerReturned: PrisonerReturned =>
onePrisonerReturnedChange(pid, prisonerReturned, gs, fr) onePrisonerReturnedChange(pid, prisonerReturned, gs, fr)
case _ => notPrisonerChange(pid, deferredChange, gs, fr) case _ => notPrisonerChange(pid, deferredChange, gs, fr)
} }
private def deferredProvinceChangesResultsForProvince( private def deferredProvinceChangesResultsForProvince(
@@ -297,8 +278,9 @@ case class EndPlayerCommandsPhaseAction(
): RandomStateTSequencer = ): RandomStateTSequencer =
arsRS.lastStateProto.provinces(pid).deferredChanges.foldLeft(arsRS) { arsRS.lastStateProto.provinces(pid).deferredChanges.foldLeft(arsRS) {
case (acc, dc) => case (acc, dc) =>
acc.withRandomActionResult { case (gs, fr) => acc.withRandomActionResult {
oneDeferredProvinceChange(pid, dc, gs, fr) case (gs, fr) =>
oneDeferredProvinceChange(pid, dc, gs, fr)
} }
} }
@@ -306,8 +288,9 @@ case class EndPlayerCommandsPhaseAction(
ars: RandomStateTSequencer ars: RandomStateTSequencer
): RandomStateTSequencer = ): RandomStateTSequencer =
ars.lastStateProto.provinces.keys ars.lastStateProto.provinces.keys
.foldLeft(ars) { case (newArs, pid) => .foldLeft(ars) {
deferredProvinceChangesResultsForProvince(pid, newArs) case (newArs, pid) =>
deferredProvinceChangesResultsForProvince(pid, newArs)
} }
override def randomResults( override def randomResults(
@@ -328,18 +311,14 @@ case class EndPlayerCommandsPhaseAction(
.withRandomActionResults((gs, fr) => .withRandomActionResults((gs, fr) =>
CheckForFactionChangesAction( CheckForFactionChangesAction(
gameId = gs.gameId, gameId = gs.gameId,
factions = factions = gs.factions.values.toVector.map(FactionConverter.fromProto),
gs.factions.values.toVector.map(FactionConverter.fromProto), provinces = gs.provinces.values.toVector.map(ProvinceConverter.fromProto),
provinces =
gs.provinces.values.toVector.map(ProvinceConverter.fromProto),
heroes = gs.heroes.values.toVector.map(HeroConverter.fromProto), heroes = gs.heroes.values.toVector.map(HeroConverter.fromProto),
killedHeroIds = gs.killedHeroes.keys.toVector killedHeroIds = gs.killedHeroes.keys.toVector
).randomResults(fr) ).randomResults(fr)
) )
.withContinuance(deferredProvinceChangesResults) .withContinuance(deferredProvinceChangesResults)
.withProtolessSequentialResultsAction(gs => .withProtolessSequentialResultsAction(gs => HeroBackstoryUpdateActionGenerator.fromGameState(gs))
HeroBackstoryUpdateActionGenerator.fromGameState(gs)
)
.withActionResult(endPhaseResult) .withActionResult(endPhaseResult)
.actionResults .actionResults
} }
@@ -7,8 +7,7 @@ import net.eagle0.eagle.internal.action_result.ActionResult
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.library.actions.impl.common.DeterministicSingleResultAction import net.eagle0.eagle.library.actions.impl.common.DeterministicSingleResultAction
case class EndPleaseRecruitMePhaseAction(gameState: GameState) case class EndPleaseRecruitMePhaseAction(gameState: GameState) extends DeterministicSingleResultAction(gameState) {
extends DeterministicSingleResultAction(gameState) {
override def immediateExecute: ActionResult = override def immediateExecute: ActionResult =
ActionResult( ActionResult(
`type` = END_PLEASE_RECRUIT_ME_PHASE, `type` = END_PLEASE_RECRUIT_ME_PHASE,
@@ -28,8 +28,8 @@ case class EndUnaffiliatedHeroActionsPhaseAction(
private def heroRejoinResults: Vector[ActionResultT] = private def heroRejoinResults: Vector[ActionResultT] =
(for { (for {
province <- provinces.filter(_.rulingFactionId.isDefined) province <- provinces.filter(_.rulingFactionId.isDefined)
uh <- province.unaffiliatedHeroes uh <- province.unaffiliatedHeroes
.filter(_.unaffiliatedHeroType == Outlaw) .filter(_.unaffiliatedHeroType == Outlaw)
} yield UnaffiliatedHeroRejoinedAction.outlawRejoinResult( } yield UnaffiliatedHeroRejoinedAction.outlawRejoinResult(
uh, uh,
province.id, province.id,
@@ -38,45 +38,40 @@ case class EndUnaffiliatedHeroActionsPhaseAction(
private def oneProvincePleaseRecruitMeRequests( private def oneProvincePleaseRecruitMeRequests(
province: ProvinceT province: ProvinceT
): Vector[(LlmRequestT, UnaffiliatedHeroT)] = { ): Vector[(LlmRequestT, UnaffiliatedHeroT)] =
province.rulingFactionId province.rulingFactionId.map { fid =>
.map { fid => val actingFaction = factions.find(_.id == fid).get
val actingFaction = factions.find(_.id == fid).get province.unaffiliatedHeroes
province.unaffiliatedHeroes // Filter out outlaws that are going to rejoin the faction anyway
// Filter out outlaws that are going to rejoin the faction anyway .filterNot(uh => uh.unaffiliatedHeroType == Outlaw && uh.lastFactionId == province.rulingFactionId)
.filterNot(uh => .filter(uh =>
uh.unaffiliatedHeroType == Outlaw && uh.lastFactionId == province.rulingFactionId UnaffiliatedHeroUtils.willPleaseRecruitMe(
targetHero = heroes.find(_.id == uh.heroId).get,
unaffiliatedHero = uh,
actingFaction = actingFaction,
actingFactionLeader = heroes.find(_.id == actingFaction.factionHeadId).get,
allProvinces = provinces,
allFactions = factions,
currentDate = currentDate
) )
.filter(uh => )
UnaffiliatedHeroUtils.willPleaseRecruitMe( .map { uh =>
targetHero = heroes.find(_.id == uh.heroId).get, val textId = s"please recruit me $currentRoundId ${uh.heroId}"
unaffiliatedHero = uh, (
actingFaction = actingFaction, PleaseRecruitMeMessage(
actingFactionLeader = requestId = textId,
heroes.find(_.id == actingFaction.factionHeadId).get, eagleGameId = gameId,
allProvinces = provinces, recipientFactionIds = Vector(fid),
allFactions = factions, alwaysGenerate = false,
currentDate = currentDate heroId = uh.heroId,
) targetFactionId = fid,
provinceId = province.id
),
uh.withPleaseRecruitMeTextId(textId)
) )
.map { uh => }
val textId = s"please recruit me $currentRoundId ${uh.heroId}" }
(
PleaseRecruitMeMessage(
requestId = textId,
eagleGameId = gameId,
recipientFactionIds = Vector(fid),
alwaysGenerate = false,
heroId = uh.heroId,
targetFactionId = fid,
provinceId = province.id
),
uh.withPleaseRecruitMeTextId(textId)
)
}
}
.getOrElse(Vector()) .getOrElse(Vector())
}
override def results: Vector[ActionResultT] = { override def results: Vector[ActionResultT] = {
val cpsAndLlmRequests = provinces.map { p => val cpsAndLlmRequests = provinces.map { p =>
@@ -7,22 +7,15 @@ import net.eagle0.eagle.common.round_phase.RoundPhase.PLAYER_COMMANDS
import net.eagle0.eagle.internal.action_result.ActionResult import net.eagle0.eagle.internal.action_result.ActionResult
import net.eagle0.eagle.internal.game_state.GameState import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.library.actions.applier.ActionResultProtoApplier import net.eagle0.eagle.library.actions.applier.ActionResultProtoApplier
import net.eagle0.eagle.library.actions.impl.common.{ import net.eagle0.eagle.library.actions.impl.common.{RandomSequentialResultsAction, RandomStateProtoSequencer}
RandomSequentialResultsAction,
RandomStateProtoSequencer
}
import net.eagle0.eagle.library.util.EagleRequire.internalRequire import net.eagle0.eagle.library.util.EagleRequire.internalRequire
import net.eagle0.eagle.model.proto_converters.{ import net.eagle0.eagle.model.proto_converters.{ActionResultProtoConverter, BattalionConverter}
ActionResultProtoConverter,
BattalionConverter
}
import net.eagle0.eagle.model.proto_converters.date.DateConverter import net.eagle0.eagle.model.proto_converters.date.DateConverter
import net.eagle0.eagle.model.proto_converters.faction.FactionConverter import net.eagle0.eagle.model.proto_converters.faction.FactionConverter
import net.eagle0.eagle.model.proto_converters.hero.HeroConverter import net.eagle0.eagle.model.proto_converters.hero.HeroConverter
import net.eagle0.eagle.model.proto_converters.province.ProvinceConverter import net.eagle0.eagle.model.proto_converters.province.ProvinceConverter
case class EndVassalCommandsPhaseAction(gameState: GameState) case class EndVassalCommandsPhaseAction(gameState: GameState) extends RandomSequentialResultsAction(gameState) {
extends RandomSequentialResultsAction(gameState) {
override def randomResults( override def randomResults(
functionalRandom: FunctionalRandom, functionalRandom: FunctionalRandom,
actionResultProtoApplier: ActionResultProtoApplier actionResultProtoApplier: ActionResultProtoApplier
@@ -44,15 +37,12 @@ case class EndVassalCommandsPhaseAction(gameState: GameState)
gameId = gs.gameId, gameId = gs.gameId,
currentDate = DateConverter.fromProto(gs.currentDate), currentDate = DateConverter.fromProto(gs.currentDate),
currentRoundId = gs.currentRoundId, currentRoundId = gs.currentRoundId,
provinces = provinces = gs.provinces.values.map(ProvinceConverter.fromProto).toVector,
gs.provinces.values.map(ProvinceConverter.fromProto).toVector,
factions = gs.factions.values factions = gs.factions.values
.map(FactionConverter.fromProto) .map(FactionConverter.fromProto)
.toVector, .toVector,
battalions = battalions = gs.battalions.values.toVector.map(BattalionConverter.fromProto),
gs.battalions.values.toVector.map(BattalionConverter.fromProto), getHero = hid => gameState.heroes.get(hid).map(HeroConverter.fromProto),
getHero =
hid => gameState.heroes.get(hid).map(HeroConverter.fromProto),
battalionTypes = gs.battalionTypes.toVector, battalionTypes = gs.battalionTypes.toVector,
hid => gs.heroes(hid).backstoryVersions.last.textId hid => gs.heroes(hid).backstoryVersions.last.textId
) )
@@ -62,8 +52,7 @@ case class EndVassalCommandsPhaseAction(gameState: GameState)
gameId = gs.gameId, gameId = gs.gameId,
currentDate = DateConverter.fromProto(gs.currentDate), currentDate = DateConverter.fromProto(gs.currentDate),
currentRoundId = gs.currentRoundId, currentRoundId = gs.currentRoundId,
provinces = provinces = gs.provinces.values.map(ProvinceConverter.fromProto).toVector,
gs.provinces.values.map(ProvinceConverter.fromProto).toVector,
factions = gs.factions.values factions = gs.factions.values
.map(FactionConverter.fromProto) .map(FactionConverter.fromProto)
.toVector, .toVector,
@@ -73,17 +62,13 @@ case class EndVassalCommandsPhaseAction(gameState: GameState)
.withRandomActionResults((gs, fr) => .withRandomActionResults((gs, fr) =>
CheckForFactionChangesAction( CheckForFactionChangesAction(
gameId = gs.gameId, gameId = gs.gameId,
factions = factions = gs.factions.values.map(FactionConverter.fromProto).toVector,
gs.factions.values.map(FactionConverter.fromProto).toVector, provinces = gs.provinces.values.map(ProvinceConverter.fromProto).toVector,
provinces =
gs.provinces.values.map(ProvinceConverter.fromProto).toVector,
heroes = gs.heroes.values.map(HeroConverter.fromProto).toVector, heroes = gs.heroes.values.map(HeroConverter.fromProto).toVector,
killedHeroIds = gs.killedHeroes.keys.toVector killedHeroIds = gs.killedHeroes.keys.toVector
).randomResults(fr).map(_.map(ActionResultProtoConverter.toProto)) ).randomResults(fr).map(_.map(ActionResultProtoConverter.toProto))
) )
.withProtolessSequentialResultsAction(gs => .withProtolessSequentialResultsAction(gs => HeroBackstoryUpdateActionGenerator.fromGameState(gs))
HeroBackstoryUpdateActionGenerator.fromGameState(gs)
)
.withActionWithResultingState(gs => .withActionWithResultingState(gs =>
actionResultProtoApplier.applyActionResult( actionResultProtoApplier.applyActionResult(
gs, gs,

Some files were not shown because too many files have changed in this diff Show More