Implemented Unit Supply by r3versi, not by me (#5234)

* Implemented Unit Supply

Unit Supply is a soft cap to number of units. If the number of units of a civilization exceeds the total supply, a production malus (capped at 70%) is applied.

* Warning icon & message for supply deficit

* Implemented Unit Supply - atlas merge

* Unit Supply: Malus->Penalty, lint, sumOf

* Unit Supply: Fresh atlas

Co-authored-by: r3versi <fluo392@gmail.com>
This commit is contained in:
SomeTroglodyte 2021-09-16 21:50:49 +02:00 committed by GitHub
parent 3d9c5bcc34
commit 340bedc7ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 710 additions and 571 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1016 KiB

After

Width:  |  Height:  |  Size: 1017 KiB

View File

@ -5,6 +5,8 @@
"extraHappinessPerLuxury": 1,
"researchCostModifier": 0.9,
"unitCostModifier": 0.5,
"unitSupplyBase": 10,
"unitSupplyPerCity": 3,
"buildingCostModifier": 0.5,
"policyCostModifier": 0.5,
"unhappinessModifier": 0.4,
@ -16,6 +18,7 @@
"aiWonderCostModifier": 1.6,
"aiBuildingMaintenanceModifier": 1,
"aiUnitMaintenanceModifier": 1,
"aiUnitSupplyModifier": 0,
"aiFreeTechs": [],
"aiMajorCivBonusStartingUnits": [],
"aiCityStateBonusStartingUnits": [],
@ -30,6 +33,8 @@
"extraHappinessPerLuxury": 1,
"researchCostModifier": 0.95,
"unitCostModifier": 0.67,
"unitSupplyBase": 7,
"unitSupplyPerCity": 3,
"buildingCostModifier": 0.67,
"policyCostModifier": 0.67,
"unhappinessModifier": 0.6,
@ -41,6 +46,7 @@
"aiWonderCostModifier": 1.3,
"aiBuildingMaintenanceModifier": 1,
"aiUnitMaintenanceModifier": 1,
"aiUnitSupplyModifier": 0,
"aiFreeTechs": [],
"aiMajorCivBonusStartingUnits": [],
"aiCityStateBonusStartingUnits": [],
@ -55,6 +61,8 @@
"extraHappinessPerLuxury": 0,
"researchCostModifier": 1,
"unitCostModifier": 0.85,
"unitSupplyBase": 7,
"unitSupplyPerCity": 2,
"buildingCostModifier": 0.85,
"policyCostModifier": 0.85,
"unhappinessModifier": 0.75,
@ -66,6 +74,7 @@
"aiWonderCostModifier": 1.1,
"aiBuildingMaintenanceModifier": 1,
"aiUnitMaintenanceModifier": 1,
"aiUnitSupplyModifier": 0.1,
"aiFreeTechs": [],
"aiMajorCivBonusStartingUnits": [],
"aiCityStateBonusStartingUnits": [],
@ -80,6 +89,8 @@
"extraHappinessPerLuxury": 0,
"researchCostModifier": 1,
"unitCostModifier": 1,
"unitSupplyBase": 5,
"unitSupplyPerCity": 2,
"buildingCostModifier": 1,
"policyCostModifier": 1,
"unhappinessModifier": 1,
@ -91,6 +102,7 @@
"aiWonderCostModifier": 1,
"aiBuildingMaintenanceModifier": 1,
"aiUnitMaintenanceModifier": 0.85,
"aiUnitSupplyModifier": 0.2,
"aiFreeTechs": [],
"aiMajorCivBonusStartingUnits": [],
"aiCityStateBonusStartingUnits": [],
@ -105,6 +117,8 @@
"extraHappinessPerLuxury": 0,
"researchCostModifier": 1,
"unitCostModifier": 1,
"unitSupplyBase": 5,
"unitSupplyPerCity": 2,
"buildingCostModifier": 1,
"policyCostModifier": 1,
"unhappinessModifier": 1,
@ -116,6 +130,7 @@
"aiWonderCostModifier": 1,
"aiBuildingMaintenanceModifier": 0.85,
"aiUnitMaintenanceModifier": 0.8,
"aiUnitSupplyModifier": 0.3,
"aiFreeTechs": ["Pottery"],
"aiMajorCivBonusStartingUnits": ["Era Starting Unit"],
"aiCityStateBonusStartingUnits": [],
@ -130,6 +145,8 @@
"extraHappinessPerLuxury": 0,
"researchCostModifier": 1,
"unitCostModifier": 1,
"unitSupplyBase": 5,
"unitSupplyPerCity": 2,
"buildingCostModifier": 1,
"policyCostModifier": 1,
"unhappinessModifier": 1,
@ -141,6 +158,7 @@
"aiWonderCostModifier": 1,
"aiBuildingMaintenanceModifier": 0.8,
"aiUnitMaintenanceModifier": 0.75,
"aiUnitSupplyModifier": 0.3,
"aiFreeTechs": ["Pottery","Animal Husbandry"],
"aiMajorCivBonusStartingUnits": ["Era Starting Unit", "Scout"],
"aiCityStateBonusStartingUnits": [],
@ -155,6 +173,8 @@
"extraHappinessPerLuxury": 0,
"researchCostModifier": 1,
"unitCostModifier": 1,
"unitSupplyBase": 5,
"unitSupplyPerCity": 2,
"buildingCostModifier": 1,
"policyCostModifier": 1,
"unhappinessModifier": 1,
@ -166,6 +186,7 @@
"aiWonderCostModifier": 1,
"aiBuildingMaintenanceModifier": 0.65,
"aiUnitMaintenanceModifier": 0.65,
"aiUnitSupplyModifier": 0.4,
"aiFreeTechs": ["Pottery","Animal Husbandry","Mining"],
"aiMajorCivBonusStartingUnits": ["Worker", "Scout", "Era Starting Unit", "Era Starting Unit"],
"aiCityStateBonusStartingUnits": [],
@ -180,6 +201,8 @@
"extraHappinessPerLuxury": 0,
"researchCostModifier": 1,
"unitCostModifier": 1,
"unitSupplyBase": 5,
"unitSupplyPerCity": 2,
"buildingCostModifier": 1,
"policyCostModifier": 1,
"unhappinessModifier": 1,
@ -191,6 +214,7 @@
"aiWonderCostModifier": 1,
"aiBuildingMaintenanceModifier": 0.5,
"aiUnitMaintenanceModifier": 0.5,
"aiUnitSupplyModifier": 0.5,
"aiFreeTechs": ["Pottery","Animal Husbandry","Mining","The Wheel"],
"aiMajorCivBonusStartingUnits": ["Settler", "Worker", "Scout", "Era Starting Unit", "Era Starting Unit", "Era Starting Unit"],
"aiCityStateBonusStartingUnits": [],

View File

@ -862,6 +862,13 @@ Transportation upkeep =
Unit upkeep =
Trades =
Units =
Unit Supply =
Base Supply =
Total Supply =
In Use =
Supply Deficit =
Production Penalty =
Increase your supply or reduce the amount of units to remove the production penalty =
Name =
Closest city =
Action =

View File

@ -320,6 +320,14 @@ class CityStats(val cityInfo: CityInfo) {
return stats
}
private fun getStatPercentBonusesFromUnitSupply(): Stats {
val stats = Stats()
val supplyDeficit = cityInfo.civInfo.stats().getUnitSupplyDeficit()
if (supplyDeficit > 0)
stats.production = cityInfo.civInfo.stats().getUnitSupplyProductionPenalty()
return stats
}
private fun constructionMatchesFilter(construction: IConstruction, filter: String): Boolean {
if (construction is Building) return construction.matchesFilter(filter)
if (construction is BaseUnit) return construction.matchesFilter(filter)
@ -465,6 +473,7 @@ class CityStats(val cityInfo: CityInfo) {
newStatPercentBonusList["National ability"] = getStatPercentBonusesFromNationUnique(currentConstruction)
newStatPercentBonusList["Puppet City"] = getStatPercentBonusesFromPuppetCity()
newStatPercentBonusList["Religion"] = getStatPercentBonusesFromUniques(currentConstruction, cityInfo.religion.getUniques())
newStatPercentBonusList["Unit Supply"] = getStatPercentBonusesFromUnitSupply()
if (UncivGame.Current.superchargedForDebug) {
val stats = Stats()

View File

@ -87,6 +87,24 @@ class CivInfoStats(val civInfo: CivilizationInfo) {
return transportationUpkeep
}
fun getUnitSupply(): Int {
/* TotalSupply = BaseSupply + NumCities*modifier + Population*modifier
* In civ5, it seems population modifier is always 0.5, so i hardcoded it down below */
var supply = getBaseUnitSupply() + getUnitSupplyFromCities() + getUnitSupplyFromPop()
if (civInfo.isMajorCiv() && civInfo.playerType == PlayerType.AI)
supply = (supply*(1f + civInfo.getDifficulty().aiUnitSupplyModifier)).toInt()
return supply
}
fun getBaseUnitSupply(): Int = civInfo.getDifficulty().unitSupplyBase
fun getUnitSupplyFromCities(): Int = civInfo.cities.size * civInfo.getDifficulty().unitSupplyPerCity
fun getUnitSupplyFromPop(): Int = civInfo.cities.sumOf { it.population.population } / 2
fun getUnitSupplyDeficit(): Int = max(0,civInfo.getCivUnitsSize() - getUnitSupply())
/** Per each supply missing, a player gets -10% production. Capped at -70%. */
fun getUnitSupplyProductionPenalty(): Float = -min(getUnitSupplyDeficit() * 10f, 70f)
fun getStatMapForNextTurn(): StatMap {
val statMap = StatMap()
for (city in civInfo.cities) {

View File

@ -329,6 +329,7 @@ class CivilizationInfo {
}
//region Units
fun getCivUnitsSize(): Int = units.size
fun getCivUnits(): Sequence<MapUnit> = units.asSequence()
fun getCivGreatPeople(): Sequence<MapUnit> = getCivUnits().filter { mapUnit -> mapUnit.isGreatPerson() }

View File

@ -11,6 +11,8 @@ class Difficulty: INamed, ICivilopediaText {
var extraHappinessPerLuxury: Float = 0f
var researchCostModifier:Float = 1f
var unitCostModifier:Float = 1f
var unitSupplyBase: Int = 0
var unitSupplyPerCity: Int = 0
var buildingCostModifier:Float = 1f
var policyCostModifier:Float = 1f
var unhappinessModifier:Float = 1f
@ -23,6 +25,7 @@ class Difficulty: INamed, ICivilopediaText {
var aiWonderCostModifier:Float = 1f
var aiBuildingMaintenanceModifier:Float = 1f
var aiUnitMaintenanceModifier = 1f
var aiUnitSupplyModifier: Float = 0f
var aiFreeTechs = ArrayList<String>()
var aiMajorCivBonusStartingUnits = ArrayList<String>()
var aiCityStateBonusStartingUnits = ArrayList<String>()

View File

@ -2,6 +2,7 @@ package com.unciv.ui.overviewscreen
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.ui.Button
import com.badlogic.gdx.scenes.scene2d.ui.Label
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.models.translations.tr
@ -14,59 +15,116 @@ import kotlin.math.abs
* Supplies the Unit sub-table for the Empire Overview
*/
class UnitOverviewTable(
viewingPlayer: CivilizationInfo,
overviewScreen: EmpireOverviewScreen
private val viewingPlayer: CivilizationInfo,
private val overviewScreen: EmpireOverviewScreen
) : Table(CameraStageBaseScreen.skin) {
init {
val game = overviewScreen.game
defaults().pad(5f)
add("Name".tr())
add("Action".tr())
add(Fonts.strength.toString())
add(Fonts.rangedStrength.toString())
add(Fonts.movement.toString())
add("Closest city".tr())
add("Promotions".tr())
add("Health".tr())
row()
addSeparator()
for (unit in viewingPlayer.getCivUnits().sortedWith(compareBy({ it.displayName() }, { !it.due },
{ it.currentMovement < 0.1f }, { abs(it.currentTile.position.x) + abs(it.currentTile.position.y) }))) {
val baseUnit = unit.baseUnit()
val button = Button(skin)
button.add(UnitGroup(unit,20f)).padRight(5f)
button.add(unit.displayName().toLabel())
button.onClick {
game.setWorldScreen()
game.worldScreen.mapHolder.setCenterPosition(unit.currentTile.position)
}
add(button).left()
if (unit.action == null) add()
else add(unit.getActionLabel().tr())
if (baseUnit.strength > 0) add(baseUnit.strength.toString()) else add()
if (baseUnit.rangedStrength > 0) add(baseUnit.rangedStrength.toString()) else add()
add(DecimalFormat("0.#").format(unit.currentMovement) + "/" + unit.getMaxMovement())
val closestCity = unit.getTile().getTilesInDistance(3).firstOrNull { it.isCityCenter() }
if (closestCity != null) add(closestCity.getCity()!!.name.tr()) else add()
val promotionsTable = Table()
val promotionsForUnit = unit.civInfo.gameInfo.ruleSet.unitPromotions.values.filter { unit.promotions.promotions.contains(it.name) } // force same sorting as on picker (.sorted() would be simpler code, but...)
for (promotion in promotionsForUnit)
promotionsTable.add(ImageGetter.getPromotionIcon(promotion.name))
if (unit.promotions.canBePromoted()) promotionsTable.add(ImageGetter.getImage("OtherIcons/Star").apply { color = Color.GOLDENROD }).size(24f).padLeft(8f)
if (unit.canUpgrade()) promotionsTable.add(ImageGetter.getUnitIcon(unit.getUnitToUpgradeTo().name, Color.GREEN)).size(28f).padLeft(8f)
promotionsTable.onClick {
if (unit.promotions.canBePromoted() || unit.promotions.promotions.isNotEmpty()) {
game.setScreen(PromotionPickerScreen(unit))
}
}
add(promotionsTable)
if (unit.health < 100) add(unit.health.toString()) else add()
row()
}
add(getUnitSupplyTable()).top().padRight(25f)
add(getUnitListTable())
pack()
}
private fun getUnitSupplyTable(): Table {
val unitSupplyTable = Table(CameraStageBaseScreen.skin)
unitSupplyTable.defaults().pad(5f)
unitSupplyTable.apply {
add("Unit Supply".tr()).colspan(2).center().row()
addSeparator()
add("Base Supply".tr()).left()
add(viewingPlayer.stats().getBaseUnitSupply().toLabel()).right().row()
add("Cities".tr()).left()
add(viewingPlayer.stats().getUnitSupplyFromCities().toLabel()).right().row()
add("Population".tr()).left()
add(viewingPlayer.stats().getUnitSupplyFromPop().toLabel()).right().row()
addSeparator()
add("Total Supply".tr()).left()
add(viewingPlayer.stats().getUnitSupply().toLabel()).right().row()
add("In Use".tr()).left()
add(viewingPlayer.getCivUnitsSize().toLabel()).right().row()
addSeparator()
val deficit = viewingPlayer.stats().getUnitSupplyDeficit()
add("Supply Deficit".tr()).left()
add(deficit.toLabel()).right().row()
add("Production Penalty".tr()).left()
add((viewingPlayer.stats().getUnitSupplyProductionPenalty()).toInt().toString()+"%").right().row()
if (deficit > 0) {
val penaltyLabel = "Increase your supply or reduce the amount of units to remove the production penalty"
.toLabel(Color.FIREBRICK)
penaltyLabel.wrap = true
add(penaltyLabel).colspan(2).left()
.width(overviewScreen.stage.width * 0.2f).row()
}
pack()
}
return unitSupplyTable
}
private fun getUnitListTable(): Table {
val game = overviewScreen.game
val unitListTable = Table(CameraStageBaseScreen.skin)
unitListTable.defaults().pad(5f)
unitListTable.apply {
add("Name".tr())
add("Action".tr())
add(Fonts.strength.toString())
add(Fonts.rangedStrength.toString())
add(Fonts.movement.toString())
add("Closest city".tr())
add("Promotions".tr())
add("Health".tr())
row()
addSeparator()
for (unit in viewingPlayer.getCivUnits().sortedWith(
compareBy({ it.displayName() },
{ !it.due },
{ it.currentMovement < 0.1f },
{ abs(it.currentTile.position.x) + abs(it.currentTile.position.y) })
)) {
val baseUnit = unit.baseUnit()
val button = Button(skin)
button.add(UnitGroup(unit, 20f)).padRight(5f)
button.add(unit.displayName().toLabel())
button.onClick {
game.setWorldScreen()
game.worldScreen.mapHolder.setCenterPosition(unit.currentTile.position)
}
add(button).left()
if (unit.action == null) add()
else add(unit.getActionLabel().tr())
if (baseUnit.strength > 0) add(baseUnit.strength.toString()) else add()
if (baseUnit.rangedStrength > 0) add(baseUnit.rangedStrength.toString()) else add()
add(DecimalFormat("0.#").format(unit.currentMovement) + "/" + unit.getMaxMovement())
val closestCity =
unit.getTile().getTilesInDistance(3).firstOrNull { it.isCityCenter() }
if (closestCity != null) add(closestCity.getCity()!!.name.tr()) else add()
val promotionsTable = Table()
val promotionsForUnit = unit.civInfo.gameInfo.ruleSet.unitPromotions.values.filter {
unit.promotions.promotions.contains(it.name)
} // force same sorting as on picker (.sorted() would be simpler code, but...)
for (promotion in promotionsForUnit)
promotionsTable.add(ImageGetter.getPromotionIcon(promotion.name))
if (unit.promotions.canBePromoted()) promotionsTable.add(
ImageGetter.getImage("OtherIcons/Star").apply { color = Color.GOLDENROD })
.size(24f).padLeft(8f)
if (unit.canUpgrade()) promotionsTable.add(
ImageGetter.getUnitIcon(
unit.getUnitToUpgradeTo().name,
Color.GREEN
)
).size(28f).padLeft(8f)
promotionsTable.onClick {
if (unit.promotions.canBePromoted() || unit.promotions.promotions.isNotEmpty()) {
game.setScreen(PromotionPickerScreen(unit))
}
}
add(promotionsTable)
if (unit.health < 100) add(unit.health.toString()) else add()
row()
}
}
return unitListTable
}
}

View File

@ -158,15 +158,27 @@ class WorldScreenTopBar(val worldScreen: WorldScreen) : Table() {
return menuButton
}
private fun getOverviewButton(): Button {
private fun getOverviewButton(): Table {
val rightTable = Table(CameraStageBaseScreen.skin).apply{ defaults().pad(10f) }
val unitSupplyImage = ImageGetter.getImage("OtherIcons/ExclamationMark")
.apply { color = Color.FIREBRICK }
.onClick { worldScreen.game.setScreen(EmpireOverviewScreen(worldScreen.selectedCiv, "Units")) }
val overviewButton = Button(CameraStageBaseScreen.skin)
overviewButton.add("Overview".toLabel()).pad(10f)
overviewButton.addTooltip('e')
overviewButton.pack()
overviewButton.onClick { worldScreen.game.setScreen(EmpireOverviewScreen(worldScreen.selectedCiv)) }
overviewButton.centerY(this)
overviewButton.x = worldScreen.stage.width - overviewButton.width - 10
return overviewButton
if (worldScreen.selectedCiv.stats().getUnitSupplyDeficit() > 0)
rightTable.add(unitSupplyImage).size(50f)
rightTable.add(overviewButton)
rightTable.pack()
rightTable.centerY(this)
rightTable.x = worldScreen.stage.width - rightTable.width - 10
return rightTable
}
private fun getSelectedCivilizationTable(): Table {