Assign Population Improvements (#6650)

City management UI to allow focusing automatic worker placement

Improvements to worker / specialist assignment routines
This commit is contained in:
itanasi 2022-05-19 15:12:23 -07:00 committed by GitHub
parent a272e8e7ba
commit a2bc1a1a29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 665 additions and 358 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1075,6 +1075,90 @@ TileSets/FantasyHex/Arrows/UnitHasAttacked
orig: 100, 60
offset: 0, 0
index: -1
TileSets/Default/Arrows/Generic
rotate: false
xy: 4, 6
size: 100, 60
orig: 100, 60
offset: 0, 0
index: -1
TileSets/FantasyHex/Arrows/Generic
rotate: false
xy: 4, 6
size: 100, 60
orig: 100, 60
offset: 0, 0
index: -1
TileSets/Default/Arrows/UnitAttacked
rotate: false
xy: 190, 1164
size: 100, 60
orig: 100, 60
offset: 0, 0
index: -1
TileSets/FantasyHex/Arrows/UnitAttacked
rotate: false
xy: 190, 1164
size: 100, 60
orig: 100, 60
offset: 0, 0
index: -1
TileSets/Default/Arrows/UnitMoved
rotate: false
xy: 298, 1150
size: 100, 60
orig: 100, 60
offset: 0, 0
index: -1
TileSets/FantasyHex/Arrows/UnitMoved
rotate: false
xy: 298, 1150
size: 100, 60
orig: 100, 60
offset: 0, 0
index: -1
TileSets/Default/Arrows/UnitMoving
rotate: false
xy: 112, 6
size: 100, 60
orig: 100, 60
offset: 0, 0
index: -1
TileSets/FantasyHex/Arrows/UnitMoving
rotate: false
xy: 112, 6
size: 100, 60
orig: 100, 60
offset: 0, 0
index: -1
TileSets/Default/Arrows/UnitTeleported
rotate: false
xy: 622, 1228
size: 100, 60
orig: 100, 60
offset: 0, 0
index: -1
TileSets/FantasyHex/Arrows/UnitTeleported
rotate: false
xy: 622, 1228
size: 100, 60
orig: 100, 60
offset: 0, 0
index: -1
TileSets/Default/Arrows/UnitWithdrew
rotate: false
xy: 730, 1228
size: 100, 60
orig: 100, 60
offset: 0, 0
index: -1
TileSets/FantasyHex/Arrows/UnitWithdrew
rotate: false
xy: 730, 1228
size: 100, 60
orig: 100, 60
offset: 0, 0
index: -1
TileSets/Default/AtollOverlay
rotate: false
xy: 4, 830
@ -1082,6 +1166,118 @@ TileSets/Default/AtollOverlay
orig: 100, 100
offset: 0, 0
index: -1
TileSets/Default/Borders/ConcaveConvexInner
rotate: false
xy: 406, 1411
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/FantasyHex/Borders/ConcaveConvexInner
rotate: false
xy: 406, 1411
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/Default/Borders/ConcaveConvexOuter
rotate: false
xy: 406, 1165
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/FantasyHex/Borders/ConcaveConvexOuter
rotate: false
xy: 406, 1165
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/Default/Borders/ConcaveInner
rotate: false
xy: 622, 1205
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/FantasyHex/Borders/ConcaveInner
rotate: false
xy: 622, 1205
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/Default/Borders/ConcaveOuter
rotate: false
xy: 1378, 1273
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/FantasyHex/Borders/ConcaveOuter
rotate: false
xy: 1378, 1273
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/Default/Borders/ConvexConcaveInner
rotate: false
xy: 495, 1165
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/FantasyHex/Borders/ConvexConcaveInner
rotate: false
xy: 495, 1165
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/Default/Borders/ConvexConcaveOuter
rotate: false
xy: 711, 1205
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/FantasyHex/Borders/ConvexConcaveOuter
rotate: false
xy: 711, 1205
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/Default/Borders/ConvexInner
rotate: false
xy: 1378, 1250
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/FantasyHex/Borders/ConvexInner
rotate: false
xy: 1378, 1250
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/Default/Borders/ConvexOuter
rotate: false
xy: 1467, 1273
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/FantasyHex/Borders/ConvexOuter
rotate: false
xy: 1467, 1273
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/Default/CityOverlay
rotate: false
xy: 1470, 1944
@ -1089,6 +1285,34 @@ TileSets/Default/CityOverlay
orig: 100, 100
offset: 0, 0
index: -1
TileSets/Default/Crosshair
rotate: false
xy: 482, 1944
size: 116, 100
orig: 116, 100
offset: 0, 0
index: -1
TileSets/FantasyHex/Crosshair
rotate: false
xy: 482, 1944
size: 116, 100
orig: 116, 100
offset: 0, 0
index: -1
TileSets/Default/CrosshatchHexagon
rotate: false
xy: 4, 1340
size: 273, 236
orig: 273, 236
offset: 0, 0
index: -1
TileSets/FantasyHex/CrosshatchHexagon
rotate: false
xy: 4, 1340
size: 273, 236
orig: 273, 236
offset: 0, 0
index: -1
TileSets/Default/FalloutOverlay
rotate: false
xy: 590, 1836
@ -1110,6 +1334,20 @@ TileSets/Default/ForestOverlay
orig: 100, 100
offset: 0, 0
index: -1
TileSets/Default/Highlight
rotate: false
xy: 4, 1832
size: 284, 212
orig: 284, 212
offset: 0, 0
index: -1
TileSets/FantasyHex/Highlight
rotate: false
xy: 4, 1832
size: 284, 212
orig: 284, 212
offset: 0, 0
index: -1
TileSets/Default/HillOverlay
rotate: false
xy: 590, 1728
@ -1175,263 +1413,25 @@ TileSets/Default/Road
index: -1
TileSets/Default/Tiles/River-Bottom
rotate: false
xy: 1574, 735
xy: 1534, 694
size: 32, 28
orig: 32, 28
offset: 0, 0
index: -1
TileSets/Default/Tiles/River-BottomLeft
rotate: false
xy: 1574, 699
xy: 1534, 658
size: 32, 28
orig: 32, 28
offset: 0, 0
index: -1
TileSets/Default/Tiles/River-BottomRight
rotate: false
xy: 1574, 663
xy: 1534, 622
size: 32, 28
orig: 32, 28
offset: 0, 0
index: -1
TileSets/FantasyHex/Arrows/Generic
rotate: false
xy: 4, 6
size: 100, 60
orig: 100, 60
offset: 0, 0
index: -1
TileSets/Default/Arrows/Generic
rotate: false
xy: 4, 6
size: 100, 60
orig: 100, 60
offset: 0, 0
index: -1
TileSets/FantasyHex/Arrows/UnitAttacked
rotate: false
xy: 190, 1164
size: 100, 60
orig: 100, 60
offset: 0, 0
index: -1
TileSets/Default/Arrows/UnitAttacked
rotate: false
xy: 190, 1164
size: 100, 60
orig: 100, 60
offset: 0, 0
index: -1
TileSets/FantasyHex/Arrows/UnitMoved
rotate: false
xy: 298, 1150
size: 100, 60
orig: 100, 60
offset: 0, 0
index: -1
TileSets/Default/Arrows/UnitMoved
rotate: false
xy: 298, 1150
size: 100, 60
orig: 100, 60
offset: 0, 0
index: -1
TileSets/FantasyHex/Arrows/UnitMoving
rotate: false
xy: 112, 6
size: 100, 60
orig: 100, 60
offset: 0, 0
index: -1
TileSets/Default/Arrows/UnitMoving
rotate: false
xy: 112, 6
size: 100, 60
orig: 100, 60
offset: 0, 0
index: -1
TileSets/FantasyHex/Arrows/UnitTeleported
rotate: false
xy: 622, 1228
size: 100, 60
orig: 100, 60
offset: 0, 0
index: -1
TileSets/Default/Arrows/UnitTeleported
rotate: false
xy: 622, 1228
size: 100, 60
orig: 100, 60
offset: 0, 0
index: -1
TileSets/FantasyHex/Arrows/UnitWithdrew
rotate: false
xy: 730, 1228
size: 100, 60
orig: 100, 60
offset: 0, 0
index: -1
TileSets/Default/Arrows/UnitWithdrew
rotate: false
xy: 730, 1228
size: 100, 60
orig: 100, 60
offset: 0, 0
index: -1
TileSets/FantasyHex/Borders/ConcaveConvexInner
rotate: false
xy: 406, 1411
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/Default/Borders/ConcaveConvexInner
rotate: false
xy: 406, 1411
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/FantasyHex/Borders/ConcaveConvexOuter
rotate: false
xy: 406, 1165
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/Default/Borders/ConcaveConvexOuter
rotate: false
xy: 406, 1165
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/FantasyHex/Borders/ConcaveInner
rotate: false
xy: 622, 1205
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/Default/Borders/ConcaveInner
rotate: false
xy: 622, 1205
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/FantasyHex/Borders/ConcaveOuter
rotate: false
xy: 1378, 1273
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/Default/Borders/ConcaveOuter
rotate: false
xy: 1378, 1273
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/FantasyHex/Borders/ConvexConcaveInner
rotate: false
xy: 495, 1165
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/Default/Borders/ConvexConcaveInner
rotate: false
xy: 495, 1165
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/FantasyHex/Borders/ConvexConcaveOuter
rotate: false
xy: 711, 1205
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/Default/Borders/ConvexConcaveOuter
rotate: false
xy: 711, 1205
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/FantasyHex/Borders/ConvexInner
rotate: false
xy: 1378, 1250
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/Default/Borders/ConvexInner
rotate: false
xy: 1378, 1250
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/FantasyHex/Borders/ConvexOuter
rotate: false
xy: 1467, 1273
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/Default/Borders/ConvexOuter
rotate: false
xy: 1467, 1273
size: 81, 15
orig: 81, 15
offset: 0, 0
index: -1
TileSets/FantasyHex/Crosshair
rotate: false
xy: 482, 1944
size: 116, 100
orig: 116, 100
offset: 0, 0
index: -1
TileSets/Default/Crosshair
rotate: false
xy: 482, 1944
size: 116, 100
orig: 116, 100
offset: 0, 0
index: -1
TileSets/FantasyHex/CrosshatchHexagon
rotate: false
xy: 4, 1340
size: 273, 236
orig: 273, 236
offset: 0, 0
index: -1
TileSets/Default/CrosshatchHexagon
rotate: false
xy: 4, 1340
size: 273, 236
orig: 273, 236
offset: 0, 0
index: -1
TileSets/FantasyHex/Highlight
rotate: false
xy: 4, 1832
size: 284, 212
orig: 284, 212
offset: 0, 0
index: -1
TileSets/Default/Highlight
rotate: false
xy: 4, 1832
size: 284, 212
orig: 284, 212
offset: 0, 0
index: -1
TileSets/FantasyHex/Railroad
rotate: false
xy: 505, 1728
@ -2316,21 +2316,21 @@ TileSets/FantasyHex/Tiles/Quarry+Stone
index: -1
TileSets/FantasyHex/Tiles/River-Bottom
rotate: false
xy: 1534, 694
xy: 1574, 735
size: 32, 28
orig: 32, 28
offset: 0, 0
index: -1
TileSets/FantasyHex/Tiles/River-BottomLeft
rotate: false
xy: 1534, 658
xy: 1574, 699
size: 32, 28
orig: 32, 28
offset: 0, 0
index: -1
TileSets/FantasyHex/Tiles/River-BottomRight
rotate: false
xy: 1534, 622
xy: 1574, 663
size: 32, 28
orig: 32, 28
offset: 0, 0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -929,6 +929,8 @@ Nothing =
Annex city =
Specialist Buildings =
Specialist Allocation =
Manual Specialists =
Auto Specialists =
Specialists =
[specialist] slots =
Food eaten =
@ -956,6 +958,16 @@ Worked by [cityName] =
Lock =
Unlock =
Move to city =
Reset Citizens =
Citizen Management =
Avoid Growth =
Default Focus =
Food Focus =
Production Focus =
Gold Focus =
Science Focus =
Culture Focus =
Happiness Focus =
Please enter a new name for your city =
Please select a tile for this building's [improvement] =

View File

@ -1,5 +1,6 @@
package com.unciv.logic.automation
import com.unciv.logic.city.CityFocus
import com.unciv.logic.city.CityInfo
import com.unciv.logic.city.INonPerpetualConstruction
import com.unciv.logic.civilization.CivilizationInfo
@ -8,7 +9,6 @@ import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.TileInfo
import com.unciv.logic.map.TileMap
import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.MilestoneType
import com.unciv.models.ruleset.Victory
import com.unciv.models.ruleset.Victory.Focus
import com.unciv.models.ruleset.tile.ResourceType
@ -16,46 +16,94 @@ import com.unciv.models.ruleset.tile.TileImprovement
import com.unciv.models.ruleset.unique.LocalUniqueCache
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.stats.Stat
import com.unciv.models.stats.Stats
import com.unciv.ui.victoryscreen.RankingType
object Automation {
fun rankTileForCityWork(tile: TileInfo, city: CityInfo, foodWeight: Float = 1f): Float {
fun rankTileForCityWork(tile: TileInfo, city: CityInfo, cityStats: Stats): Float {
val stats = tile.getTileStats(city, city.civInfo)
return rankStatsForCityWork(stats, city, foodWeight)
return rankStatsForCityWork(stats, city, cityStats)
}
private fun rankStatsForCityWork(stats: Stats, city: CityInfo, foodWeight: Float = 1f): Float {
var rank = 0f
if (city.population.population < 5) {
// "small city" - we care more about food and less about global problems like gold science and culture
rank += stats.food * 1.2f * foodWeight
rank += stats.production
rank += stats.science / 2
rank += stats.culture / 2
rank += stats.gold / 5 // it's barely worth anything at this point
} else {
if (stats.food <= 2 || city.civInfo.getHappiness() > 5) rank += stats.food * 1.2f * foodWeight // food get more value to keep city growing
else rank += (2.4f + (stats.food - 2) / 2) * foodWeight // 1.2 point for each food up to 2, from there on half a point
if (city.civInfo.gold < 0 && city.civInfo.statsForNextTurn.gold <= 0)
rank += stats.gold // we have a global problem
else rank += stats.gold / 3 // 3 gold is worse than 2 production
rank += stats.production
rank += stats.science
if (city.tiles.size < 12 || city.civInfo.wantsToFocusOn(Victory.Focus.Culture)) {
rank += stats.culture
} else rank += stats.culture / 2
fun rankSpecialist(specialist: String, cityInfo: CityInfo, cityStats: Stats): Float {
val stats = cityInfo.cityStats.getStatsOfSpecialist(specialist)
var rank = rankStatsForCityWork(stats, cityInfo, cityStats, true)
// derive GPP score
var gpp = 0f
if (cityInfo.getRuleset().specialists.containsKey(specialist)) { // To solve problems in total remake mods
val specialistInfo = cityInfo.getRuleset().specialists[specialist]!!
gpp = specialistInfo.greatPersonPoints.sumValues().toFloat()
}
gpp = gpp * (100 + cityInfo.currentGPPBonus) / 100
rank += gpp * 3 // GPP weight
return rank
}
internal fun rankSpecialist(stats: Stats, cityInfo: CityInfo): Float {
var rank = rankStatsForCityWork(stats, cityInfo)
rank += 0.3f //GPP bonus
return rank
private fun rankStatsForCityWork(stats: Stats, city: CityInfo, cityStats: Stats, specialist: Boolean = false): Float {
val cityAIFocus = city.cityAIFocus
val yieldStats = stats.clone()
if (specialist) {
// If you have the Food Bonus, count as 1 extra food production (base is 2food)
for (unique in city.getMatchingUniques(UniqueType.FoodConsumptionBySpecialists))
if (city.matchesFilter(unique.params[1]))
yieldStats.food -= (unique.params[0].toFloat() / 100f) * 2f // base 2 food per Pop
// Specialist Happiness Percentage Change 0f-1f
for (unique in city.getMatchingUniques(UniqueType.UnhappinessFromPopulationTypePercentageChange))
if (city.matchesFilter(unique.params[2]) && unique.params[1] == "Specialists")
yieldStats.happiness -= (unique.params[0].toFloat() / 100f) // relative val is negative, make positive
if (city.civInfo.getHappiness() < 0) yieldStats.happiness *= 2 // double weight for unhappy civilization
}
val surplusFood = cityStats[Stat.Food]
// Apply base weights
yieldStats.applyRankingWeights()
if (surplusFood > 0 && city.avoidGrowth) {
yieldStats.food = 0f // don't need more food!
} else {
if (cityAIFocus != CityFocus.NoFocus && cityAIFocus != CityFocus.FoodFocus && cityAIFocus != CityFocus.ProductionGrowthFocus && cityAIFocus != CityFocus.GoldGrowthFocus) {
// Focus on non-food/growth
if (surplusFood < 0)
yieldStats.food *= 8 // Starving, need Food, get to 0
else
yieldStats.food /= 2
} else if (!city.avoidGrowth) {
// NoFocus or Food/Growth Focus. Target +2 Food Surplus
if (surplusFood < 2)
yieldStats.food *= 8
else if (cityAIFocus != CityFocus.FoodFocus)
yieldStats.food /= 2
if (city.population.population < 5 && cityAIFocus != CityFocus.FoodFocus)
// NoFocus or GoldGrow or ProdGrow, not Avoid Growth, pop < 5. FoodFocus already does this up
yieldStats.food *= 3
}
}
if (city.population.population < 5) {
// "small city" - we care more about food and less about global problems like gold science and culture
// Food already handled above. Science/Culture have low weights in Stats already
yieldStats.gold /= 2 // it's barely worth anything at this point
} else {
if (city.civInfo.gold < 0 && city.civInfo.statsForNextTurn.gold <= 0)
yieldStats.gold *= 2 // We have a global problem
if (city.tiles.size < 12 || city.civInfo.wantsToFocusOn(Focus.Culture))
yieldStats.culture *= 2
if (city.civInfo.getHappiness() < 0 && !specialist) // since this doesn't get updated, may overshoot
yieldStats.happiness *= 2
if (city.civInfo.wantsToFocusOn(Focus.Science))
yieldStats.science *= 2
}
// Apply City focus
cityAIFocus.applyWeightTo(yieldStats)
return yieldStats.values.sum()
}
fun tryTrainMilitaryUnit(city: CityInfo) {
@ -74,8 +122,8 @@ object Automation {
mapUnit.getMatchingUniques(UniqueType.CarryAirUnits).firstOrNull() ?: return 0
if (mapUnitCarryUnique.params[1] != carryFilter) return 0 //Carries a different type of unit
return mapUnitCarryUnique.params[0].toInt() +
mapUnit.getMatchingUniques(UniqueType.CarryExtraAirUnits)
.filter { it.params[1] == carryFilter }.sumOf { it.params[0].toInt() }
mapUnit.getMatchingUniques(UniqueType.CarryExtraAirUnits)
.filter { it.params[1] == carryFilter }.sumOf { it.params[0].toInt() }
}
val totalCarriableUnits =
@ -97,13 +145,15 @@ object Automation {
findWaterConnectedCitiesAndEnemies.stepToEnd()
if (findWaterConnectedCitiesAndEnemies.getReachedTiles().none {
(it.isCityCenter() && it.getOwner() != city.civInfo)
|| (it.militaryUnit != null && it.militaryUnit!!.civInfo != city.civInfo)
|| (it.militaryUnit != null && it.militaryUnit!!.civInfo != city.civInfo)
}) // there is absolutely no reason for you to make water units on this body of water.
militaryUnits = militaryUnits.filter { !it.isWaterUnit() }
val carryingOnlyUnits = militaryUnits.filter { it.hasUnique(UniqueType.CarryAirUnits)
&& it.hasUnique(UniqueType.CannotAttack) }.toList()
val carryingOnlyUnits = militaryUnits.filter {
it.hasUnique(UniqueType.CarryAirUnits)
&& it.hasUnique(UniqueType.CannotAttack)
}.toList()
for (unit in carryingOnlyUnits)
if (providesUnneededCarryingSlots(unit, city.civInfo))
@ -125,9 +175,11 @@ object Automation {
.map { it.unitType }
.distinct()
if (availableTypes.none()) return null
val bestUnitsForType = availableTypes.map { type -> militaryUnits
val bestUnitsForType = availableTypes.map { type ->
militaryUnits
.filter { unit -> unit.unitType == type }
.maxByOrNull { unit -> unit.cost }!! }
.maxByOrNull { unit -> unit.cost }!!
}
// Check the maximum force evaluation for the shortlist so we can prune useless ones (ie scouts)
val bestForce = bestUnitsForType.maxOf { it.getForceEvaluation() }
chosenUnit = bestUnitsForType.filter { it.uniqueTo != null || it.getForceEvaluation() > bestForce / 3 }.toList().random()
@ -172,14 +224,14 @@ object Automation {
/** Checks both feasibility of Buildings with a CreatesOneImprovement unique
* and resource scarcity making a construction undesirable.
*/
*/
fun allowAutomatedConstruction(
civInfo: CivilizationInfo,
cityInfo: CityInfo,
construction: INonPerpetualConstruction
): Boolean {
return allowCreateImprovementBuildings(civInfo, cityInfo, construction)
&& allowSpendingResource(civInfo, construction)
&& allowSpendingResource(civInfo, construction)
}
/** Checks both feasibility of Buildings with a [UniqueType.CreatesOneImprovement] unique (appropriate tile available).
@ -241,7 +293,7 @@ object Automation {
val neededForBuilding = civInfo.lastEraResourceUsedForBuilding[resource] != null
// Don't care about old units
val neededForUnits = civInfo.lastEraResourceUsedForUnit[resource] != null
&& civInfo.lastEraResourceUsedForUnit[resource]!! >= civInfo.getEraNumber()
&& civInfo.lastEraResourceUsedForUnit[resource]!! >= civInfo.getEraNumber()
// No need to save for both
if (!neededForBuilding || !neededForUnits) {
@ -283,11 +335,11 @@ object Automation {
}
/** Support [UniqueType.CreatesOneImprovement] unique - find best tile for placement automation */
fun getTileForConstructionImprovement(cityInfo: CityInfo, improvement: TileImprovement): TileInfo? {
fun getTileForConstructionImprovement(cityInfo: CityInfo, improvement: TileImprovement): TileInfo? {
return cityInfo.getTiles().filter {
it.canBuildImprovement(improvement, cityInfo.civInfo)
}.maxByOrNull {
rankTileForCityWork(it, cityInfo)
rankTileForCityWork(it, cityInfo, cityInfo.cityStats.currentCityStats)
}
}
@ -350,7 +402,7 @@ object Automation {
if (adjacentTile.hasViewableResource(cityInfo.civInfo) &&
(adjacentDistance < 3 ||
adjacentTile.tileResource.resourceType != ResourceType.Bonus
)
)
) score -= 1
if (adjacentTile.naturalWonder != null) {
if (adjacentDistance < 3) adjacentNaturalWonder = true
@ -381,7 +433,7 @@ object Automation {
}
}
enum class ThreatLevel{
enum class ThreatLevel {
VeryLow,
Low,
Medium,

View File

@ -853,7 +853,7 @@ object NextTurnAutomation {
city.annexCity()
}
city.reassignPopulation()
city.reassignAllPopulation()
city.cityConstructions.chooseNextConstruction()
if (city.health < city.getMaxHealth())

View File

@ -19,6 +19,7 @@ import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.stats.Stat
import com.unciv.models.stats.Stats
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
@ -34,6 +35,49 @@ enum class CityFlags {
Resistance
}
// if tableEnabled == true, then Stat != null
enum class CityFocus(val label: String, val tableEnabled: Boolean, val stat: Stat? = null) {
NoFocus("Default Focus", true, null) {
override fun getStatMultiplier(stat: Stat) = 1f // actually redundant, but that's two steps to see
},
FoodFocus("${Stat.Food.name} Focus", true, Stat.Food),
ProductionFocus("${Stat.Production.name} Focus", true, Stat.Production),
GoldFocus("${Stat.Gold.name} Focus", true, Stat.Gold),
ScienceFocus("${Stat.Science.name} Focus", true, Stat.Science),
CultureFocus("${Stat.Culture.name} Focus", true, Stat.Culture),
GoldGrowthFocus("Gold Growth Focus", false) {
override fun getStatMultiplier(stat: Stat) = when (stat) {
Stat.Gold, Stat.Food -> 2f
else -> 1f
}
},
ProductionGrowthFocus("Production Growth Focus", false) {
override fun getStatMultiplier(stat: Stat) = when (stat) {
Stat.Production, Stat.Food -> 2f
else -> 1f
}
},
FaithFocus("${Stat.Faith.name} Focus", true, Stat.Faith),
HappinessFocus("${Stat.Happiness.name} Focus", false, Stat.Happiness);
//GreatPersonFocus;
open fun getStatMultiplier(stat: Stat) = when (this.stat) {
stat -> 3f
else -> 1f
}
fun applyWeightTo(stats: Stats) {
for (stat in Stat.values()) {
stats[stat] *= getStatMultiplier(stat)
}
}
fun safeValueOf(stat: Stat): CityFocus {
return values().firstOrNull { it.stat == stat } ?: NoFocus
}
}
class CityInfo {
@Suppress("JoinDeclarationAndAssignment")
@Transient
@ -81,10 +125,15 @@ class CityInfo {
/** Tiles that the population in them won't be reassigned */
var lockedTiles = HashSet<Vector2>()
var manualSpecialists = false
var isBeingRazed = false
var attackedThisTurn = false
var hasSoldBuildingThisTurn = false
var isPuppet = false
var updateCitizens = false // flag so that on endTurn() the Governor reassigns Citizens
var cityAIFocus: CityFocus = CityFocus.NoFocus
var avoidGrowth: Boolean = false
@Transient var currentGPPBonus: Int = 0 // temporary variable saved for rankSpecialist()
/** The very first found city is the _original_ capital,
* while the _current_ capital can be any other city after the original one is captured.
@ -148,7 +197,6 @@ class CityInfo {
}
population.autoAssignPopulation()
cityStats.update()
// Update proximity rankings for all civs
for (otherCiv in civInfo.gameInfo.getAliveMajorCivs()) {
@ -302,6 +350,10 @@ class CityInfo {
toReturn.isOriginalCapital = isOriginalCapital
toReturn.flagsCountdown.putAll(flagsCountdown)
toReturn.demandedResource = demandedResource
toReturn.updateCitizens = updateCitizens
toReturn.cityAIFocus = cityAIFocus
toReturn.avoidGrowth = avoidGrowth
toReturn.manualSpecialists = manualSpecialists
return toReturn
}
@ -436,7 +488,7 @@ class CityInfo {
fun isGrowing() = foodForNextTurn() > 0
fun isStarving() = foodForNextTurn() < 0
private fun foodForNextTurn() = cityStats.currentCityStats.food.roundToInt()
fun foodForNextTurn() = cityStats.currentCityStats.food.roundToInt()
/** Take null to mean infinity. */
fun getNumTurnsToNewPopulation(): Int? {
@ -453,10 +505,32 @@ class CityInfo {
if (!isStarving()) return null
return population.foodStored / -foodForNextTurn() + 1
}
fun containsBuildingUnique(uniqueType: UniqueType) =
cityConstructions.getBuiltBuildings().flatMap { it.uniqueObjects }.any { it.isOfType(uniqueType) }
fun getGreatPersonPercentageBonus(): Int{
var allGppPercentageBonus = 0
for (unique in getMatchingUniques(UniqueType.GreatPersonPointPercentage)
+ getMatchingUniques(UniqueType.GreatPersonPointPercentageDeprecated)
) {
if (!matchesFilter(unique.params[1])) continue
allGppPercentageBonus += unique.params[0].toInt()
}
// Sweden UP
for (otherCiv in civInfo.getKnownCivs()) {
if (!civInfo.getDiplomacyManager(otherCiv).hasFlag(DiplomacyFlags.DeclarationOfFriendship))
continue
for (ourUnique in civInfo.getMatchingUniques(UniqueType.GreatPersonBoostWithFriendship))
allGppPercentageBonus += ourUnique.params[0].toInt()
for (theirUnique in otherCiv.getMatchingUniques(UniqueType.GreatPersonBoostWithFriendship))
allGppPercentageBonus += theirUnique.params[0].toInt()
}
return allGppPercentageBonus
}
fun getGreatPersonPointsForNextTurn(): HashMap<String, Counter<String>> {
val sourceToGPP = HashMap<String, Counter<String>>()
@ -481,24 +555,7 @@ class CityInfo {
gppCounter.add(unitName, gppCounter[unitName]!! * unique.params[1].toInt() / 100)
}
var allGppPercentageBonus = 0
for (unique in getMatchingUniques(UniqueType.GreatPersonPointPercentage)
+ getMatchingUniques(UniqueType.GreatPersonPointPercentageDeprecated)
) {
if (!matchesFilter(unique.params[1])) continue
allGppPercentageBonus += unique.params[0].toInt()
}
// Sweden UP
for (otherCiv in civInfo.getKnownCivs()) {
if (!civInfo.getDiplomacyManager(otherCiv).hasFlag(DiplomacyFlags.DeclarationOfFriendship))
continue
for (ourUnique in civInfo.getMatchingUniques(UniqueType.GreatPersonBoostWithFriendship))
allGppPercentageBonus += ourUnique.params[0].toInt()
for (theirUnique in otherCiv.getMatchingUniques(UniqueType.GreatPersonBoostWithFriendship))
allGppPercentageBonus += theirUnique.params[0].toInt()
}
val allGppPercentageBonus = getGreatPersonPercentageBonus()
for (unitName in gppCounter.keys)
gppCounter.add(unitName, gppCounter[unitName]!! * allGppPercentageBonus / 100)
@ -585,7 +642,11 @@ class CityInfo {
tryUpdateRoadStatus()
attackedThisTurn = false
if (isPuppet) reassignPopulation()
if (isPuppet) reassignAllPopulation()
else if (updateCitizens){
reassignPopulation()
updateCitizens = false
}
// The ordering is intentional - you get a turn without WLTKD even if you have the next resource already
if (!hasFlag(CityFlags.WeLoveTheKing))
@ -644,19 +705,23 @@ class CityInfo {
demandedResource = ""
}
fun reassignPopulation() {
var foodWeight = 1f
var foodPerTurn = 0f
while (foodWeight < 3 && foodPerTurn <= 0) {
workedTiles = hashSetOf()
population.specialistAllocations.clear()
for (i in 0..population.population)
population.autoAssignPopulation(foodWeight)
cityStats.update()
// Reassign all Specialists and Unlock all tiles
// Mainly for automated cities, Puppets, just captured
fun reassignAllPopulation() {
manualSpecialists = false
reassignPopulation(resetLocked = true)
}
foodPerTurn = foodForNextTurn().toFloat()
foodWeight += 0.5f
fun reassignPopulation(resetLocked: Boolean = false) {
if (resetLocked) {
workedTiles = hashSetOf()
lockedTiles = hashSetOf()
} else {
workedTiles = lockedTiles
}
if (!manualSpecialists)
population.specialistAllocations.clear()
population.autoAssignPopulation()
}
fun endTurn() {
@ -905,4 +970,4 @@ class CityInfo {
}
//endregion
}
}

View File

@ -108,7 +108,7 @@ class CityInfoConquestFunctions(val city: CityInfo){
health = getMaxHealth() / 2 // I think that cities recover to half health when conquered?
if (population.population > 1) population.addPopulation(-1 - population.population / 4) // so from 2-4 population, remove 1, from 5-8, remove 2, etc.
reassignPopulation()
reassignAllPopulation()
if (!reconqueredCityWhileStillInResistance && foundingCiv != receivingCiv.civName) {
// add resistance

View File

@ -2,9 +2,12 @@ package com.unciv.logic.city
import com.unciv.logic.automation.Automation
import com.unciv.logic.civilization.NotificationIcon
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
import com.unciv.logic.map.TileInfo
import com.unciv.models.Counter
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.Stat
import com.unciv.ui.utils.toPercent
import com.unciv.ui.utils.withItem
import com.unciv.ui.utils.withoutItem
import kotlin.math.floor
@ -82,7 +85,7 @@ class PopulationManager {
if (percentOfFoodCarriedOver > 95) percentOfFoodCarriedOver = 95
foodStored += (getFoodToNextPopulation() * percentOfFoodCarriedOver / 100f).toInt()
addPopulation(1)
autoAssignPopulation()
cityInfo.updateCitizens = true
cityInfo.civInfo.addNotification("[${cityInfo.name}] has grown!", cityInfo.location, NotificationIcon.Growth)
}
}
@ -109,34 +112,50 @@ class PopulationManager {
addPopulation(-population + count)
}
internal fun autoAssignPopulation(foodWeight: Float = 1f) {
internal fun autoAssignPopulation() {
cityInfo.cityStats.update() // calculate current stats with current assignments
val cityStats = cityInfo.cityStats.currentCityStats
cityInfo.currentGPPBonus = cityInfo.getGreatPersonPercentageBonus() // pre-calculate
var specialistFoodBonus = 2f // See CityStats.calcFoodEaten()
for (unique in cityInfo.getMatchingUniques(UniqueType.FoodConsumptionBySpecialists))
if (cityInfo.matchesFilter(unique.params[1]))
specialistFoodBonus *= unique.params[0].toPercent()
specialistFoodBonus = 2f - specialistFoodBonus
for (i in 1..getFreePopulation()) {
//evaluate tiles
val bestTile: TileInfo? = cityInfo.getTiles()
val (bestTile, valueBestTile) = cityInfo.getTiles()
.filter { it.aerialDistanceTo(cityInfo.getCenterTile()) <= 3 }
.filterNot { it.providesYield() }
.maxByOrNull { Automation.rankTileForCityWork(it, cityInfo, foodWeight) }
val valueBestTile = if (bestTile == null) 0f
else Automation.rankTileForCityWork(bestTile, cityInfo, foodWeight)
.associateWith { Automation.rankTileForCityWork(it, cityInfo, cityStats) }
.maxByOrNull { it.value }
?: object : Map.Entry<TileInfo?, Float> {
override val key: TileInfo? = null
override val value = 0f
}
val bestJob: String? = getMaxSpecialists()
val bestJob: String? = if (cityInfo.manualSpecialists) null else getMaxSpecialists()
.filter { specialistAllocations[it.key]!! < it.value }
.map { it.key }
.maxByOrNull { Automation.rankSpecialist(getStatsOfSpecialist(it), cityInfo) }
.maxByOrNull { Automation.rankSpecialist(it, cityInfo, cityStats) }
var valueBestSpecialist = 0f
if (bestJob != null) {
val specialistStats = getStatsOfSpecialist(bestJob)
valueBestSpecialist = Automation.rankSpecialist(specialistStats, cityInfo)
valueBestSpecialist = Automation.rankSpecialist(bestJob, cityInfo, cityStats)
}
//assign population
if (valueBestTile > valueBestSpecialist) {
if (bestTile != null)
if (bestTile != null) {
cityInfo.workedTiles = cityInfo.workedTiles.withItem(bestTile.position)
} else if (bestJob != null) specialistAllocations.add(bestJob, 1)
cityStats[Stat.Food] += bestTile.getTileStats(cityInfo, cityInfo.civInfo)[Stat.Food]
}
} else if (bestJob != null) {
specialistAllocations.add(bestJob, 1)
cityStats[Stat.Food] += specialistFoodBonus
}
}
cityInfo.cityStats.update()
}
fun unassignExtraPopulation() {
@ -162,19 +181,19 @@ class PopulationManager {
cityInfo.workedTiles.asSequence()
.map { cityInfo.tileMap[it] }
.minByOrNull {
Automation.rankTileForCityWork(it, cityInfo)
Automation.rankTileForCityWork(it, cityInfo, cityInfo.cityStats.currentCityStats)
+(if (it.isLocked()) 10 else 0)
}!!
}
val valueWorstTile = if (worstWorkedTile == null) 0f
else Automation.rankTileForCityWork(worstWorkedTile, cityInfo)
else Automation.rankTileForCityWork(worstWorkedTile, cityInfo, cityInfo.cityStats.currentCityStats)
//evaluate specialists
val worstJob: String? = specialistAllocations.keys
.minByOrNull { Automation.rankSpecialist(getStatsOfSpecialist(it), cityInfo) }
val worstJob: String? = if (cityInfo.manualSpecialists) null else specialistAllocations.keys
.minByOrNull { Automation.rankSpecialist(it, cityInfo, cityInfo.cityStats.currentCityStats) }
var valueWorstSpecialist = 0f
if (worstJob != null)
valueWorstSpecialist = Automation.rankSpecialist(getStatsOfSpecialist(worstJob), cityInfo)
valueWorstSpecialist = Automation.rankSpecialist(worstJob, cityInfo, cityInfo.cityStats.currentCityStats)
//un-assign population

View File

@ -257,6 +257,9 @@ class TechManager {
UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo)
}
updateTransientBooleans()
for (city in civInfo.cities) {
city.updateCitizens = true
}
civInfo.addNotification("Research of [$techName] has completed!", TechAction(techName), NotificationIcon.Science, techName)
civInfo.popupAlerts.add(PopupAlert(AlertType.TechResearched, techName))

View File

@ -678,6 +678,7 @@ class MapUnit {
}
tile.improvementInProgress = null
tile.getCity()?.updateCitizens = true
}

View File

@ -126,6 +126,17 @@ open class Stats(
}
operator fun div(number: Float) = times(1/number)
/** Apply weighting for Production Ranking */
fun applyRankingWeights(){
food *= 14
production *= 12
gold *= 8 // 3 gold worth about 2 production
science *= 7
culture *= 6
happiness *= 10 // base
faith *= 5
}
/** ***Not*** only a debug helper. It returns a string representing the content, already _translated_.
*

View File

@ -0,0 +1,62 @@
package com.unciv.ui.cityscreen
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.logic.city.CityFocus
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.utils.*
class CitizenManagementTable(val cityScreen: CityScreen) : Table() {
private val innerTable = Table()
val city = cityScreen.city
init {
innerTable.background = ImageGetter.getBackground(ImageGetter.getBlue().darken(0.5f))
add(innerTable).pad(2f).fill()
background = ImageGetter.getBackground(Color.WHITE)
}
fun update(visible: Boolean = false) {
innerTable.clear()
if (!visible) {
isVisible = false
return
}
isVisible = true
val colorSelected = BaseScreen.skin.get("selection", Color::class.java)
val colorButton = BaseScreen.skin.get("color", Color::class.java)
// effectively a button, but didn't want to rewrite TextButton style
// and much more compact and can control backgrounds easily based on settings
val avoidLabel = "Avoid Growth".toLabel()
val avoidCell = Table()
avoidCell.touchable = Touchable.enabled
avoidCell.add(avoidLabel).pad(5f)
avoidCell.onClick { city.avoidGrowth = !city.avoidGrowth; city.reassignPopulation(); cityScreen.update() }
avoidCell.background = ImageGetter.getBackground(if (city.avoidGrowth) colorSelected else colorButton)
innerTable.add(avoidCell).colspan(2).growX().pad(3f)
innerTable.row()
for (focus in CityFocus.values()) {
if (!focus.tableEnabled) continue
if (focus == CityFocus.FaithFocus && !city.civInfo.gameInfo.isReligionEnabled()) continue
val label = focus.label.toLabel()
val cell = Table()
cell.touchable = Touchable.enabled
cell.add(label).pad(5f)
cell.onClick { city.cityAIFocus = focus; city.reassignPopulation(); cityScreen.update() }
cell.background = ImageGetter.getBackground(if (city.cityAIFocus == focus) colorSelected else colorButton)
innerTable.add(cell).growX().pad(3f)
if (focus.stat != null)
innerTable.add(ImageGetter.getStatIcon(focus.stat.name)).size(20f).padRight(5f)
innerTable.row()
}
pack()
}
}

View File

@ -15,10 +15,10 @@ import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.tile.TileImprovement
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.Stat
import com.unciv.models.translations.tr
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.map.TileGroupMap
import com.unciv.ui.popup.ToastPopup
import com.unciv.ui.tilegroups.TileGroup
import com.unciv.ui.tilegroups.TileSetStrings
import com.unciv.ui.utils.*
import java.util.*
@ -56,6 +56,12 @@ class CityScreen(
/** Displays raze city button - sits on TOP CENTER */
private var razeCityButtonHolder = Table()
/** Displays reset locks button - sits on BOT RIGHT */
private var resetCitizensButtonHolder = Table()
/** Displays reset locks button - sits on BOT RIGHT */
private var citizenManagementButtonHolder = Table()
/** Displays city stats info */
private var cityStatsTable = CityStatsTable(this)
@ -65,6 +71,10 @@ class CityScreen(
/** Displays selected construction info, alternate with tileTable - sits on BOTTOM RIGHT */
private var selectedConstructionTable = ConstructionInfoTable(this)
/** Displays selected construction info, alternate with tileTable - sits on BOTTOM RIGHT */
private var citizenManagementTable = CitizenManagementTable(this)
var citizenManagementVisible = false
/** Displays city name, allows switching between cities - sits on BOTTOM CENTER */
private var cityPickerTable = CityScreenCityPickerTable(this)
@ -109,10 +119,27 @@ class CityScreen(
//stage.setDebugTableUnderMouse(true)
stage.addActor(cityStatsTable)
val resetCitizensButton = "Reset Citizens".toTextButton()
resetCitizensButton.labelCell.pad(5f)
resetCitizensButton.onClick { city.reassignPopulation(resetLocked = true); update() }
resetCitizensButtonHolder.add(resetCitizensButton)
resetCitizensButtonHolder.pack()
stage.addActor(resetCitizensButtonHolder)
val citizenManagementButton = "Citizen Management".toTextButton()
citizenManagementButton.labelCell.pad(5f)
citizenManagementButton.onClick {
clearSelection()
citizenManagementVisible = true
update()
}
citizenManagementButtonHolder.add(citizenManagementButton)
citizenManagementButtonHolder.pack()
stage.addActor(citizenManagementButtonHolder)
constructionsTable.addActorsToStage()
stage.addActor(cityInfoTable)
stage.addActor(selectedConstructionTable)
stage.addActor(tileTable)
stage.addActor(citizenManagementTable)
stage.addActor(cityPickerTable) // add late so it's top in Z-order and doesn't get covered in cramped portrait
stage.addActor(exitCityButton)
update()
@ -150,7 +177,24 @@ class CityScreen(
tileTable.setPosition(stage.width - posFromEdge, posFromEdge, Align.bottomRight)
selectedConstructionTable.update(selectedConstruction)
selectedConstructionTable.setPosition(stage.width - posFromEdge, posFromEdge, Align.bottomRight)
citizenManagementTable.update(citizenManagementVisible)
citizenManagementTable.setPosition(stage.width - posFromEdge, posFromEdge, Align.bottomRight)
if (selectedTile == null && selectedConstruction == null && !citizenManagementVisible)
resetCitizensButtonHolder.setPosition(stage.width - posFromEdge,
posFromEdge, Align.bottomRight)
else if (selectedConstruction != null)
resetCitizensButtonHolder.setPosition(stage.width - posFromEdge,
posFromEdge + selectedConstructionTable.height + 10f, Align.bottomRight)
else if (selectedTile != null)
resetCitizensButtonHolder.setPosition(stage.width - posFromEdge,
posFromEdge + tileTable.height + 10f, Align.bottomRight)
else
resetCitizensButtonHolder.setPosition(stage.width - posFromEdge,
posFromEdge + citizenManagementTable.height + 10f, Align.bottomRight)
citizenManagementButtonHolder.isVisible = !citizenManagementVisible
citizenManagementButtonHolder.setPosition(stage.width - posFromEdge,
posFromEdge + resetCitizensButtonHolder.y + resetCitizensButtonHolder.height + 10f, Align.bottomRight)
// In portrait mode only: calculate already occupied horizontal space
val rightMargin = when {
!isPortrait() -> 0f
@ -343,9 +387,12 @@ class CityScreen(
if (tileGroup.isWorkable && canChangeState) {
if (!tileInfo.providesYield() && cityInfo.population.getFreePopulation() > 0) {
cityInfo.workedTiles.add(tileInfo.position)
cityInfo.lockedTiles.add(tileInfo.position)
game.settings.addCompletedTutorialTask("Reassign worked tiles")
} else if (tileInfo.isWorked() && !tileInfo.isLocked())
} else if (tileInfo.isWorked()) {
cityInfo.workedTiles.remove(tileInfo.position)
cityInfo.lockedTiles.remove(tileInfo.position)
}
cityInfo.cityStats.update()
}
update()
@ -365,11 +412,13 @@ class CityScreen(
pickTileData = null
}
selectedTile = null
citizenManagementVisible = false
}
private fun selectTile(newTile: TileInfo?) {
selectedConstruction = null
selectedQueueEntryTargetTile = null
pickTileData = null
citizenManagementVisible = false
selectedTile = newTile
}
fun clearSelection() = selectTile(null)

View File

@ -7,6 +7,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align
import com.unciv.UncivGame
import com.unciv.logic.city.CityFlags
import com.unciv.logic.city.CityFocus
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.Stat
import com.unciv.models.translations.tr
@ -35,9 +36,24 @@ class CityStatsTable(val cityScreen: CityScreen): Table() {
innerTable.clear()
val miniStatsTable = Table()
val selected = BaseScreen.skin.get("selection", Color::class.java)
for ((stat, amount) in cityInfo.cityStats.currentCityStats) {
if (stat == Stat.Faith && !cityInfo.civInfo.gameInfo.isReligionEnabled()) continue
miniStatsTable.add(ImageGetter.getStatIcon(stat.name)).size(20f).padRight(5f)
val icon = Table()
if (cityInfo.cityAIFocus.stat == stat) {
icon.add(ImageGetter.getStatIcon(stat.name).surroundWithCircle(27f, false, color = selected))
icon.onClick {
cityInfo.cityAIFocus = CityFocus.NoFocus
cityInfo.reassignPopulation(); cityScreen.update()
}
} else {
icon.add(ImageGetter.getStatIcon(stat.name).surroundWithCircle(27f, false, color = Color.CLEAR))
icon.onClick {
cityInfo.cityAIFocus = cityInfo.cityAIFocus.safeValueOf(stat)
cityInfo.reassignPopulation(); cityScreen.update()
}
}
miniStatsTable.add(icon).size(27f).padRight(5f)
val valueToDisplay = if (stat == Stat.Happiness) cityInfo.cityStats.happinessList.values.sum() else amount
miniStatsTable.add(round(valueToDisplay).toInt().toLabel()).padRight(10f)
}
@ -57,6 +73,8 @@ class CityStatsTable(val cityScreen: CityScreen): Table() {
private fun addText() {
val unassignedPopString = "{Unassigned population}: ".tr() +
cityInfo.population.getFreePopulation().toString() + "/" + cityInfo.population.population
val unassignedPopLabel = unassignedPopString.toLabel()
unassignedPopLabel.onClick { cityInfo.reassignPopulation(); cityScreen.update() }
var turnsToExpansionString =
if (cityInfo.cityStats.currentCityStats.culture > 0 && cityInfo.expansion.getChoosableTiles().any()) {
@ -80,7 +98,7 @@ class CityStatsTable(val cityScreen: CityScreen): Table() {
}.tr()
turnsToPopString += " (${cityInfo.population.foodStored}${Fonts.food}/${cityInfo.population.getFoodToNextPopulation()}${Fonts.food})"
innerTable.add(unassignedPopString.toLabel()).row()
innerTable.add(unassignedPopLabel).row()
innerTable.add(turnsToExpansionString.toLabel()).row()
innerTable.add(turnsToPopString.toLabel()).row()

View File

@ -6,15 +6,27 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.models.translations.tr
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.utils.*
class SpecialistAllocationTable(val cityScreen: CityScreen): Table(BaseScreen.skin){
class SpecialistAllocationTable(val cityScreen: CityScreen) : Table(BaseScreen.skin) {
val cityInfo = cityScreen.city
fun update() {
clear()
// Auto/Manual Specialists Toggle
// Color of "color" coming from Skin.json that's loaded into BaseScreen
// 5 columns: unassignButton, AllocationTable, assignButton, SeparatorVertical, SpecialistsStatsTabe
if (cityInfo.manualSpecialists) {
val manualSpecialists = "Manual Specialists".toLabel().addBorder(5f, BaseScreen.skin.get("color", Color::class.java))
manualSpecialists.onClick { cityInfo.manualSpecialists = false; cityInfo.reassignPopulation(); cityScreen.update() }
add(manualSpecialists).colspan(5).row()
} else {
val autoSpecialists = "Auto Specialists".toLabel().addBorder(5f, BaseScreen.skin.get("color", Color::class.java))
autoSpecialists.onClick { cityInfo.manualSpecialists = true; update() }
add(autoSpecialists).colspan(5).row()
}
for ((specialistName, maxSpecialists) in cityInfo.population.getMaxSpecialists()) {
if (!cityInfo.getRuleset().specialists.containsKey(specialistName)) // specialist doesn't exist in this ruleset, probably a mod
continue
@ -31,7 +43,7 @@ class SpecialistAllocationTable(val cityScreen: CityScreen): Table(BaseScreen.sk
}
fun getAllocationTable(assignedSpecialists: Int, maxSpecialists: Int, specialistName: String):Table{
fun getAllocationTable(assignedSpecialists: Int, maxSpecialists: Int, specialistName: String): Table {
val specialistIconTable = Table()
val specialistObject = cityInfo.getRuleset().specialists[specialistName]!!
@ -44,14 +56,15 @@ class SpecialistAllocationTable(val cityScreen: CityScreen): Table(BaseScreen.sk
return specialistIconTable
}
private fun getAssignButton(assignedSpecialists: Int, maxSpecialists: Int, specialistName: String):Actor {
private fun getAssignButton(assignedSpecialists: Int, maxSpecialists: Int, specialistName: String): Actor {
if (assignedSpecialists >= maxSpecialists || cityInfo.isPuppet) return Table()
val assignButton = "+".toLabel(Color.BLACK, Constants.headingFontSize)
.apply { this.setAlignment(Align.center) }
.surroundWithCircle(30f).apply { circle.color= Color.GREEN.darken(0.2f) }
.apply { this.setAlignment(Align.center) }
.surroundWithCircle(30f).apply { circle.color = Color.GREEN.darken(0.2f) }
assignButton.onClick {
cityInfo.population.specialistAllocations.add(specialistName, 1)
cityInfo.manualSpecialists = true
cityInfo.cityStats.update()
cityScreen.update()
}
@ -60,17 +73,18 @@ class SpecialistAllocationTable(val cityScreen: CityScreen): Table(BaseScreen.sk
return assignButton
}
private fun getUnassignButton(assignedSpecialists: Int, specialistName: String):Actor {
val unassignButton = "-".toLabel(Color.BLACK,Constants.headingFontSize)
.apply { this.setAlignment(Align.center) }
.surroundWithCircle(30f).apply { circle.color= Color.RED.darken(0.1f) }
private fun getUnassignButton(assignedSpecialists: Int, specialistName: String): Actor {
val unassignButton = "-".toLabel(Color.BLACK, Constants.headingFontSize)
.apply { this.setAlignment(Align.center) }
.surroundWithCircle(30f).apply { circle.color = Color.RED.darken(0.1f) }
unassignButton.onClick {
cityInfo.population.specialistAllocations.add(specialistName, -1)
cityInfo.manualSpecialists = true
cityInfo.cityStats.update()
cityScreen.update()
}
if (assignedSpecialists <= 0 || cityInfo.isPuppet) unassignButton.isVisible=false
if (assignedSpecialists <= 0 || cityInfo.isPuppet) unassignButton.isVisible = false
if (!UncivGame.Current.worldScreen.isPlayersTurn) unassignButton.clear()
return unassignButton
}
@ -88,7 +102,7 @@ class SpecialistAllocationTable(val cityScreen: CityScreen): Table(BaseScreen.sk
}
fun asExpander(onChange: (()->Unit)?): ExpanderTab {
fun asExpander(onChange: (() -> Unit)?): ExpanderTab {
return ExpanderTab(
title = "{Specialists}:",
fontSize = Constants.defaultFontSize,

View File

@ -282,6 +282,7 @@ object UnitActions {
tile.setPillaged()
unit.civInfo.lastSeenImprovement.remove(tile.position)
if (tile.resource != null) tile.getOwner()?.updateDetailedCivResources() // this might take away a resource
tile.getCity()?.updateCitizens = true
val freePillage = unit.hasUnique(UniqueType.NoMovementToPillage, checkCivInfoUniques = true)
if (!freePillage) unit.useMovementPoints(1f)