mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 22:35:42 +00:00
Add protoless ProvinceViewFilter overload with faction filtering (#5034)
- Add filteredProvinceView(ProvinceT, ScalaGameState, FactionId) overload - Use protoless FactionUtils.hasAlliance, Visibility.hasFullVisibility, and ProvinceUtils.incomingOthers - Add helper methods: fullProvinceInfoScala, maybeIncomingAttackersScala, unaffiliatedHeroInfoScala - Handle reconned provinces directly from Scala FactionT.reconnedProvinces GameStateViewFilter improvements: - Eliminate GameStateConverter.toProto() call in Scala overload - Use new ProvinceViewFilter Scala overload for faction filtering - Convert battalionTypes and chronicleEntries to proto only at output boundary 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+15
-2
@@ -179,6 +179,18 @@ Remove Legacy* utilities by migrating remaining callers:
|
||||
- Removed `GameStateConverter.toProto()` and `UnaffiliatedHeroConverter.toProto()` calls
|
||||
- Factory is now fully protoless internally (still returns proto types for API boundary)
|
||||
|
||||
**ProvinceViewFilter** - added Scala overload with faction filtering:
|
||||
- New `filteredProvinceView(province: ProvinceT, gs: ScalaGameState, factionId: FactionId)` overload
|
||||
- Uses protoless `FactionUtils.hasAlliance`, `Visibility.hasFullVisibility`, and `ProvinceUtils.incomingOthers`
|
||||
- Handles reconned provinces directly from Scala `FactionT.reconnedProvinces` (already Scala type)
|
||||
- Added helper methods: `fullProvinceInfoScala`, `maybeIncomingAttackersScala`, `unaffiliatedHeroInfoScala`
|
||||
- Events still converted to proto at the end (ProvinceView.knownEvents uses proto events)
|
||||
|
||||
**GameStateViewFilter** - eliminated GameStateConverter.toProto() call:
|
||||
- Scala overload now fully protoless internally
|
||||
- Uses the new ProvinceViewFilter Scala overload with faction filtering
|
||||
- Converts `battalionTypes` and `chronicleEntries` to proto only at output boundary
|
||||
|
||||
## Key Files
|
||||
|
||||
### Protoless Model Types
|
||||
@@ -195,6 +207,7 @@ Remove Legacy* utilities by migrating remaining callers:
|
||||
## Notes
|
||||
|
||||
- The AI client code (`src/main/scala/net/eagle0/eagle/ai/`) is now fully protoless
|
||||
- Proto GameState is still needed at boundaries: persistence, gRPC to Shardok, and client view creation
|
||||
- Proto GameState is still needed at boundaries: persistence, gRPC to Shardok
|
||||
- `PerformUnaffiliatedHeroesAction` and the LLM pipeline use protoless `GameState`
|
||||
- Next focus: view_filters package to reduce proto usage in client view creation
|
||||
- `GameStateViewFilter` Scala overload is now fully protoless internally (converts to proto only at output)
|
||||
- `ProvinceViewFilter` has Scala overloads for all three modes: no filtering, faction filtering, and withdrawn-from view
|
||||
|
||||
@@ -142,9 +142,10 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/view_filters:faction_view_filter",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/view_filters:hero_view_filter",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/view_filters:province_view_filter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:battalion_type_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:round_phase_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/chronicle_entry:chronicle_entry_converter",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/date",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/view/province:province_view",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
],
|
||||
@@ -230,6 +231,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:incoming_army_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:province_event_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util:stat_with_condition_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils:legacy_faction_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/hero:hero_utils",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/hero:legacy_hero_utils",
|
||||
@@ -244,6 +246,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/view/province:province_view",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/province",
|
||||
"//src/main/scala/net/eagle0/eagle/model/state/unaffiliated_hero",
|
||||
"//src/main/scala/net/eagle0/eagle/model/view/incoming_army:incoming_army_view",
|
||||
"//src/main/scala/net/eagle0/eagle/model/view/province:full_province_info",
|
||||
"//src/main/scala/net/eagle0/eagle/model/view/province:province_view",
|
||||
|
||||
+8
-10
@@ -1,9 +1,10 @@
|
||||
package net.eagle0.eagle.library.util.view_filters
|
||||
|
||||
import net.eagle0.eagle.internal.game_state.GameState
|
||||
import net.eagle0.eagle.model.proto_converters.chronicle_entry.ChronicleEntryConverter
|
||||
import net.eagle0.eagle.model.proto_converters.date.DateConverter
|
||||
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
|
||||
import net.eagle0.eagle.model.proto_converters.view.province.ProvinceViewConverter
|
||||
import net.eagle0.eagle.model.proto_converters.BattalionTypeConverter
|
||||
import net.eagle0.eagle.model.proto_converters.RoundPhaseConverter
|
||||
import net.eagle0.eagle.model.state.game_state.GameState as ScalaGameState
|
||||
import net.eagle0.eagle.views.faction_view.FactionView
|
||||
@@ -13,27 +14,24 @@ import net.eagle0.eagle.FactionId
|
||||
object GameStateViewFilter {
|
||||
|
||||
/**
|
||||
* Scala GameState overload - uses Scala sub-filters where available. Converts to proto only for ProvinceViewFilter
|
||||
* (which still needs proto for faction filtering).
|
||||
* Scala GameState overload - fully protoless internal processing, converts to proto only at the output boundary.
|
||||
*/
|
||||
def filteredGameState(
|
||||
gs: ScalaGameState,
|
||||
factionId: Option[FactionId]
|
||||
): GameStateView = {
|
||||
// Convert to proto only for ProvinceViewFilter which still needs it
|
||||
val protoGs = GameStateConverter.toProto(gs)
|
||||
val factions = gs.factions.values.toVector
|
||||
|
||||
GameStateView(
|
||||
currentRoundId = gs.currentRoundId,
|
||||
currentPhase = RoundPhaseConverter.toProto(gs.currentPhase),
|
||||
currentDate = gs.currentDate.map(DateConverter.toProto),
|
||||
provinces = protoGs.provinces.map {
|
||||
provinces = gs.provinces.map {
|
||||
case (pid, prov) =>
|
||||
pid -> ProvinceViewConverter.toProto(
|
||||
factionId
|
||||
.map(fid => ProvinceViewFilter.filteredProvinceView(prov, protoGs, fid))
|
||||
.getOrElse(ProvinceViewFilter.filteredProvinceView(prov, protoGs))
|
||||
.map(fid => ProvinceViewFilter.filteredProvinceView(prov, gs, fid))
|
||||
.getOrElse(ProvinceViewFilter.filteredProvinceView(prov, gs))
|
||||
)
|
||||
},
|
||||
heroes = (gs.heroes ++ gs.killedHeroes).map {
|
||||
@@ -52,8 +50,8 @@ object GameStateViewFilter {
|
||||
outstandingBattles = gs.outstandingBattles
|
||||
.map(ob => BattleFilter.filterBattle(ob, factions, factionId)),
|
||||
gameId = gs.gameId,
|
||||
battalionTypes = protoGs.battalionTypes,
|
||||
chronicleEntries = protoGs.chronicleEntries
|
||||
battalionTypes = gs.battalionTypes.map(BattalionTypeConverter.toProto),
|
||||
chronicleEntries = gs.chronicleEntries.map(ChronicleEntryConverter.toProto)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+161
-1
@@ -7,7 +7,7 @@ import net.eagle0.eagle.internal.hero.Hero
|
||||
import net.eagle0.eagle.internal.province.Province
|
||||
import net.eagle0.eagle.internal.unaffiliated_hero.UnaffiliatedHero
|
||||
import net.eagle0.eagle.library.util.{IncomingArmyUtils, ProvinceEventUtils}
|
||||
import net.eagle0.eagle.library.util.faction_utils.LegacyFactionUtils
|
||||
import net.eagle0.eagle.library.util.faction_utils.{FactionUtils, LegacyFactionUtils}
|
||||
import net.eagle0.eagle.library.util.hero.HeroUtils
|
||||
import net.eagle0.eagle.library.util.hero.LegacyHeroUtils
|
||||
import net.eagle0.eagle.library.util.province.LegacyProvinceUtils
|
||||
@@ -153,6 +153,166 @@ object ProvinceViewFilter {
|
||||
)
|
||||
}
|
||||
|
||||
/** Filtered view for a specific faction (Scala overload) */
|
||||
def filteredProvinceView(
|
||||
province: ProvinceT,
|
||||
gs: ScalaGameState,
|
||||
factionId: FactionId
|
||||
): ProvinceView = {
|
||||
val factions = gs.factions.values.toVector
|
||||
|
||||
val relevantFactions = factions.filter(possibleAlly =>
|
||||
FactionUtils.hasAlliance(possibleAlly.id, factionId, factions)
|
||||
) ++ gs.factions.get(factionId)
|
||||
|
||||
// Take the most recent reconned view from this faction or any allies
|
||||
// (already stored as Scala ProvinceView in Scala GameState)
|
||||
val reconnedView =
|
||||
relevantFactions
|
||||
.flatMap(_.reconnedProvinces)
|
||||
.filter(_.id == province.id)
|
||||
.maxByOption(_.asOf)
|
||||
|
||||
val showAll = province.rulingFactionId.exists { rfid =>
|
||||
Visibility.hasFullVisibility(factionId, rfid, factions)
|
||||
}
|
||||
|
||||
// Convert Scala events to proto for filtering (ProvinceView uses proto events)
|
||||
val activeEventsProto = province.activeEvents.map(ProvinceEventConverter.toProto)
|
||||
val knownEvents = activeEventsProto.filter { pe =>
|
||||
if showAll then true
|
||||
else if reconnedView.exists(_.knownEvents.contains(pe)) then true
|
||||
else eventIsUniversallyVisible(pe)
|
||||
}
|
||||
|
||||
val incomingAttackers =
|
||||
myIncomingArmiesScala(province, gs, factionId) ++ (if showAll then
|
||||
maybeIncomingAttackersScala(
|
||||
province,
|
||||
gs
|
||||
)
|
||||
else Vector())
|
||||
|
||||
val incomingSupplies =
|
||||
if showAll then
|
||||
province.incomingShipments.map { shipment =>
|
||||
IncomingArmyView(
|
||||
factionId = shipment.factionId,
|
||||
heroCount = 0,
|
||||
troopCount = 0,
|
||||
originProvinceId = shipment.originProvinceId
|
||||
.getOrElse(shipment.destinationProvinceId),
|
||||
destinationProvinceId = shipment.destinationProvinceId,
|
||||
arrivalRoundId = shipment.arrivalRound,
|
||||
food = shipment.supplies.food,
|
||||
gold = shipment.supplies.gold
|
||||
)
|
||||
}
|
||||
else Vector()
|
||||
|
||||
val fullInfo = Option
|
||||
.when(showAll)(fullProvinceInfoScala(province, gs, Some(factionId)))
|
||||
.orElse(
|
||||
reconnedView.flatMap(_.fullInfo).map(deadHeroesFilteredOutScala(_, gs))
|
||||
)
|
||||
|
||||
ProvinceView(
|
||||
id = province.id,
|
||||
name = province.name,
|
||||
rulingFactionId = province.rulingFactionId,
|
||||
fullInfo = fullInfo,
|
||||
knownEvents = knownEvents,
|
||||
asOf =
|
||||
if showAll then gs.currentDate
|
||||
else reconnedView.flatMap(_.asOf),
|
||||
knownIncomingArmies = incomingAttackers ++ incomingSupplies
|
||||
)
|
||||
}
|
||||
|
||||
private def deadHeroesFilteredOutScala(
|
||||
fullProvinceInfo: FullProvinceInfo,
|
||||
gameState: ScalaGameState
|
||||
): FullProvinceInfo =
|
||||
fullProvinceInfo.copy(
|
||||
rulingFactionHeroIds =
|
||||
fullProvinceInfo.rulingFactionHeroIds.filterNot(hid => gameState.killedHeroes.contains(hid)),
|
||||
unaffiliatedHeroes =
|
||||
fullProvinceInfo.unaffiliatedHeroes.filterNot(uh => gameState.killedHeroes.contains(uh.heroId))
|
||||
)
|
||||
|
||||
private def fullProvinceInfoScala(
|
||||
province: ProvinceT,
|
||||
gs: ScalaGameState,
|
||||
factionId: Option[FactionId]
|
||||
): FullProvinceInfo = {
|
||||
val factions = gs.factions.values.toVector
|
||||
val factionLeaderIds = factions.flatMap(_.leaderIds)
|
||||
|
||||
FullProvinceInfo(
|
||||
rulingHeroId = province.rulingHeroId,
|
||||
rulingFactionHeroIds = province.rulingFactionHeroIds
|
||||
.map(hid => gs.heroes(hid))
|
||||
.sortWith(HeroUtils.sortOrderer(factionLeaderIds))
|
||||
.map(_.id),
|
||||
battalions = province.battalionIds
|
||||
.map(bid => gs.battalions(bid))
|
||||
.map(BattalionViewFilter.filteredBattalionViewBelongingToPlayer)
|
||||
.sortBy(_.typeId.value),
|
||||
defendingArmy = province.defendingArmy
|
||||
.map(army => ArmyFilter.filterArmy(army, gs.battalions, factionId)),
|
||||
economy = province.economy.toFloat,
|
||||
agriculture = province.agriculture.toFloat,
|
||||
infrastructure = province.infrastructure.toFloat,
|
||||
economyDevastation = province.economyDevastation.toFloat,
|
||||
agricultureDevastation = province.agricultureDevastation.toFloat,
|
||||
infrastructureDevastation = province.infrastructureDevastation.toFloat,
|
||||
gold = province.gold,
|
||||
food = province.food,
|
||||
priceIndex = province.priceIndex,
|
||||
goldCap = ProvinceUtils.goldCap(province),
|
||||
foodCap = ProvinceUtils.foodCap(province),
|
||||
support = Some(supportScala(province.support)),
|
||||
foodConsumption = ProvinceUtils.monthlyFoodConsumption(province, gs),
|
||||
provinceOrders = ProvinceOrderTypeConverter.toProto(province.provinceOrders),
|
||||
hexMapName = province.hexMapName,
|
||||
castleCount = province.castleCount,
|
||||
heroCap = province.heroCap,
|
||||
unaffiliatedHeroes = province.unaffiliatedHeroes.map(unaffiliatedHeroInfoScala(_, gs)),
|
||||
rulerIsTraveling = province.rulerIsTraveling
|
||||
)
|
||||
}
|
||||
|
||||
private def maybeIncomingAttackersScala(
|
||||
province: ProvinceT,
|
||||
gs: ScalaGameState
|
||||
): Vector[IncomingArmyView] =
|
||||
ProvinceUtils
|
||||
.incomingOthers(province, gs.currentRoundId)
|
||||
.filter(ma =>
|
||||
IncomingArmyUtils.isHostileProvince(
|
||||
province = province,
|
||||
factionId = ma.army.factionId,
|
||||
gameState = gs
|
||||
)
|
||||
)
|
||||
.map(army => incomingArmyInfoScala(ma = army, gs = gs, showDetails = false))
|
||||
|
||||
private def unaffiliatedHeroInfoScala(
|
||||
uh: net.eagle0.eagle.model.state.unaffiliated_hero.UnaffiliatedHeroT,
|
||||
gs: ScalaGameState
|
||||
): UnaffiliatedHeroBasics = {
|
||||
val hero = gs.heroes(uh.heroId)
|
||||
val factions = gs.factions.values.toVector
|
||||
UnaffiliatedHeroBasics(
|
||||
heroId = hero.id,
|
||||
nameTextId = hero.nameTextId,
|
||||
profession = hero.profession,
|
||||
status = uh.unaffiliatedHeroType,
|
||||
isFactionLeader = FactionUtils.isFactionLeader(hero.id, factions),
|
||||
recruitmentInfo = uh.recruitmentInfo
|
||||
)
|
||||
}
|
||||
|
||||
private def myIncomingArmiesScala(
|
||||
province: ProvinceT,
|
||||
gs: ScalaGameState,
|
||||
|
||||
@@ -132,6 +132,7 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/availability:__pkg__",
|
||||
"//src/main/scala/net/eagle0/eagle/library/actions/impl:__subpackages__",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers:__pkg__",
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/view_filters:__pkg__",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state:__pkg__",
|
||||
"//src/test/scala/net/eagle0/eagle/library/actions/impl:__subpackages__",
|
||||
"//src/test/scala/net/eagle0/eagle/library/util:__subpackages__",
|
||||
|
||||
@@ -4,6 +4,7 @@ scala_library(
|
||||
name = "chronicle_entry_converter",
|
||||
srcs = ["ChronicleEntryConverter.scala"],
|
||||
visibility = [
|
||||
"//src/main/scala/net/eagle0/eagle/library/util/view_filters:__pkg__",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:__pkg__",
|
||||
"//src/main/scala/net/eagle0/eagle/model/proto_converters:__subpackages__",
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user