Combine castles and infrastructure for hero cap (#8766)

This commit is contained in:
2026-07-23 22:18:15 -07:00
committed by GitHub
parent 6ae78186e2
commit a588c2dd9c
16 changed files with 53 additions and 27 deletions
@@ -235,7 +235,8 @@ minimumTrustToOfferTruce 0 Int
minimumTrustToInvite 20 Int
minimumTrustToOfferAlliance 30 Int
requiredPowerMultiplierForDismiss 1.5 Double
heroCapDivisor 3.0 Double
heroCapDivisor 10.0 Double
heroCapPerCastle 5 Int
overHeroCapLoyaltyDelta -1 Double
minimumAgilityForArchery 75 Double
minimumAgilityForStartFire 70 Double
1 actionVigorCost 15 Int
235 minimumTrustToInvite 20 Int
236 minimumTrustToOfferAlliance 30 Int
237 requiredPowerMultiplierForDismiss 1.5 Double
238 heroCapDivisor 3.0 10.0 Double
239 heroCapPerCastle 5 Int
240 overHeroCapLoyaltyDelta -1 Double
241 minimumAgilityForArchery 75 Double
242 minimumAgilityForStartFire 70 Double
@@ -95,7 +95,7 @@ object ProvinceUpdateHelpers {
agricultureDevastation =
(p.agricultureDevastation + cp.agricultureDevastationDelta.getOrElse(0.0)).max(0.0).min(p.agriculture),
infrastructureDevastation = newInfrastructureDevastation,
heroCap = ProvinceUtils.heroCap(newInfrastructure - newInfrastructureDevastation),
heroCap = ProvinceUtils.heroCap(p.castleCount, newInfrastructure - newInfrastructureDevastation),
support = (p.support + cp.supportDelta.getOrElse(0.0)).max(0.0).min(100.0)
)
}
@@ -3100,7 +3100,20 @@ scala_setting_library(
name = "hero_cap_divisor",
setting_name = "HeroCapDivisor",
setting_type = "Double",
setting_value = "3.0",
setting_value = "10.0",
visibility = [
"//src/main/scala/net/eagle0/eagle:__subpackages__",
"//src/main/scala/net/eagle0/util:__subpackages__",
"//src/test/resources/net/eagle0/shardok/maps:__pkg__",
"//src/test/scala/net/eagle0/eagle:__subpackages__",
],
)
scala_setting_library(
name = "hero_cap_per_castle",
setting_name = "HeroCapPerCastle",
setting_type = "Int",
setting_value = "5",
visibility = [
"//src/main/scala/net/eagle0/eagle:__subpackages__",
"//src/main/scala/net/eagle0/util:__subpackages__",
@@ -125,6 +125,7 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library/settings:gold_increase_per_economy",
"//src/main/scala/net/eagle0/eagle/library/settings:gold_per_hero_held_back",
"//src/main/scala/net/eagle0/eagle/library/settings:hero_cap_divisor",
"//src/main/scala/net/eagle0/eagle/library/settings:hero_cap_per_castle",
"//src/main/scala/net/eagle0/eagle/library/settings:history_backend",
"//src/main/scala/net/eagle0/eagle/library/settings:improvement_per_stat",
"//src/main/scala/net/eagle0/eagle/library/settings:improvement_reduction_max",
@@ -18,6 +18,7 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library/settings:food_increase_per_agriculture",
"//src/main/scala/net/eagle0/eagle/library/settings:gold_increase_per_economy",
"//src/main/scala/net/eagle0/eagle/library/settings:hero_cap_divisor",
"//src/main/scala/net/eagle0/eagle/library/settings:hero_cap_per_castle",
"//src/main/scala/net/eagle0/eagle/library/settings:min_support_for_taxes",
"//src/main/scala/net/eagle0/eagle/library/settings:per_development_resource_limit",
"//src/main/scala/net/eagle0/eagle/library/util:battalion_utils",
@@ -7,6 +7,7 @@ import net.eagle0.eagle.library.settings.{
FoodIncreasePerAgriculture,
GoldIncreasePerEconomy,
HeroCapDivisor,
HeroCapPerCastle,
MinSupportForTaxes,
PerDevelopmentResourceLimit
}
@@ -69,12 +70,12 @@ object ProvinceUtils {
(province.infrastructure - province.infrastructureDevastation).max(0.0)
def heroCap(province: ProvinceT): Int =
heroCap(effectiveInfrastructure(province))
heroCap(province.castleCount, effectiveInfrastructure(province))
def heroCap(effectiveInfrastructure: Double): Int = {
def heroCap(castleCount: Int, effectiveInfrastructure: Double): Int = {
val divisor = HeroCapDivisor.doubleValue
if !(divisor > 0.0) then throw new EagleInternalException(s"HeroCapDivisor must be positive, but was $divisor")
(effectiveInfrastructure.max(0.0) / divisor).floor.toInt.max(1)
castleCount * HeroCapPerCastle.intValue + (effectiveInfrastructure.max(0.0) / divisor).floor.toInt
}
def goldCap(province: ProvinceT, isFocusProvince: Boolean): Int =
@@ -149,7 +149,7 @@ object ProvinceConverter {
pendingConquestInfo = pendingConquestInfo.map(PendingConquestInfoConverter.toProto),
hexMapName = hexMapName,
castleCount = castleCount,
heroCap = ProvinceUtils.heroCap((infrastructure - infrastructureDevastation).max(0.0))
heroCap = ProvinceUtils.heroCap(castleCount, (infrastructure - infrastructureDevastation).max(0.0))
)
}
@@ -242,7 +242,7 @@ object ProvinceConverter {
pendingConquestInfo = pendingConquestInfoProto.map(PendingConquestInfoConverter.fromProto),
hexMapName = hexMapName,
castleCount = castleCount,
heroCap = ProvinceUtils.heroCap((infrastructure - infrastructureDevastation).max(0.0))
heroCap = ProvinceUtils.heroCap(castleCount, (infrastructure - infrastructureDevastation).max(0.0))
)
}
}
@@ -174,7 +174,7 @@ object GameParametersUtils {
_.economy := provinceOverrides.economy,
_.agriculture := provinceOverrides.agriculture,
_.infrastructure := provinceOverrides.infrastructure,
_.heroCap := ProvinceUtils.heroCap(provinceOverrides.infrastructure),
_.heroCap := ProvinceUtils.heroCap(province.castleCount, provinceOverrides.infrastructure),
_.priceIndex := PriceIndexUtils.steadyState(
provinceOverrides.economy,
provinceOverrides.agriculture,
@@ -208,7 +208,7 @@ object GameParametersUtils {
economy = provinceOverrides.economy,
agriculture = provinceOverrides.agriculture,
infrastructure = provinceOverrides.infrastructure,
heroCap = ProvinceUtils.heroCap(provinceOverrides.infrastructure),
heroCap = ProvinceUtils.heroCap(province.castleCount, provinceOverrides.infrastructure),
priceIndex = PriceIndexUtils.steadyState(
provinceOverrides.economy,
provinceOverrides.agriculture,
@@ -249,7 +249,7 @@ object GameParametersUtils {
economy = overrides.economy,
agriculture = overrides.agriculture,
infrastructure = overrides.infrastructure,
heroCap = ProvinceUtils.heroCap(overrides.infrastructure),
heroCap = ProvinceUtils.heroCap(p.castleCount, overrides.infrastructure),
priceIndex = PriceIndexUtils.steadyState(
overrides.economy,
overrides.agriculture,
@@ -315,7 +315,7 @@ object TutorialGameCreation {
economy = if po.economy > 0 then po.economy else baseProvince.economy,
agriculture = if po.agriculture > 0 then po.agriculture else baseProvince.agriculture,
infrastructure = infrastructure,
heroCap = ProvinceUtils.heroCap(infrastructure)
heroCap = ProvinceUtils.heroCap(baseProvince.castleCount, infrastructure)
)
)
}
@@ -1,6 +1,6 @@
package net.eagle0.eagle.library.actions.applier
import net.eagle0.eagle.library.settings.{ExtraXpForStatBumpOver100, HeroCapDivisor, XpForStatBump}
import net.eagle0.eagle.library.settings.{ExtraXpForStatBumpOver100, HeroCapDivisor, HeroCapPerCastle, XpForStatBump}
import net.eagle0.eagle.model.action_result.{NotificationDetails, NotificationT}
import net.eagle0.eagle.model.action_result.changed_province.concrete.ChangedProvinceC
import net.eagle0.eagle.model.action_result.concrete.{
@@ -43,7 +43,8 @@ class ActionResultApplierImplTest extends AnyFlatSpec with Matchers with BeforeA
override def beforeEach(): Unit = {
XpForStatBump.setIntValue(100)
ExtraXpForStatBumpOver100.setIntValue(50)
HeroCapDivisor.setDoubleValue(3.0)
HeroCapDivisor.setDoubleValue(10.0)
HeroCapPerCastle.setIntValue(5)
}
private val applier = ActionResultApplierImpl(None)
@@ -126,6 +127,7 @@ class ActionResultApplierImplTest extends AnyFlatSpec with Matchers with BeforeA
provinces = Map(
provinceId -> ProvinceC(
id = provinceId,
castleCount = 2,
infrastructure = 45,
infrastructureDevastation = 12,
heroCap = 99
@@ -144,7 +146,7 @@ class ActionResultApplierImplTest extends AnyFlatSpec with Matchers with BeforeA
val result = applier.applyActionResult(startingState, actionResult)
result.resultingState.provinces(provinceId).heroCap shouldBe 9
result.resultingState.provinces(provinceId).heroCap shouldBe 12
}
it should "not advance command tokens for automatic results with an acting faction" in {
@@ -8,6 +8,7 @@ scala_test(
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier_impl",
"//src/main/scala/net/eagle0/eagle/library/settings:extra_xp_for_stat_bump_over100",
"//src/main/scala/net/eagle0/eagle/library/settings:hero_cap_divisor",
"//src/main/scala/net/eagle0/eagle/library/settings:hero_cap_per_castle",
"//src/main/scala/net/eagle0/eagle/library/settings:xp_for_stat_bump",
"//src/main/scala/net/eagle0/eagle/model/action_result:notification_trait",
"//src/main/scala/net/eagle0/eagle/model/action_result/changed_province/concrete",
@@ -122,6 +122,7 @@ scala_test(
"//src/main/scala/net/eagle0/eagle/library/settings:focus_province_storage_multiplier",
"//src/main/scala/net/eagle0/eagle/library/settings:food_increase_per_agriculture",
"//src/main/scala/net/eagle0/eagle/library/settings:hero_cap_divisor",
"//src/main/scala/net/eagle0/eagle/library/settings:hero_cap_per_castle",
"//src/main/scala/net/eagle0/eagle/library/settings:improvement_per_stat",
"//src/main/scala/net/eagle0/eagle/library/settings:improvement_reduction_max",
"//src/main/scala/net/eagle0/eagle/library/settings:improvement_support_gain",
@@ -7,6 +7,7 @@ import net.eagle0.eagle.library.settings.{
FocusProvinceStorageMultiplier,
FoodIncreasePerAgriculture,
HeroCapDivisor,
HeroCapPerCastle,
ImprovementPerStat,
ImprovementReductionMax,
ImprovementSupportGain,
@@ -83,7 +84,8 @@ class ProvinceUtilsTest extends AnyFlatSpec with Matchers with BeforeAndAfterEac
BaseResourceLimit.setIntValue(5000)
PerDevelopmentResourceLimit.setIntValue(50)
FocusProvinceStorageMultiplier.setDoubleValue(1.5)
HeroCapDivisor.setDoubleValue(3.0)
HeroCapDivisor.setDoubleValue(10.0)
HeroCapPerCastle.setIntValue(5)
}
"provinceLeader" should "return the faction head if present" in {
@@ -376,20 +378,21 @@ class ProvinceUtilsTest extends AnyFlatSpec with Matchers with BeforeAndAfterEac
ProvinceUtils.containsFactionLeader(province, Vector(actingFaction)) shouldBe false
}
"heroCap" should "use effective infrastructure divided by the configured divisor" in {
"heroCap" should "add castle and effective infrastructure capacity" in {
val province = ProvinceC(
id = 7,
castleCount = 2,
infrastructure = 45,
infrastructureDevastation = 12
)
ProvinceUtils.heroCap(province) shouldBe 11
ProvinceUtils.heroCap(province) shouldBe 13
}
it should "floor fractional values and enforce a minimum cap of one" in {
ProvinceUtils.heroCap(effectiveInfrastructure = 100) shouldBe 33
ProvinceUtils.heroCap(effectiveInfrastructure = 8.99) shouldBe 2
ProvinceUtils.heroCap(effectiveInfrastructure = 0) shouldBe 1
it should "floor the infrastructure contribution" in {
ProvinceUtils.heroCap(castleCount = 2, effectiveInfrastructure = 39.99) shouldBe 13
ProvinceUtils.heroCap(castleCount = 0, effectiveInfrastructure = 9.99) shouldBe 0
ProvinceUtils.heroCap(castleCount = 0, effectiveInfrastructure = 10) shouldBe 1
}
"foodCap / goldCap" should "use the base resource cap when the province is not a focus province" in {
@@ -1902,7 +1902,7 @@ class SqliteHistoryTest extends AnyFlatSpec with Matchers with BeforeAndAfterEac
// ---------------- newProvinces "new entity" decomposition (v11 RED until implemented) ----------------
private def province(id: Int, name: String): ProvinceC =
ProvinceC(id = id, name = name, gold = 100, food = 50, heroCap = 1)
ProvinceC(id = id, name = name, gold = 100, food = 50, heroCap = 0)
"newProvinces v11 live write + reader" should "round-trip a new province via withNewResults" in {
// newProvinces holds full ProvinceT snapshots (no Changed* wrapper) the deepest entity. Stored as one proto blob.
@@ -26,6 +26,7 @@ scala_test(
deps = [
"//src/main/scala/net/eagle0/eagle/library:engine",
"//src/main/scala/net/eagle0/eagle/library/settings:hero_cap_divisor",
"//src/main/scala/net/eagle0/eagle/library/settings:hero_cap_per_castle",
"//src/main/scala/net/eagle0/eagle/library/settings:history_backend",
"//src/main/scala/net/eagle0/eagle/library/settings/loaders:battalion_type_loader",
"//src/main/scala/net/eagle0/eagle/service:in_memory_history",
@@ -2,7 +2,7 @@ package net.eagle0.eagle.service.new_game_creation
import java.nio.file.Files
import net.eagle0.eagle.library.settings.{HeroCapDivisor, HistoryBackend}
import net.eagle0.eagle.library.settings.{HeroCapDivisor, HeroCapPerCastle, HistoryBackend}
import net.eagle0.eagle.library.settings.loaders.BattalionTypeLoader
import net.eagle0.eagle.service.{GameHistoryFactory, InMemoryHistory}
import org.scalatest.flatspec.AnyFlatSpec
@@ -104,13 +104,14 @@ class FixedNewGameCreationTest extends AnyFlatSpec with Matchers {
}
}
"GameParametersUtils.provincesWithoutPlayers" should "derive hero caps from generic province infrastructure" in {
HeroCapDivisor.setDoubleValue(3.0)
"GameParametersUtils.provincesWithoutPlayers" should "derive hero caps from castles and generic infrastructure" in {
HeroCapDivisor.setDoubleValue(10.0)
HeroCapPerCastle.setIntValue(5)
val provinces =
GameParametersUtils.provincesWithoutPlayers(GameParametersUtils.defaultExpandedGameParameters.gameParameters)
all(provinces.map(_.infrastructure)) shouldBe 20.0
all(provinces.map(_.heroCap)) shouldBe 6
all(provinces.map(province => province.heroCap == 5 * province.castleCount + 2)) shouldBe true
}
"FixedNewGameCreation" should "create the warmup-sized default game" in {