Scale hero cap by infrastructure (#8787)

This commit is contained in:
2026-07-25 18:38:11 -07:00
committed by GitHub
parent 4898a1fe80
commit 865d974a51
11 changed files with 55 additions and 17 deletions
@@ -235,7 +235,8 @@ minimumTrustToOfferTruce 0 Int
minimumTrustToInvite 20 Int
minimumTrustToOfferAlliance 30 Int
requiredPowerMultiplierForDismiss 1.5 Double
heroCapDivisor 10.0 Double
heroCapBaseMultiplier 0.8 Double
heroCapDivisor 100.0 Double
heroCapPerCastle 5 Int
overHeroCapLoyaltyDelta -1 Double
minimumAgilityForArchery 75 Double
1 actionVigorCost 15 Int
235 minimumTrustToInvite 20 Int
236 minimumTrustToOfferAlliance 30 Int
237 requiredPowerMultiplierForDismiss 1.5 Double
238 heroCapDivisor heroCapBaseMultiplier 10.0 0.8 Double
239 heroCapDivisor 100.0 Double
240 heroCapPerCastle 5 Int
241 overHeroCapLoyaltyDelta -1 Double
242 minimumAgilityForArchery 75 Double
@@ -3096,11 +3096,24 @@ scala_setting_library(
],
)
scala_setting_library(
name = "hero_cap_base_multiplier",
setting_name = "HeroCapBaseMultiplier",
setting_type = "Double",
setting_value = "0.8",
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_divisor",
setting_name = "HeroCapDivisor",
setting_type = "Double",
setting_value = "10.0",
setting_value = "100.0",
visibility = [
"//src/main/scala/net/eagle0/eagle:__subpackages__",
"//src/main/scala/net/eagle0/util:__subpackages__",
@@ -124,6 +124,7 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library/settings:gold_cost_for_divine",
"//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_base_multiplier",
"//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",
@@ -17,6 +17,7 @@ scala_library(
"//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:gold_increase_per_economy",
"//src/main/scala/net/eagle0/eagle/library/settings:hero_cap_base_multiplier",
"//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",
@@ -6,6 +6,7 @@ import net.eagle0.eagle.library.settings.{
FocusProvinceStorageMultiplier,
FoodIncreasePerAgriculture,
GoldIncreasePerEconomy,
HeroCapBaseMultiplier,
HeroCapDivisor,
HeroCapPerCastle,
MinSupportForTaxes,
@@ -73,9 +74,13 @@ object ProvinceUtils {
heroCap(province.castleCount, effectiveInfrastructure(province))
def heroCap(castleCount: Int, effectiveInfrastructure: Double): Int = {
val divisor = HeroCapDivisor.doubleValue
val baseMultiplier = HeroCapBaseMultiplier.doubleValue
val divisor = HeroCapDivisor.doubleValue
if !(baseMultiplier >= 0.0) then
throw new EagleInternalException(s"HeroCapBaseMultiplier must be non-negative, but was $baseMultiplier")
if !(divisor > 0.0) then throw new EagleInternalException(s"HeroCapDivisor must be positive, but was $divisor")
castleCount * HeroCapPerCastle.intValue + (effectiveInfrastructure.max(0.0) / divisor).floor.toInt
val baseCap = castleCount * HeroCapPerCastle.intValue
(baseCap * (baseMultiplier + effectiveInfrastructure.max(0.0) / divisor)).ceil.toInt
}
def goldCap(province: ProvinceT, isFocusProvince: Boolean): Int =
@@ -1,6 +1,12 @@
package net.eagle0.eagle.library.actions.applier
import net.eagle0.eagle.library.settings.{ExtraXpForStatBumpOver100, HeroCapDivisor, HeroCapPerCastle, XpForStatBump}
import net.eagle0.eagle.library.settings.{
ExtraXpForStatBumpOver100,
HeroCapBaseMultiplier,
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 +49,8 @@ class ActionResultApplierImplTest extends AnyFlatSpec with Matchers with BeforeA
override def beforeEach(): Unit = {
XpForStatBump.setIntValue(100)
ExtraXpForStatBumpOver100.setIntValue(50)
HeroCapDivisor.setDoubleValue(10.0)
HeroCapBaseMultiplier.setDoubleValue(0.8)
HeroCapDivisor.setDoubleValue(100.0)
HeroCapPerCastle.setIntValue(5)
}
@@ -146,7 +153,7 @@ class ActionResultApplierImplTest extends AnyFlatSpec with Matchers with BeforeA
val result = applier.applyActionResult(startingState, actionResult)
result.resultingState.provinces(provinceId).heroCap shouldBe 12
result.resultingState.provinces(provinceId).heroCap shouldBe 11
}
it should "not advance command tokens for automatic results with an acting faction" in {
@@ -7,6 +7,7 @@ scala_test(
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//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_base_multiplier",
"//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",
@@ -121,6 +121,7 @@ scala_test(
"//src/main/scala/net/eagle0/eagle/library/settings:base_resource_limit",
"//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_base_multiplier",
"//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",
@@ -6,6 +6,7 @@ import net.eagle0.eagle.library.settings.{
BaseResourceLimit,
FocusProvinceStorageMultiplier,
FoodIncreasePerAgriculture,
HeroCapBaseMultiplier,
HeroCapDivisor,
HeroCapPerCastle,
ImprovementPerStat,
@@ -84,7 +85,8 @@ class ProvinceUtilsTest extends AnyFlatSpec with Matchers with BeforeAndAfterEac
BaseResourceLimit.setIntValue(5000)
PerDevelopmentResourceLimit.setIntValue(50)
FocusProvinceStorageMultiplier.setDoubleValue(1.5)
HeroCapDivisor.setDoubleValue(10.0)
HeroCapBaseMultiplier.setDoubleValue(0.8)
HeroCapDivisor.setDoubleValue(100.0)
HeroCapPerCastle.setIntValue(5)
}
@@ -378,7 +380,7 @@ class ProvinceUtilsTest extends AnyFlatSpec with Matchers with BeforeAndAfterEac
ProvinceUtils.containsFactionLeader(province, Vector(actingFaction)) shouldBe false
}
"heroCap" should "add castle and effective infrastructure capacity" in {
"heroCap" should "multiply castle capacity by effective infrastructure" in {
val province = ProvinceC(
id = 7,
castleCount = 2,
@@ -386,13 +388,17 @@ class ProvinceUtilsTest extends AnyFlatSpec with Matchers with BeforeAndAfterEac
infrastructureDevastation = 12
)
ProvinceUtils.heroCap(province) shouldBe 13
ProvinceUtils.heroCap(province) shouldBe 12
}
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
it should "use the original castle capacity at 20 infrastructure" in {
ProvinceUtils.heroCap(castleCount = 3, effectiveInfrastructure = 20) shouldBe 15
}
it should "round the multiplied capacity up" in {
ProvinceUtils.heroCap(castleCount = 2, effectiveInfrastructure = 33) shouldBe 12
ProvinceUtils.heroCap(castleCount = 3, effectiveInfrastructure = 100) shouldBe 27
ProvinceUtils.heroCap(castleCount = 0, effectiveInfrastructure = 100) shouldBe 0
}
"foodCap / goldCap" should "use the base resource cap when the province is not a focus province" in {
@@ -25,6 +25,7 @@ scala_test(
],
deps = [
"//src/main/scala/net/eagle0/eagle/library:engine",
"//src/main/scala/net/eagle0/eagle/library/settings:hero_cap_base_multiplier",
"//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",
@@ -2,7 +2,7 @@ package net.eagle0.eagle.service.new_game_creation
import java.nio.file.Files
import net.eagle0.eagle.library.settings.{HeroCapDivisor, HeroCapPerCastle, HistoryBackend}
import net.eagle0.eagle.library.settings.{HeroCapBaseMultiplier, HeroCapDivisor, HeroCapPerCastle, HistoryBackend}
import net.eagle0.eagle.library.settings.loaders.BattalionTypeLoader
import net.eagle0.eagle.service.{GameHistoryFactory, InMemoryHistory}
import org.scalatest.flatspec.AnyFlatSpec
@@ -105,13 +105,14 @@ class FixedNewGameCreationTest extends AnyFlatSpec with Matchers {
}
"GameParametersUtils.provincesWithoutPlayers" should "derive hero caps from castles and generic infrastructure" in {
HeroCapDivisor.setDoubleValue(10.0)
HeroCapBaseMultiplier.setDoubleValue(0.8)
HeroCapDivisor.setDoubleValue(100.0)
HeroCapPerCastle.setIntValue(5)
val provinces =
GameParametersUtils.provincesWithoutPlayers(GameParametersUtils.defaultExpandedGameParameters.gameParameters)
all(provinces.map(_.infrastructure)) shouldBe 20.0
all(provinces.map(province => province.heroCap == 5 * province.castleCount + 2)) shouldBe true
all(provinces.map(province => province.heroCap == 5 * province.castleCount)) shouldBe true
}
"FixedNewGameCreation" should "create the warmup-sized default game" in {