mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-13 09:18:43 +07:00
Rework of the "Creates improvement on a specific tile" Unique (#6687)
* Make Citadel tile takeover a unique * CreatesOneImprovement unique overhaul step 1 * CreatesOneImprovement unique overhaul - increase highlights alpha * Fix missing translatables, again * CreatesOneImprovement unique overhaul - review suggestions Co-authored-by: Yair Morgenstern <yairm210@hotmail.com>
This commit is contained in:
@ -198,8 +198,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Citadel",
|
"name": "Citadel",
|
||||||
"uniques": ["Great Improvement", "Gives a defensive bonus of [100]%", "Adjacent enemy units ending their turn take [30] damage", "Can be built just outside your borders"],
|
"uniques": [
|
||||||
"civilopediaText": [{"text":"Constructing it will take over the tiles around it and assign them to your closest city"}]
|
"Great Improvement",
|
||||||
|
"Gives a defensive bonus of [100]%",
|
||||||
|
"Adjacent enemy units ending their turn take [30] damage",
|
||||||
|
"Can be built just outside your borders",
|
||||||
|
"Constructing it will take over the tiles around it and assign them to your closest city"]
|
||||||
},
|
},
|
||||||
|
|
||||||
//Civilization unique improvements
|
//Civilization unique improvements
|
||||||
|
@ -198,8 +198,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Citadel",
|
"name": "Citadel",
|
||||||
"uniques": ["Great Improvement", "Gives a defensive bonus of [100]%", "Adjacent enemy units ending their turn take [30] damage", "Can be built just outside your borders"],
|
"uniques": [
|
||||||
"civilopediaText": [{"text":"Constructing it will take over the tiles around it and assign them to your closest city"}]
|
"Great Improvement",
|
||||||
|
"Gives a defensive bonus of [100]%",
|
||||||
|
"Adjacent enemy units ending their turn take [30] damage",
|
||||||
|
"Can be built just outside your borders",
|
||||||
|
"Constructing it will take over the tiles around it and assign them to your closest city"]
|
||||||
},
|
},
|
||||||
|
|
||||||
//Civilization unique improvements
|
//Civilization unique improvements
|
||||||
|
@ -953,6 +953,7 @@ Lock =
|
|||||||
Unlock =
|
Unlock =
|
||||||
Move to city =
|
Move to city =
|
||||||
Please enter a new name for your city =
|
Please enter a new name for your city =
|
||||||
|
Please select a tile for this building's [improvement] =
|
||||||
|
|
||||||
# Ask for text or numbers popup UI
|
# Ask for text or numbers popup UI
|
||||||
|
|
||||||
|
@ -44,7 +44,6 @@ object Constants {
|
|||||||
const val unknownNationName = "???"
|
const val unknownNationName = "???"
|
||||||
|
|
||||||
const val fort = "Fort"
|
const val fort = "Fort"
|
||||||
const val citadel = "Citadel"
|
|
||||||
|
|
||||||
const val futureTech = "Future Tech"
|
const val futureTech = "Future Tech"
|
||||||
// Easter egg name. Is to avoid conflicts when players name their own religions.
|
// Easter egg name. Is to avoid conflicts when players name their own religions.
|
||||||
|
@ -12,6 +12,7 @@ import com.unciv.models.ruleset.MilestoneType
|
|||||||
import com.unciv.models.ruleset.Victory
|
import com.unciv.models.ruleset.Victory
|
||||||
import com.unciv.models.ruleset.Victory.Focus
|
import com.unciv.models.ruleset.Victory.Focus
|
||||||
import com.unciv.models.ruleset.tile.ResourceType
|
import com.unciv.models.ruleset.tile.ResourceType
|
||||||
|
import com.unciv.models.ruleset.tile.TileImprovement
|
||||||
import com.unciv.models.ruleset.unique.LocalUniqueCache
|
import com.unciv.models.ruleset.unique.LocalUniqueCache
|
||||||
import com.unciv.models.ruleset.unique.UniqueType
|
import com.unciv.models.ruleset.unique.UniqueType
|
||||||
import com.unciv.models.ruleset.unit.BaseUnit
|
import com.unciv.models.ruleset.unit.BaseUnit
|
||||||
@ -169,9 +170,35 @@ object Automation {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Checks both feasibility of Buildings with a [UniqueType.CreatesOneImprovement] unique (appropriate tile available).
|
||||||
|
* Constructions without pass uncontested. */
|
||||||
|
fun allowCreateImprovementBuildings(
|
||||||
|
civInfo: CivilizationInfo,
|
||||||
|
cityInfo: CityInfo,
|
||||||
|
construction: INonPerpetualConstruction
|
||||||
|
): Boolean {
|
||||||
|
if (construction !is Building) return true
|
||||||
|
if (!construction.hasCreateOneImprovementUnique()) return true // redundant but faster???
|
||||||
|
val improvement = construction.getImprovementToCreate(cityInfo.getRuleset()) ?: return true
|
||||||
|
return cityInfo.getTiles().any {
|
||||||
|
it.canBuildImprovement(improvement, civInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Determines whether the AI should be willing to spend strategic resources to build
|
/** Determines whether the AI should be willing to spend strategic resources to build
|
||||||
* [construction] in [city], assumes that we are actually able to do so. */
|
* [construction] for [civInfo], assumes that we are actually able to do so. */
|
||||||
fun allowSpendingResource(civInfo: CivilizationInfo, construction: INonPerpetualConstruction): Boolean {
|
fun allowSpendingResource(civInfo: CivilizationInfo, construction: INonPerpetualConstruction): Boolean {
|
||||||
// City states do whatever they want
|
// City states do whatever they want
|
||||||
if (civInfo.isCityState())
|
if (civInfo.isCityState())
|
||||||
@ -255,6 +282,15 @@ object Automation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Support [UniqueType.CreatesOneImprovement] unique - find best tile for placement automation */
|
||||||
|
fun getTileForConstructionImprovement(cityInfo: CityInfo, improvement: TileImprovement): TileInfo? {
|
||||||
|
return cityInfo.getTiles().filter {
|
||||||
|
it.canBuildImprovement(improvement, cityInfo.civInfo)
|
||||||
|
}.maxByOrNull {
|
||||||
|
rankTileForCityWork(it, cityInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ranks a tile for any purpose except the expansion algorithm of cities
|
// Ranks a tile for any purpose except the expansion algorithm of cities
|
||||||
internal fun rankTile(tile: TileInfo?, civInfo: CivilizationInfo): Float {
|
internal fun rankTile(tile: TileInfo?, civInfo: CivilizationInfo): Float {
|
||||||
if (tile == null) return 0f
|
if (tile == null) return 0f
|
||||||
|
@ -55,17 +55,15 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
|||||||
|
|
||||||
data class ConstructionChoice(val choice: String, var choiceModifier: Float, val remainingWork: Int)
|
data class ConstructionChoice(val choice: String, var choiceModifier: Float, val remainingWork: Int)
|
||||||
|
|
||||||
private fun addChoice(choices: ArrayList<ConstructionChoice>, choice: String, choiceModifier: Float){
|
private fun addChoice(choices: ArrayList<ConstructionChoice>, choice: String, choiceModifier: Float) {
|
||||||
choices.add(ConstructionChoice(choice,choiceModifier,cityConstructions.getRemainingWork(choice)))
|
choices.add(ConstructionChoice(choice, choiceModifier, cityConstructions.getRemainingWork(choice)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun chooseNextConstruction() {
|
fun chooseNextConstruction() {
|
||||||
if (!UncivGame.Current.settings.autoAssignCityProduction
|
if (!UncivGame.Current.settings.autoAssignCityProduction
|
||||||
&& civInfo.playerType == PlayerType.Human && !cityInfo.isPuppet
|
&& civInfo.playerType == PlayerType.Human && !cityInfo.isPuppet)
|
||||||
) {
|
|
||||||
return
|
return
|
||||||
}
|
|
||||||
if (cityConstructions.getCurrentConstruction() !is PerpetualConstruction) return // don't want to be stuck on these forever
|
if (cityConstructions.getCurrentConstruction() !is PerpetualConstruction) return // don't want to be stuck on these forever
|
||||||
|
|
||||||
addFoodBuildingChoice()
|
addFoodBuildingChoice()
|
||||||
@ -119,9 +117,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
|||||||
.filter { it.getStatBuyCost(cityInfo, stat = Stat.Faith)!! <= civInfo.religionManager.storedFaith }
|
.filter { it.getStatBuyCost(cityInfo, stat = Stat.Faith)!! <= civInfo.religionManager.storedFaith }
|
||||||
.firstOrNull() ?: return
|
.firstOrNull() ?: return
|
||||||
|
|
||||||
|
|
||||||
cityConstructions.purchaseConstruction(chosenItem.name, -1, false, stat=Stat.Faith)
|
cityConstructions.purchaseConstruction(chosenItem.name, -1, false, stat=Stat.Faith)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addMilitaryUnitChoice() {
|
private fun addMilitaryUnitChoice() {
|
||||||
@ -152,7 +148,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
|||||||
val buildableWorkboatUnits = buildableUnits
|
val buildableWorkboatUnits = buildableUnits
|
||||||
.filter {
|
.filter {
|
||||||
it.hasUnique(UniqueType.CreateWaterImprovements)
|
it.hasUnique(UniqueType.CreateWaterImprovements)
|
||||||
&& Automation.allowSpendingResource(civInfo, it)
|
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it)
|
||||||
}
|
}
|
||||||
val alreadyHasWorkBoat = buildableWorkboatUnits.any()
|
val alreadyHasWorkBoat = buildableWorkboatUnits.any()
|
||||||
&& !cityInfo.getTiles().any {
|
&& !cityInfo.getTiles().any {
|
||||||
@ -182,7 +178,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
|||||||
val workerEquivalents = buildableUnits
|
val workerEquivalents = buildableUnits
|
||||||
.filter {
|
.filter {
|
||||||
it.hasUnique(UniqueType.BuildImprovements)
|
it.hasUnique(UniqueType.BuildImprovements)
|
||||||
&& Automation.allowSpendingResource(civInfo, it)
|
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it)
|
||||||
}
|
}
|
||||||
if (workerEquivalents.none()) return // for mods with no worker units
|
if (workerEquivalents.none()) return // for mods with no worker units
|
||||||
|
|
||||||
@ -196,7 +192,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
|||||||
private fun addCultureBuildingChoice() {
|
private fun addCultureBuildingChoice() {
|
||||||
val cultureBuilding = buildableNotWonders
|
val cultureBuilding = buildableNotWonders
|
||||||
.filter { it.isStatRelated(Stat.Culture)
|
.filter { it.isStatRelated(Stat.Culture)
|
||||||
&& Automation.allowSpendingResource(civInfo, it) }.minByOrNull { it.cost }
|
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it) }.minByOrNull { it.cost }
|
||||||
if (cultureBuilding != null) {
|
if (cultureBuilding != null) {
|
||||||
var modifier = 0.5f
|
var modifier = 0.5f
|
||||||
if (cityInfo.cityStats.currentCityStats.culture == 0f) // It won't grow if we don't help it
|
if (cityInfo.cityStats.currentCityStats.culture == 0f) // It won't grow if we don't help it
|
||||||
@ -216,7 +212,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
|||||||
|
|
||||||
private fun addOtherBuildingChoice() {
|
private fun addOtherBuildingChoice() {
|
||||||
val otherBuilding = buildableNotWonders
|
val otherBuilding = buildableNotWonders
|
||||||
.filter { Automation.allowSpendingResource(civInfo, it) }.minByOrNull { it.cost }
|
.filter { Automation.allowAutomatedConstruction(civInfo, cityInfo, it) }.minByOrNull { it.cost }
|
||||||
if (otherBuilding != null) {
|
if (otherBuilding != null) {
|
||||||
val modifier = 0.6f
|
val modifier = 0.6f
|
||||||
addChoice(relativeCostEffectiveness, otherBuilding.name, modifier)
|
addChoice(relativeCostEffectiveness, otherBuilding.name, modifier)
|
||||||
@ -256,7 +252,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
|||||||
if (!buildableWonders.any()) return
|
if (!buildableWonders.any()) return
|
||||||
|
|
||||||
val highestPriorityWonder = buildableWonders
|
val highestPriorityWonder = buildableWonders
|
||||||
.filter { Automation.allowSpendingResource(civInfo, it) }
|
.filter { Automation.allowAutomatedConstruction(civInfo, cityInfo, it) }
|
||||||
.maxByOrNull { getWonderPriority(it) }!!
|
.maxByOrNull { getWonderPriority(it) }!!
|
||||||
val citiesBuildingWonders = civInfo.cities
|
val citiesBuildingWonders = civInfo.cities
|
||||||
.count { it.cityConstructions.isBuildingWonder() }
|
.count { it.cityConstructions.isBuildingWonder() }
|
||||||
@ -269,7 +265,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
|||||||
private fun addUnitTrainingBuildingChoice() {
|
private fun addUnitTrainingBuildingChoice() {
|
||||||
val unitTrainingBuilding = buildableNotWonders.asSequence()
|
val unitTrainingBuilding = buildableNotWonders.asSequence()
|
||||||
.filter { it.hasUnique(UniqueType.UnitStartingExperience)
|
.filter { it.hasUnique(UniqueType.UnitStartingExperience)
|
||||||
&& Automation.allowSpendingResource(civInfo, it)}.minByOrNull { it.cost }
|
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it) }.minByOrNull { it.cost }
|
||||||
if (unitTrainingBuilding != null && (!civInfo.wantsToFocusOn(Focus.Culture) || isAtWar)) {
|
if (unitTrainingBuilding != null && (!civInfo.wantsToFocusOn(Focus.Culture) || isAtWar)) {
|
||||||
var modifier = if (cityIsOverAverageProduction) 0.5f else 0.1f // You shouldn't be cranking out units anytime soon
|
var modifier = if (cityIsOverAverageProduction) 0.5f else 0.1f // You shouldn't be cranking out units anytime soon
|
||||||
if (isAtWar) modifier *= 2
|
if (isAtWar) modifier *= 2
|
||||||
@ -282,7 +278,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
|||||||
private fun addDefenceBuildingChoice() {
|
private fun addDefenceBuildingChoice() {
|
||||||
val defensiveBuilding = buildableNotWonders.asSequence()
|
val defensiveBuilding = buildableNotWonders.asSequence()
|
||||||
.filter { it.cityStrength > 0
|
.filter { it.cityStrength > 0
|
||||||
&& Automation.allowSpendingResource(civInfo, it)}.minByOrNull { it.cost }
|
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it)}.minByOrNull { it.cost }
|
||||||
if (defensiveBuilding != null && (isAtWar || !civInfo.wantsToFocusOn(Victory.Focus.Culture))) {
|
if (defensiveBuilding != null && (isAtWar || !civInfo.wantsToFocusOn(Victory.Focus.Culture))) {
|
||||||
var modifier = 0.2f
|
var modifier = 0.2f
|
||||||
if (isAtWar) modifier = 0.5f
|
if (isAtWar) modifier = 0.5f
|
||||||
@ -300,7 +296,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
|||||||
val happinessBuilding = buildableNotWonders.asSequence()
|
val happinessBuilding = buildableNotWonders.asSequence()
|
||||||
.filter { (it.isStatRelated(Stat.Happiness)
|
.filter { (it.isStatRelated(Stat.Happiness)
|
||||||
|| it.uniques.contains("Remove extra unhappiness from annexed cities"))
|
|| it.uniques.contains("Remove extra unhappiness from annexed cities"))
|
||||||
&& Automation.allowSpendingResource(civInfo, it)}
|
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it) }
|
||||||
.minByOrNull { it.cost }
|
.minByOrNull { it.cost }
|
||||||
if (happinessBuilding != null) {
|
if (happinessBuilding != null) {
|
||||||
var modifier = 1f
|
var modifier = 1f
|
||||||
@ -315,7 +311,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
|||||||
if (allTechsAreResearched) return
|
if (allTechsAreResearched) return
|
||||||
val scienceBuilding = buildableNotWonders.asSequence()
|
val scienceBuilding = buildableNotWonders.asSequence()
|
||||||
.filter { it.isStatRelated(Stat.Science)
|
.filter { it.isStatRelated(Stat.Science)
|
||||||
&& Automation.allowSpendingResource(civInfo, it)}
|
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it) }
|
||||||
.minByOrNull { it.cost }
|
.minByOrNull { it.cost }
|
||||||
if (scienceBuilding != null) {
|
if (scienceBuilding != null) {
|
||||||
var modifier = 1.1f
|
var modifier = 1.1f
|
||||||
@ -327,7 +323,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
|||||||
|
|
||||||
private fun addGoldBuildingChoice() {
|
private fun addGoldBuildingChoice() {
|
||||||
val goldBuilding = buildableNotWonders.asSequence().filter { it.isStatRelated(Stat.Gold)
|
val goldBuilding = buildableNotWonders.asSequence().filter { it.isStatRelated(Stat.Gold)
|
||||||
&& Automation.allowSpendingResource(civInfo, it)}
|
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it) }
|
||||||
.minByOrNull { it.cost }
|
.minByOrNull { it.cost }
|
||||||
if (goldBuilding != null) {
|
if (goldBuilding != null) {
|
||||||
val modifier = if (civInfo.statsForNextTurn.gold < 0) 3f else 1.2f
|
val modifier = if (civInfo.statsForNextTurn.gold < 0) 3f else 1.2f
|
||||||
@ -337,7 +333,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
|||||||
|
|
||||||
private fun addProductionBuildingChoice() {
|
private fun addProductionBuildingChoice() {
|
||||||
val productionBuilding = buildableNotWonders.asSequence()
|
val productionBuilding = buildableNotWonders.asSequence()
|
||||||
.filter { it.isStatRelated(Stat.Production) && Automation.allowSpendingResource(civInfo, it) }
|
.filter { it.isStatRelated(Stat.Production) && Automation.allowAutomatedConstruction(civInfo, cityInfo, it) }
|
||||||
.minByOrNull { it.cost }
|
.minByOrNull { it.cost }
|
||||||
if (productionBuilding != null) {
|
if (productionBuilding != null) {
|
||||||
addChoice(relativeCostEffectiveness, productionBuilding.name, 1.5f)
|
addChoice(relativeCostEffectiveness, productionBuilding.name, 1.5f)
|
||||||
@ -350,7 +346,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
|||||||
.filter {
|
.filter {
|
||||||
(it.isStatRelated(Stat.Food)
|
(it.isStatRelated(Stat.Food)
|
||||||
|| it.hasUnique(UniqueType.CarryOverFood, conditionalState)
|
|| it.hasUnique(UniqueType.CarryOverFood, conditionalState)
|
||||||
) && Automation.allowSpendingResource(civInfo, it)
|
) && Automation.allowAutomatedConstruction(civInfo, cityInfo, it)
|
||||||
}.minByOrNull { it.cost }
|
}.minByOrNull { it.cost }
|
||||||
if (foodBuilding != null) {
|
if (foodBuilding != null) {
|
||||||
var modifier = 1f
|
var modifier = 1f
|
||||||
|
@ -736,9 +736,7 @@ object Battle {
|
|||||||
if (tile.getTileImprovement()!!.hasUnique(UniqueType.Unpillagable)) {
|
if (tile.getTileImprovement()!!.hasUnique(UniqueType.Unpillagable)) {
|
||||||
tile.improvement = null
|
tile.improvement = null
|
||||||
} else {
|
} else {
|
||||||
tile.turnsToImprovement = 2
|
tile.setPillaged()
|
||||||
tile.improvementInProgress = tile.improvement
|
|
||||||
tile.improvement = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tile.roadStatus = RoadStatus.None
|
tile.roadStatus = RoadStatus.None
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
package com.unciv.logic.city
|
package com.unciv.logic.city
|
||||||
|
|
||||||
|
import com.unciv.UncivGame
|
||||||
|
import com.unciv.logic.automation.Automation
|
||||||
import com.unciv.logic.automation.ConstructionAutomation
|
import com.unciv.logic.automation.ConstructionAutomation
|
||||||
import com.unciv.logic.civilization.AlertType
|
import com.unciv.logic.civilization.AlertType
|
||||||
import com.unciv.logic.civilization.NotificationIcon
|
import com.unciv.logic.civilization.NotificationIcon
|
||||||
import com.unciv.logic.civilization.PopupAlert
|
import com.unciv.logic.civilization.PopupAlert
|
||||||
|
import com.unciv.logic.map.MapUnit // for Kdoc only
|
||||||
|
import com.unciv.logic.map.TileInfo
|
||||||
import com.unciv.models.ruleset.Building
|
import com.unciv.models.ruleset.Building
|
||||||
import com.unciv.models.ruleset.Ruleset
|
import com.unciv.models.ruleset.Ruleset
|
||||||
import com.unciv.models.ruleset.unique.LocalUniqueCache
|
import com.unciv.models.ruleset.unique.LocalUniqueCache
|
||||||
@ -19,11 +23,13 @@ import com.unciv.ui.civilopedia.FormattedLine
|
|||||||
import com.unciv.ui.utils.Fonts
|
import com.unciv.ui.utils.Fonts
|
||||||
import com.unciv.ui.utils.withItem
|
import com.unciv.ui.utils.withItem
|
||||||
import com.unciv.ui.utils.withoutItem
|
import com.unciv.ui.utils.withoutItem
|
||||||
|
import com.unciv.ui.worldscreen.unit.UnitActions // for Kdoc only
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* City constructions manager.
|
* City constructions manager.
|
||||||
*
|
*
|
||||||
@ -33,6 +39,7 @@ import kotlin.math.roundToInt
|
|||||||
* @property constructionQueue a list of constructions names enqueued
|
* @property constructionQueue a list of constructions names enqueued
|
||||||
*/
|
*/
|
||||||
class CityConstructions {
|
class CityConstructions {
|
||||||
|
//region Non-Serialized Properties
|
||||||
@Transient
|
@Transient
|
||||||
lateinit var cityInfo: CityInfo
|
lateinit var cityInfo: CityInfo
|
||||||
|
|
||||||
@ -42,8 +49,7 @@ class CityConstructions {
|
|||||||
@Transient
|
@Transient
|
||||||
val builtBuildingUniqueMap = UniqueMap()
|
val builtBuildingUniqueMap = UniqueMap()
|
||||||
|
|
||||||
var builtBuildings = HashSet<String>()
|
// No backing field, not serialized
|
||||||
val inProgressConstructions = HashMap<String, Int>()
|
|
||||||
var currentConstructionFromQueue: String
|
var currentConstructionFromQueue: String
|
||||||
get() {
|
get() {
|
||||||
return if (constructionQueue.isEmpty()) ""
|
return if (constructionQueue.isEmpty()) ""
|
||||||
@ -52,15 +58,23 @@ class CityConstructions {
|
|||||||
set(value) {
|
set(value) {
|
||||||
if (constructionQueue.isEmpty()) constructionQueue.add(value) else constructionQueue[0] = value
|
if (constructionQueue.isEmpty()) constructionQueue.add(value) else constructionQueue[0] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
//region Serialized Fields
|
||||||
|
|
||||||
|
var builtBuildings = HashSet<String>()
|
||||||
|
val inProgressConstructions = HashMap<String, Int>()
|
||||||
var currentConstructionIsUserSet = false
|
var currentConstructionIsUserSet = false
|
||||||
var constructionQueue = mutableListOf<String>()
|
var constructionQueue = mutableListOf<String>()
|
||||||
var productionOverflow = 0
|
var productionOverflow = 0
|
||||||
val queueMaxSize = 10
|
private val queueMaxSize = 10
|
||||||
|
|
||||||
// Maps cities to the buildings they received
|
// Maps cities to the buildings they received
|
||||||
val freeBuildingsProvidedFromThisCity: HashMap<String, HashSet<String>> = hashMapOf()
|
val freeBuildingsProvidedFromThisCity: HashMap<String, HashSet<String>> = hashMapOf()
|
||||||
|
|
||||||
|
//endregion
|
||||||
//region pure functions
|
//region pure functions
|
||||||
|
|
||||||
fun clone(): CityConstructions {
|
fun clone(): CityConstructions {
|
||||||
val toReturn = CityConstructions()
|
val toReturn = CityConstructions()
|
||||||
toReturn.builtBuildings.addAll(builtBuildings)
|
toReturn.builtBuildings.addAll(builtBuildings)
|
||||||
@ -111,49 +125,14 @@ class CityConstructions {
|
|||||||
fun getCityProductionTextForCityButton(): String {
|
fun getCityProductionTextForCityButton(): String {
|
||||||
val currentConstructionSnapshot = currentConstructionFromQueue // See below
|
val currentConstructionSnapshot = currentConstructionFromQueue // See below
|
||||||
var result = currentConstructionSnapshot.tr()
|
var result = currentConstructionSnapshot.tr()
|
||||||
if (currentConstructionSnapshot != "") {
|
if (currentConstructionSnapshot.isNotEmpty()) {
|
||||||
val construction = PerpetualConstruction.perpetualConstructionsMap[currentConstructionSnapshot]
|
val construction = PerpetualConstruction.perpetualConstructionsMap[currentConstructionSnapshot]
|
||||||
if (construction == null) result += getTurnsToConstructionString(currentConstructionSnapshot)
|
result += construction?.getProductionTooltip(cityInfo)
|
||||||
else result += construction.getProductionTooltip(cityInfo)
|
?: getTurnsToConstructionString(currentConstructionSnapshot)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addFreeBuildings() {
|
|
||||||
// "Gain a free [buildingName] [cityFilter]"
|
|
||||||
val freeBuildingUniques = cityInfo.getLocalMatchingUniques(UniqueType.GainFreeBuildings, StateForConditionals(cityInfo.civInfo, cityInfo))
|
|
||||||
|
|
||||||
for (unique in freeBuildingUniques) {
|
|
||||||
val freeBuildingName = cityInfo.civInfo.getEquivalentBuilding(unique.params[0]).name
|
|
||||||
val citiesThatApply = when (unique.params[1]) {
|
|
||||||
"in this city" -> listOf(cityInfo)
|
|
||||||
"in other cities" -> cityInfo.civInfo.cities.filter { it !== cityInfo }
|
|
||||||
else -> cityInfo.civInfo.cities.filter { it.matchesFilter(unique.params[1]) }
|
|
||||||
}
|
|
||||||
|
|
||||||
for (city in citiesThatApply) {
|
|
||||||
if (city.cityConstructions.containsBuildingOrEquivalent(freeBuildingName)) continue
|
|
||||||
city.cityConstructions.addBuilding(freeBuildingName)
|
|
||||||
if (city.id !in freeBuildingsProvidedFromThisCity)
|
|
||||||
freeBuildingsProvidedFromThisCity[city.id] = hashSetOf()
|
|
||||||
|
|
||||||
freeBuildingsProvidedFromThisCity[city.id]!!.add(freeBuildingName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Civ-level uniques - for these only add free buildings from each city to itself to avoid weirdness on city conquest
|
|
||||||
for (unique in cityInfo.civInfo.getMatchingUniques(UniqueType.GainFreeBuildings, stateForConditionals = StateForConditionals(cityInfo.civInfo, cityInfo))) {
|
|
||||||
val freeBuildingName = cityInfo.civInfo.getEquivalentBuilding(unique.params[0]).name
|
|
||||||
if (cityInfo.matchesFilter(unique.params[1])) {
|
|
||||||
if (cityInfo.id !in freeBuildingsProvidedFromThisCity)
|
|
||||||
freeBuildingsProvidedFromThisCity[cityInfo.id] = hashSetOf()
|
|
||||||
freeBuildingsProvidedFromThisCity[cityInfo.id]!!.add(freeBuildingName)
|
|
||||||
if (!isBuilt(freeBuildingName))
|
|
||||||
addBuilding(freeBuildingName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @constructionName needs to be a non-perpetual construction, else an empty string is returned */
|
/** @constructionName needs to be a non-perpetual construction, else an empty string is returned */
|
||||||
internal fun getTurnsToConstructionString(constructionName: String, useStoredProduction:Boolean = true): String {
|
internal fun getTurnsToConstructionString(constructionName: String, useStoredProduction:Boolean = true): String {
|
||||||
val construction = getConstruction(constructionName)
|
val construction = getConstruction(constructionName)
|
||||||
@ -284,9 +263,17 @@ class CityConstructions {
|
|||||||
|
|
||||||
return ceil((workLeft-productionOverflow) / production.toDouble()).toInt()
|
return ceil((workLeft-productionOverflow) / production.toDouble()).toInt()
|
||||||
}
|
}
|
||||||
//endregion
|
|
||||||
|
|
||||||
|
fun hasBuildableStatBuildings(stat: Stat): Boolean {
|
||||||
|
return getBasicStatBuildings(stat)
|
||||||
|
.map { cityInfo.civInfo.getEquivalentBuilding(it.name) }
|
||||||
|
.filter { it.isBuildable(this) || isBeingConstructedOrEnqueued(it.name) }
|
||||||
|
.any()
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
//region state changing functions
|
//region state changing functions
|
||||||
|
|
||||||
fun setTransients() {
|
fun setTransients() {
|
||||||
builtBuildingObjects = ArrayList(builtBuildings.map {
|
builtBuildingObjects = ArrayList(builtBuildings.map {
|
||||||
cityInfo.getRuleset().buildings[it]
|
cityInfo.getRuleset().buildings[it]
|
||||||
@ -469,6 +456,41 @@ class CityConstructions {
|
|||||||
builtBuildingUniqueMap.addUnique(unique)
|
builtBuildingUniqueMap.addUnique(unique)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addFreeBuildings() {
|
||||||
|
// "Gain a free [buildingName] [cityFilter]"
|
||||||
|
val freeBuildingUniques = cityInfo.getLocalMatchingUniques(UniqueType.GainFreeBuildings, StateForConditionals(cityInfo.civInfo, cityInfo))
|
||||||
|
|
||||||
|
for (unique in freeBuildingUniques) {
|
||||||
|
val freeBuildingName = cityInfo.civInfo.getEquivalentBuilding(unique.params[0]).name
|
||||||
|
val citiesThatApply = when (unique.params[1]) {
|
||||||
|
"in this city" -> listOf(cityInfo)
|
||||||
|
"in other cities" -> cityInfo.civInfo.cities.filter { it !== cityInfo }
|
||||||
|
else -> cityInfo.civInfo.cities.filter { it.matchesFilter(unique.params[1]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
for (city in citiesThatApply) {
|
||||||
|
if (city.cityConstructions.containsBuildingOrEquivalent(freeBuildingName)) continue
|
||||||
|
city.cityConstructions.addBuilding(freeBuildingName)
|
||||||
|
if (city.id !in freeBuildingsProvidedFromThisCity)
|
||||||
|
freeBuildingsProvidedFromThisCity[city.id] = hashSetOf()
|
||||||
|
|
||||||
|
freeBuildingsProvidedFromThisCity[city.id]!!.add(freeBuildingName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Civ-level uniques - for these only add free buildings from each city to itself to avoid weirdness on city conquest
|
||||||
|
for (unique in cityInfo.civInfo.getMatchingUniques(UniqueType.GainFreeBuildings, stateForConditionals = StateForConditionals(cityInfo.civInfo, cityInfo))) {
|
||||||
|
val freeBuildingName = cityInfo.civInfo.getEquivalentBuilding(unique.params[0]).name
|
||||||
|
if (cityInfo.matchesFilter(unique.params[1])) {
|
||||||
|
if (cityInfo.id !in freeBuildingsProvidedFromThisCity)
|
||||||
|
freeBuildingsProvidedFromThisCity[cityInfo.id] = hashSetOf()
|
||||||
|
freeBuildingsProvidedFromThisCity[cityInfo.id]!!.add(freeBuildingName)
|
||||||
|
if (!isBuilt(freeBuildingName))
|
||||||
|
addBuilding(freeBuildingName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Purchase a construction for gold
|
* Purchase a construction for gold
|
||||||
* called from NextTurnAutomation and the City UI
|
* called from NextTurnAutomation and the City UI
|
||||||
@ -480,22 +502,36 @@ class CityConstructions {
|
|||||||
* @param automatic Flag whether automation should try to choose what next to build (not coming from UI)
|
* @param automatic Flag whether automation should try to choose what next to build (not coming from UI)
|
||||||
* Note: settings.autoAssignCityProduction is handled later
|
* Note: settings.autoAssignCityProduction is handled later
|
||||||
* @param stat Stat object of the stat with which was paid for the construction
|
* @param stat Stat object of the stat with which was paid for the construction
|
||||||
* @return Success (false e.g. unit cannot be placed
|
* @param tile Supports [UniqueType.CreatesOneImprovement] the tile to place the improvement from that unique on.
|
||||||
|
* Ignored when the [constructionName] does not have that unique. If null and the building has the unique, a tile is chosen automatically.
|
||||||
|
* @return Success (false e.g. unit cannot be placed)
|
||||||
*/
|
*/
|
||||||
fun purchaseConstruction(
|
fun purchaseConstruction(
|
||||||
constructionName: String,
|
constructionName: String,
|
||||||
queuePosition: Int,
|
queuePosition: Int,
|
||||||
automatic: Boolean,
|
automatic: Boolean,
|
||||||
stat: Stat = Stat.Gold
|
stat: Stat = Stat.Gold,
|
||||||
|
tile: TileInfo? = null
|
||||||
): Boolean {
|
): Boolean {
|
||||||
if (!(getConstruction(constructionName) as INonPerpetualConstruction).postBuildEvent(this, stat))
|
val construction = getConstruction(constructionName) as? INonPerpetualConstruction ?: return false
|
||||||
|
|
||||||
|
// Support UniqueType.CreatesOneImprovement: it is active when getImprovementToCreate returns an improvement
|
||||||
|
val improvementToPlace = (construction as? Building)?.getImprovementToCreate(cityInfo.getRuleset())
|
||||||
|
if (improvementToPlace != null) {
|
||||||
|
// If active without a predetermined tile to place the improvement on, automate a tile
|
||||||
|
val finalTile = tile
|
||||||
|
?: Automation.getTileForConstructionImprovement(cityInfo, improvementToPlace)
|
||||||
|
?: return false // This was never reached in testing
|
||||||
|
finalTile.markForCreatesOneImprovement(improvementToPlace.name)
|
||||||
|
// postBuildEvent does the rest by calling cityConstructions.applyCreateOneImprovement
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!construction.postBuildEvent(this, stat))
|
||||||
return false // nothing built - no pay
|
return false // nothing built - no pay
|
||||||
|
|
||||||
if (!cityInfo.civInfo.gameInfo.gameParameters.godMode) {
|
if (!cityInfo.civInfo.gameInfo.gameParameters.godMode) {
|
||||||
val construction = getConstruction(constructionName)
|
val constructionCost = construction.getStatBuyCost(cityInfo, stat)
|
||||||
if (construction is PerpetualConstruction) return false
|
?: return false // We should never end up here anyway, so things have already gone _way_ wrong
|
||||||
val constructionCost = (construction as INonPerpetualConstruction).getStatBuyCost(cityInfo, stat)
|
|
||||||
if (constructionCost == null) return false // We should never end up here anyway, so things have already gone _way_ wrong
|
|
||||||
cityInfo.addStat(stat, -1 * constructionCost)
|
cityInfo.addStat(stat, -1 * constructionCost)
|
||||||
|
|
||||||
val conditionalState = StateForConditionals(civInfo = cityInfo.civInfo, cityInfo = cityInfo)
|
val conditionalState = StateForConditionals(civInfo = cityInfo.civInfo, cityInfo = cityInfo)
|
||||||
@ -523,21 +559,12 @@ class CityConstructions {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasBuildableStatBuildings(stat: Stat): Boolean {
|
|
||||||
return getBasicStatBuildings(stat)
|
|
||||||
.map { cityInfo.civInfo.getEquivalentBuilding(it.name) }
|
|
||||||
.filter { it.isBuildable(this) || isBeingConstructedOrEnqueued(it.name) }
|
|
||||||
.any()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addCheapestBuildableStatBuilding(stat: Stat): String? {
|
fun addCheapestBuildableStatBuilding(stat: Stat): String? {
|
||||||
val cheapestBuildableStatBuilding = getBasicStatBuildings(stat)
|
val cheapestBuildableStatBuilding = getBasicStatBuildings(stat)
|
||||||
.map { cityInfo.civInfo.getEquivalentBuilding(it.name) }
|
.map { cityInfo.civInfo.getEquivalentBuilding(it.name) }
|
||||||
.filter { it.isBuildable(this) || isBeingConstructedOrEnqueued(it.name) }
|
.filter { it.isBuildable(this) || isBeingConstructedOrEnqueued(it.name) }
|
||||||
.minByOrNull { it.cost }?.name
|
.minByOrNull { it.cost }?.name
|
||||||
|
?: return null
|
||||||
if (cheapestBuildableStatBuilding == null)
|
|
||||||
return null
|
|
||||||
|
|
||||||
constructionComplete(getConstruction(cheapestBuildableStatBuilding) as INonPerpetualConstruction)
|
constructionComplete(getConstruction(cheapestBuildableStatBuilding) as INonPerpetualConstruction)
|
||||||
|
|
||||||
@ -555,6 +582,13 @@ class CityConstructions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ConstructionAutomation(this).chooseNextConstruction()
|
ConstructionAutomation(this).chooseNextConstruction()
|
||||||
|
|
||||||
|
/** Support for [UniqueType.CreatesOneImprovement] - if an Improvement-creating Building was auto-queued, auto-choose a tile: */
|
||||||
|
val building = getCurrentConstruction() as? Building ?: return
|
||||||
|
val improvement = building.getImprovementToCreate(cityInfo.getRuleset()) ?: return
|
||||||
|
if (getTileForImprovement(improvement.name) != null) return
|
||||||
|
val newTile = Automation.getTileForConstructionImprovement(cityInfo, improvement) ?: return
|
||||||
|
newTile.markForCreatesOneImprovement(improvement.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addToQueue(constructionName: String) {
|
fun addToQueue(constructionName: String) {
|
||||||
@ -578,13 +612,13 @@ class CityConstructions {
|
|||||||
/** If this was done automatically, we should automatically try to choose a new construction and treat it as such */
|
/** If this was done automatically, we should automatically try to choose a new construction and treat it as such */
|
||||||
fun removeFromQueue(constructionQueueIndex: Int, automatic: Boolean) {
|
fun removeFromQueue(constructionQueueIndex: Int, automatic: Boolean) {
|
||||||
val constructionName = constructionQueue.removeAt(constructionQueueIndex)
|
val constructionName = constructionQueue.removeAt(constructionQueueIndex)
|
||||||
|
|
||||||
|
// UniqueType.CreatesOneImprovement support
|
||||||
val construction = getConstruction(constructionName)
|
val construction = getConstruction(constructionName)
|
||||||
if (construction is Building) {
|
if (construction is Building) {
|
||||||
val improvement = construction.getImprovement(cityInfo.getRuleset())
|
val improvement = construction.getImprovementToCreate(cityInfo.getRuleset())
|
||||||
if (improvement != null) {
|
if (improvement != null) {
|
||||||
val tileWithImprovement = cityInfo.getTiles().firstOrNull { it.improvementInProgress == improvement.name }
|
getTileForImprovement(improvement.name)?.stopWorkingOnImprovement()
|
||||||
tileWithImprovement?.improvementInProgress = null
|
|
||||||
tileWithImprovement?.turnsToImprovement = 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -604,10 +638,61 @@ class CityConstructions {
|
|||||||
raisePriority(constructionQueueIndex + 1)
|
raisePriority(constructionQueueIndex + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
//endregion
|
|
||||||
private fun MutableList<String>.swap(idx1: Int, idx2: Int) {
|
private fun MutableList<String>.swap(idx1: Int, idx2: Int) {
|
||||||
val tmp = this[idx1]
|
val tmp = this[idx1]
|
||||||
this[idx1] = this[idx2]
|
this[idx1] = this[idx2]
|
||||||
this[idx2] = tmp
|
this[idx2] = tmp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Support for [UniqueType.CreatesOneImprovement]:
|
||||||
|
*
|
||||||
|
* If [building] is an improvement-creating one, find a marked tile matching the improvement to be created
|
||||||
|
* (skip if none found), then un-mark the tile and place the improvement unless [removeOnly] is set.
|
||||||
|
*/
|
||||||
|
fun applyCreateOneImprovement(building: Building, removeOnly: Boolean = false) {
|
||||||
|
val improvement = building.getImprovementToCreate(cityInfo.getRuleset())
|
||||||
|
?: return
|
||||||
|
val tileForImprovement = getTileForImprovement(improvement.name) ?: return
|
||||||
|
tileForImprovement.stopWorkingOnImprovement() // clears mark
|
||||||
|
if (removeOnly) return
|
||||||
|
/**todo unify with [UnitActions.getImprovementConstructionActions] and [MapUnit.workOnImprovement] - this won't allow e.g. a building to place a road */
|
||||||
|
tileForImprovement.improvement = improvement.name
|
||||||
|
cityInfo.civInfo.lastSeenImprovement[tileForImprovement.position] = improvement.name
|
||||||
|
cityInfo.cityStats.update()
|
||||||
|
cityInfo.civInfo.updateDetailedCivResources()
|
||||||
|
// If bought the worldscreen will not have been marked to update, and the new improvement won't show until later...
|
||||||
|
if (UncivGame.isCurrentInitialized())
|
||||||
|
UncivGame.Current.worldScreen.shouldUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Support for [UniqueType.CreatesOneImprovement]:
|
||||||
|
*
|
||||||
|
* To be called after circumstances forced clearing a marker from a tile (pillaging, nuking).
|
||||||
|
* Should remove one matching building from the queue without looking for a marked tile.
|
||||||
|
*/
|
||||||
|
fun removeCreateOneImprovementConstruction(improvement: String) {
|
||||||
|
val ruleset = cityInfo.getRuleset()
|
||||||
|
val indexToRemove = constructionQueue.withIndex().mapNotNull {
|
||||||
|
val construction = getConstruction(it.value)
|
||||||
|
val buildingImprovement = (construction as? Building)?.getImprovementToCreate(ruleset)?.name
|
||||||
|
it.index.takeIf { buildingImprovement == improvement }
|
||||||
|
}.firstOrNull() ?: return
|
||||||
|
|
||||||
|
constructionQueue.removeAt(indexToRemove)
|
||||||
|
|
||||||
|
if (constructionQueue.isEmpty()) {
|
||||||
|
constructionQueue.add("Nothing")
|
||||||
|
currentConstructionIsUserSet = false
|
||||||
|
} else currentConstructionIsUserSet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Support for [UniqueType.CreatesOneImprovement]:
|
||||||
|
*
|
||||||
|
* Find the selected tile for a specific improvement being constructed via a building, if any.
|
||||||
|
*/
|
||||||
|
fun getTileForImprovement(improvementName: String) = cityInfo.getTiles()
|
||||||
|
.firstOrNull {
|
||||||
|
it.isMarkedForCreatesOneImprovement(improvementName)
|
||||||
|
}
|
||||||
|
//endregion
|
||||||
}
|
}
|
||||||
|
@ -129,6 +129,8 @@ class CityExpansionManager {
|
|||||||
city.lockedTiles.remove(tileInfo.position)
|
city.lockedTiles.remove(tileInfo.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tileInfo.removeCreatesOneImprovementMarker()
|
||||||
|
|
||||||
tileInfo.setOwningCity(null)
|
tileInfo.setOwningCity(null)
|
||||||
|
|
||||||
cityInfo.civInfo.updateDetailedCivResources()
|
cityInfo.civInfo.updateDetailedCivResources()
|
||||||
|
@ -696,7 +696,9 @@ class CityInfo {
|
|||||||
|
|
||||||
// The relinquish ownership MUST come before removing the city,
|
// The relinquish ownership MUST come before removing the city,
|
||||||
// because it updates the city stats which assumes there is a capital, so if you remove the capital it crashes
|
// because it updates the city stats which assumes there is a capital, so if you remove the capital it crashes
|
||||||
getTiles().forEach { expansion.relinquishOwnership(it) }
|
for (tile in getTiles()) {
|
||||||
|
expansion.relinquishOwnership(tile)
|
||||||
|
}
|
||||||
civInfo.cities = civInfo.cities.toMutableList().apply { remove(this@CityInfo) }
|
civInfo.cities = civInfo.cities.toMutableList().apply { remove(this@CityInfo) }
|
||||||
getCenterTile().improvement = "City ruins"
|
getCenterTile().improvement = "City ruins"
|
||||||
|
|
||||||
|
@ -449,9 +449,11 @@ class MapUnit {
|
|||||||
|
|
||||||
fun isIdle(): Boolean {
|
fun isIdle(): Boolean {
|
||||||
if (currentMovement == 0f) return false
|
if (currentMovement == 0f) return false
|
||||||
if (getTile().improvementInProgress != null
|
val tile = getTile()
|
||||||
&& canBuildImprovement(getTile().getTileImprovementInProgress()!!))
|
if (tile.improvementInProgress != null &&
|
||||||
return false
|
canBuildImprovement(tile.getTileImprovementInProgress()!!) &&
|
||||||
|
!tile.isMarkedForCreatesOneImprovement()
|
||||||
|
) return false
|
||||||
return !(isFortified() || isExploring() || isSleeping() || isAutomated() || isMoving())
|
return !(isFortified() || isExploring() || isSleeping() || isAutomated() || isMoving())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -625,6 +627,7 @@ class MapUnit {
|
|||||||
|
|
||||||
private fun workOnImprovement() {
|
private fun workOnImprovement() {
|
||||||
val tile = getTile()
|
val tile = getTile()
|
||||||
|
if (tile.isMarkedForCreatesOneImprovement()) return
|
||||||
tile.turnsToImprovement -= 1
|
tile.turnsToImprovement -= 1
|
||||||
if (tile.turnsToImprovement != 0) return
|
if (tile.turnsToImprovement != 0) return
|
||||||
|
|
||||||
|
@ -120,6 +120,8 @@ open class TileInfo {
|
|||||||
return toReturn
|
return toReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//region pure functions
|
||||||
|
|
||||||
fun containsGreatImprovement(): Boolean {
|
fun containsGreatImprovement(): Boolean {
|
||||||
return getTileImprovement()?.isGreatImprovement() == true
|
return getTileImprovement()?.isGreatImprovement() == true
|
||||||
}
|
}
|
||||||
@ -128,7 +130,6 @@ open class TileInfo {
|
|||||||
if (improvementInProgress == null) return false
|
if (improvementInProgress == null) return false
|
||||||
return ruleset.tileImprovements[improvementInProgress!!]!!.isGreatImprovement()
|
return ruleset.tileImprovements[improvementInProgress!!]!!.isGreatImprovement()
|
||||||
}
|
}
|
||||||
//region pure functions
|
|
||||||
|
|
||||||
/** Returns military, civilian and air units in tile */
|
/** Returns military, civilian and air units in tile */
|
||||||
fun getUnits() = sequence {
|
fun getUnits() = sequence {
|
||||||
@ -592,7 +593,7 @@ open class TileInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasImprovementInProgress() = improvementInProgress != null
|
fun hasImprovementInProgress() = improvementInProgress != null && turnsToImprovement > 0
|
||||||
|
|
||||||
@delegate:Transient
|
@delegate:Transient
|
||||||
private val _isCoastalTile: Boolean by lazy { neighbors.any { it.baseTerrain == Constants.coast } }
|
private val _isCoastalTile: Boolean by lazy { neighbors.any { it.baseTerrain == Constants.coast } }
|
||||||
@ -714,6 +715,7 @@ open class TileInfo {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Get info on a selected tile, used on WorldScreen (right side above minimap), CityScreen or MapEditorViewTab. */
|
||||||
fun toMarkup(viewingCiv: CivilizationInfo?): ArrayList<FormattedLine> {
|
fun toMarkup(viewingCiv: CivilizationInfo?): ArrayList<FormattedLine> {
|
||||||
val lineList = ArrayList<FormattedLine>()
|
val lineList = ArrayList<FormattedLine>()
|
||||||
val isViewableToPlayer = viewingCiv == null || UncivGame.Current.viewEntireMapForDebug
|
val isViewableToPlayer = viewingCiv == null || UncivGame.Current.viewEntireMapForDebug
|
||||||
@ -727,6 +729,7 @@ open class TileInfo {
|
|||||||
if (UncivGame.Current.viewEntireMapForDebug || city.civInfo == viewingCiv)
|
if (UncivGame.Current.viewEntireMapForDebug || city.civInfo == viewingCiv)
|
||||||
lineList += city.cityConstructions.getProductionMarkup(ruleset)
|
lineList += city.cityConstructions.getProductionMarkup(ruleset)
|
||||||
}
|
}
|
||||||
|
|
||||||
lineList += FormattedLine(baseTerrain, link="Terrain/$baseTerrain")
|
lineList += FormattedLine(baseTerrain, link="Terrain/$baseTerrain")
|
||||||
for (terrainFeature in terrainFeatures)
|
for (terrainFeature in terrainFeatures)
|
||||||
lineList += FormattedLine(terrainFeature, link="Terrain/$terrainFeature")
|
lineList += FormattedLine(terrainFeature, link="Terrain/$terrainFeature")
|
||||||
@ -753,11 +756,14 @@ open class TileInfo {
|
|||||||
val shownImprovement = getShownImprovement(viewingCiv)
|
val shownImprovement = getShownImprovement(viewingCiv)
|
||||||
if (shownImprovement != null)
|
if (shownImprovement != null)
|
||||||
lineList += FormattedLine(shownImprovement, link="Improvement/$shownImprovement")
|
lineList += FormattedLine(shownImprovement, link="Improvement/$shownImprovement")
|
||||||
|
|
||||||
if (improvementInProgress != null && isViewableToPlayer) {
|
if (improvementInProgress != null && isViewableToPlayer) {
|
||||||
|
// Negative turnsToImprovement is used for UniqueType.CreatesOneImprovement
|
||||||
val line = "{$improvementInProgress}" +
|
val line = "{$improvementInProgress}" +
|
||||||
if (turnsToImprovement > 0) " - $turnsToImprovement${Fonts.turn}" else " ({Under construction})"
|
if (turnsToImprovement > 0) " - $turnsToImprovement${Fonts.turn}" else " ({Under construction})"
|
||||||
lineList += FormattedLine(line, link="Improvement/$improvementInProgress")
|
lineList += FormattedLine(line, link="Improvement/$improvementInProgress")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (civilianUnit != null && isViewableToPlayer)
|
if (civilianUnit != null && isViewableToPlayer)
|
||||||
lineList += FormattedLine(civilianUnit!!.name.tr() + " - " + civilianUnit!!.civInfo.civName.tr(),
|
lineList += FormattedLine(civilianUnit!!.name.tr() + " - " + civilianUnit!!.civInfo.civName.tr(),
|
||||||
link="Unit/${civilianUnit!!.name}")
|
link="Unit/${civilianUnit!!.name}")
|
||||||
@ -767,6 +773,7 @@ open class TileInfo {
|
|||||||
" - " + militaryUnit!!.civInfo.civName.tr()
|
" - " + militaryUnit!!.civInfo.civName.tr()
|
||||||
lineList += FormattedLine(milUnitString, link="Unit/${militaryUnit!!.name}")
|
lineList += FormattedLine(milUnitString, link="Unit/${militaryUnit!!.name}")
|
||||||
}
|
}
|
||||||
|
|
||||||
val defenceBonus = getDefensiveBonus()
|
val defenceBonus = getDefensiveBonus()
|
||||||
if (defenceBonus != 0f) {
|
if (defenceBonus != 0f) {
|
||||||
var defencePercentString = (defenceBonus * 100).toInt().toString() + "%"
|
var defencePercentString = (defenceBonus * 100).toInt().toString() + "%"
|
||||||
@ -815,9 +822,16 @@ open class TileInfo {
|
|||||||
|
|
||||||
fun getContinent() = continent
|
fun getContinent() = continent
|
||||||
|
|
||||||
//endregion
|
/** Checks if this tile is marked as target tile for a building with a [UniqueType.CreatesOneImprovement] unique */
|
||||||
|
fun isMarkedForCreatesOneImprovement() =
|
||||||
|
turnsToImprovement < 0 && improvementInProgress != null
|
||||||
|
/** Checks if this tile is marked as target tile for a building with a [UniqueType.CreatesOneImprovement] unique creating a specific [improvement] */
|
||||||
|
fun isMarkedForCreatesOneImprovement(improvement: String) =
|
||||||
|
turnsToImprovement < 0 && improvementInProgress == improvement
|
||||||
|
|
||||||
|
//endregion
|
||||||
//region state-changing functions
|
//region state-changing functions
|
||||||
|
|
||||||
fun setTransients() {
|
fun setTransients() {
|
||||||
setTerrainTransients()
|
setTerrainTransients()
|
||||||
setUnitTransients(true)
|
setUnitTransients(true)
|
||||||
@ -914,11 +928,39 @@ open class TileInfo {
|
|||||||
else improvement.getTurnsToBuild(civInfo, unit)
|
else improvement.getTurnsToBuild(civInfo, unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Clears [improvementInProgress] and [turnsToImprovement] */
|
||||||
fun stopWorkingOnImprovement() {
|
fun stopWorkingOnImprovement() {
|
||||||
improvementInProgress = null
|
improvementInProgress = null
|
||||||
turnsToImprovement = 0
|
turnsToImprovement = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Sets tile improvement to pillaged (without prior checks for validity)
|
||||||
|
* and ensures that matching [UniqueType.CreatesOneImprovement] queued buildings are removed. */
|
||||||
|
fun setPillaged() {
|
||||||
|
// http://well-of-souls.com/civ/civ5_improvements.html says that naval improvements are destroyed upon pillage
|
||||||
|
// and I can't find any other sources so I'll go with that
|
||||||
|
if (isLand) {
|
||||||
|
// Setting turnsToImprovement might interfere with UniqueType.CreatesOneImprovement
|
||||||
|
removeCreatesOneImprovementMarker()
|
||||||
|
improvementInProgress = improvement
|
||||||
|
turnsToImprovement = 2
|
||||||
|
}
|
||||||
|
improvement = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Marks tile as target tile for a building with a [UniqueType.CreatesOneImprovement] unique */
|
||||||
|
fun markForCreatesOneImprovement(improvement: String) {
|
||||||
|
improvementInProgress = improvement
|
||||||
|
turnsToImprovement = -1
|
||||||
|
}
|
||||||
|
/** Un-Marks a tile as target tile for a building with a [UniqueType.CreatesOneImprovement] unique,
|
||||||
|
* and ensures that matching queued buildings are removed. */
|
||||||
|
fun removeCreatesOneImprovementMarker() {
|
||||||
|
if (!isMarkedForCreatesOneImprovement()) return
|
||||||
|
owningCity?.cityConstructions?.removeCreateOneImprovementConstruction(improvementInProgress!!)
|
||||||
|
stopWorkingOnImprovement()
|
||||||
|
}
|
||||||
|
|
||||||
fun normalizeToRuleset(ruleset: Ruleset) {
|
fun normalizeToRuleset(ruleset: Ruleset) {
|
||||||
if (naturalWonder != null && !ruleset.terrains.containsKey(naturalWonder))
|
if (naturalWonder != null && !ruleset.terrains.containsKey(naturalWonder))
|
||||||
naturalWonder = null
|
naturalWonder = null
|
||||||
|
@ -683,15 +683,8 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
|||||||
|
|
||||||
cityConstructions.addBuilding(name)
|
cityConstructions.addBuilding(name)
|
||||||
|
|
||||||
val improvement = getImprovement(civInfo.gameInfo.ruleSet)
|
/** Support for [UniqueType.CreatesOneImprovement] */
|
||||||
if (improvement != null) {
|
cityConstructions.applyCreateOneImprovement(this)
|
||||||
val tileWithImprovement = cityConstructions.cityInfo.getTiles().firstOrNull { it.improvementInProgress == improvement.name }
|
|
||||||
if (tileWithImprovement != null) {
|
|
||||||
tileWithImprovement.turnsToImprovement = 0
|
|
||||||
tileWithImprovement.improvementInProgress = null
|
|
||||||
tileWithImprovement.improvement = improvement.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// "Provides a free [buildingName] [cityFilter]"
|
// "Provides a free [buildingName] [cityFilter]"
|
||||||
cityConstructions.addFreeBuildings()
|
cityConstructions.addFreeBuildings()
|
||||||
@ -741,10 +734,20 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getImprovement(ruleset: Ruleset): TileImprovement? {
|
private val _hasCreatesOneImprovementUnique by lazy {
|
||||||
val improvementUnique = getMatchingUniques("Creates a [] improvement on a specific tile")
|
hasUnique(UniqueType.CreatesOneImprovement)
|
||||||
|
}
|
||||||
|
fun hasCreateOneImprovementUnique() = _hasCreatesOneImprovementUnique
|
||||||
|
|
||||||
|
private var _getImprovementToCreate: TileImprovement? = null
|
||||||
|
fun getImprovementToCreate(ruleset: Ruleset): TileImprovement? {
|
||||||
|
if (!hasCreateOneImprovementUnique()) return null
|
||||||
|
if (_getImprovementToCreate == null) {
|
||||||
|
val improvementUnique = getMatchingUniques(UniqueType.CreatesOneImprovement)
|
||||||
.firstOrNull() ?: return null
|
.firstOrNull() ?: return null
|
||||||
return ruleset.tileImprovements[improvementUnique.params[0]]
|
_getImprovementToCreate = ruleset.tileImprovements[improvementUnique.params[0]]
|
||||||
|
}
|
||||||
|
return _getImprovementToCreate
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isSellable() = !isAnyWonder() && !hasUnique(UniqueType.Unsellable)
|
fun isSellable() = !isAnyWonder() && !hasUnique(UniqueType.Unsellable)
|
||||||
|
@ -657,9 +657,6 @@ class Ruleset {
|
|||||||
if (building.requiredBuildingInAllCities != null)
|
if (building.requiredBuildingInAllCities != null)
|
||||||
lines.add("${building.name} contains 'requiredBuildingInAllCities' - please convert to a \"" +
|
lines.add("${building.name} contains 'requiredBuildingInAllCities' - please convert to a \"" +
|
||||||
UniqueType.RequiresBuildingInAllCities.text.fillPlaceholders(building.requiredBuildingInAllCities!!)+"\" unique", RulesetErrorSeverity.Warning)
|
UniqueType.RequiresBuildingInAllCities.text.fillPlaceholders(building.requiredBuildingInAllCities!!)+"\" unique", RulesetErrorSeverity.Warning)
|
||||||
for (unique in building.getMatchingUniques("Creates a [] improvement on a specific tile"))
|
|
||||||
if (!tileImprovements.containsKey(unique.params[0]))
|
|
||||||
lines += "${building.name} creates a ${unique.params[0]} improvement which does not exist!"
|
|
||||||
checkUniques(building, lines, rulesetSpecific, forOptionsPopup)
|
checkUniques(building, lines, rulesetSpecific, forOptionsPopup)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,10 +364,12 @@ enum class UniqueParameterType(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/** [UniqueType.ConstructImprovementConsumingUnit] */
|
/** For [UniqueType.ConstructImprovementConsumingUnit], [UniqueType.CreatesOneImprovement] */
|
||||||
ImprovementName("improvementName", "Trading Post", "The name of any improvement"){
|
ImprovementName("improvementName", "Trading Post", "The name of any improvement"){
|
||||||
override fun getErrorSeverity(parameterText: String,ruleset: Ruleset):
|
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
|
||||||
UniqueType.UniqueComplianceErrorSeverity? {
|
UniqueType.UniqueComplianceErrorSeverity? {
|
||||||
|
if (parameterText == Constants.cancelImprovementOrder)
|
||||||
|
return UniqueType.UniqueComplianceErrorSeverity.RulesetInvariant
|
||||||
if (ruleset.tileImprovements.containsKey(parameterText)) return null
|
if (ruleset.tileImprovements.containsKey(parameterText)) return null
|
||||||
return UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific
|
return UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific
|
||||||
}
|
}
|
||||||
|
@ -390,10 +390,10 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
|
|||||||
NotDestroyedWhenCityCaptured("Never destroyed when the city is captured", UniqueTarget.Building),
|
NotDestroyedWhenCityCaptured("Never destroyed when the city is captured", UniqueTarget.Building),
|
||||||
DoublesGoldFromCapturingCity("Doubles Gold given to enemy if city is captured", UniqueTarget.Building),
|
DoublesGoldFromCapturingCity("Doubles Gold given to enemy if city is captured", UniqueTarget.Building),
|
||||||
|
|
||||||
|
|
||||||
RemoveAnnexUnhappiness("Remove extra unhappiness from annexed cities", UniqueTarget.Building),
|
RemoveAnnexUnhappiness("Remove extra unhappiness from annexed cities", UniqueTarget.Building),
|
||||||
ConnectTradeRoutes("Connects trade routes over water", UniqueTarget.Building),
|
ConnectTradeRoutes("Connects trade routes over water", UniqueTarget.Building),
|
||||||
|
|
||||||
|
CreatesOneImprovement("Creates a [improvementName] improvement on a specific tile", UniqueTarget.Building),
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
@ -614,6 +614,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
|
|||||||
DefensiveBonus("Gives a defensive bonus of [relativeAmount]%", UniqueTarget.Improvement),
|
DefensiveBonus("Gives a defensive bonus of [relativeAmount]%", UniqueTarget.Improvement),
|
||||||
ImprovementMaintenance("Costs [amount] gold per turn when in your territory", UniqueTarget.Improvement), // Unused
|
ImprovementMaintenance("Costs [amount] gold per turn when in your territory", UniqueTarget.Improvement), // Unused
|
||||||
DamagesAdjacentEnemyUnits("Adjacent enemy units ending their turn take [amount] damage", UniqueTarget.Improvement),
|
DamagesAdjacentEnemyUnits("Adjacent enemy units ending their turn take [amount] damage", UniqueTarget.Improvement),
|
||||||
|
TakeOverTilesAroundWhenBuilt("Constructing it will take over the tiles around it and assign them to your closest city", UniqueTarget.Improvement),
|
||||||
|
|
||||||
GreatImprovement("Great Improvement", UniqueTarget.Improvement),
|
GreatImprovement("Great Improvement", UniqueTarget.Improvement),
|
||||||
IsAncientRuinsEquivalent("Provides a random bonus when entered", UniqueTarget.Improvement),
|
IsAncientRuinsEquivalent("Provides a random bonus when entered", UniqueTarget.Improvement),
|
||||||
|
@ -9,8 +9,10 @@ import com.badlogic.gdx.scenes.scene2d.ui.TextButton
|
|||||||
import com.badlogic.gdx.utils.Align
|
import com.badlogic.gdx.utils.Align
|
||||||
import com.unciv.Constants
|
import com.unciv.Constants
|
||||||
import com.unciv.logic.city.*
|
import com.unciv.logic.city.*
|
||||||
|
import com.unciv.logic.map.TileInfo
|
||||||
import com.unciv.models.UncivSound
|
import com.unciv.models.UncivSound
|
||||||
import com.unciv.models.ruleset.Building
|
import com.unciv.models.ruleset.Building
|
||||||
|
import com.unciv.models.ruleset.unique.UniqueType
|
||||||
import com.unciv.models.ruleset.unit.BaseUnit
|
import com.unciv.models.ruleset.unit.BaseUnit
|
||||||
import com.unciv.models.stats.Stat
|
import com.unciv.models.stats.Stat
|
||||||
import com.unciv.models.translations.tr
|
import com.unciv.models.translations.tr
|
||||||
@ -37,7 +39,6 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
|||||||
/* -1 = Nothing, >= 0 queue entry (0 = current construction) */
|
/* -1 = Nothing, >= 0 queue entry (0 = current construction) */
|
||||||
private var selectedQueueEntry = -1 // None
|
private var selectedQueueEntry = -1 // None
|
||||||
private var preferredBuyStat = Stat.Gold // Used for keyboard buy
|
private var preferredBuyStat = Stat.Gold // Used for keyboard buy
|
||||||
var improvementBuildingToConstruct: Building? = null
|
|
||||||
|
|
||||||
private val upperTable = Table(BaseScreen.skin)
|
private val upperTable = Table(BaseScreen.skin)
|
||||||
private val showCityInfoTableButton = "Show stats drilldown".toTextButton()
|
private val showCityInfoTableButton = "Show stats drilldown".toTextButton()
|
||||||
@ -91,7 +92,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
|||||||
/** Forces layout calculation and returns the lower Table's (available constructions) width
|
/** Forces layout calculation and returns the lower Table's (available constructions) width
|
||||||
* - or - the upper Table's width, whichever is greater (in case the former only contains "Loading...")
|
* - or - the upper Table's width, whichever is greater (in case the former only contains "Loading...")
|
||||||
*/
|
*/
|
||||||
fun getLowerWidth() = max(lowerTable.packIfNeeded().width, getUpperWidth()) //
|
fun getLowerWidth() = max(lowerTable.packIfNeeded().width, getUpperWidth())
|
||||||
|
|
||||||
fun addActorsToStage() {
|
fun addActorsToStage() {
|
||||||
cityScreen.stage.addActor(upperTable)
|
cityScreen.stage.addActor(upperTable)
|
||||||
@ -296,8 +297,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
|||||||
|
|
||||||
table.touchable = Touchable.enabled
|
table.touchable = Touchable.enabled
|
||||||
table.onClick {
|
table.onClick {
|
||||||
cityScreen.selectedConstruction = cityConstructions.getConstruction(constructionName)
|
cityScreen.selectConstruction(constructionName)
|
||||||
cityScreen.selectedTile = null
|
|
||||||
selectedQueueEntry = constructionQueueIndex
|
selectedQueueEntry = constructionQueueIndex
|
||||||
cityScreen.update()
|
cityScreen.update()
|
||||||
}
|
}
|
||||||
@ -326,7 +326,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
|||||||
pickConstructionButton.background = ImageGetter.getBackground(Color.BLACK)
|
pickConstructionButton.background = ImageGetter.getBackground(Color.BLACK)
|
||||||
pickConstructionButton.touchable = Touchable.enabled
|
pickConstructionButton.touchable = Touchable.enabled
|
||||||
|
|
||||||
if (!isSelectedQueueEntry() && cityScreen.selectedConstruction != null && cityScreen.selectedConstruction == construction) {
|
if (!isSelectedQueueEntry() && cityScreen.selectedConstruction == construction) {
|
||||||
pickConstructionButton.background = ImageGetter.getBackground(Color.GREEN.darken(0.5f))
|
pickConstructionButton.background = ImageGetter.getBackground(Color.GREEN.darken(0.5f))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,7 +336,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
|||||||
|
|
||||||
if (!cannotAddConstructionToQueue(construction, cityScreen.city, cityScreen.city.cityConstructions)) {
|
if (!cannotAddConstructionToQueue(construction, cityScreen.city, cityScreen.city.cityConstructions)) {
|
||||||
val addToQueueButton = ImageGetter.getImage("OtherIcons/New").apply { color = Color.BLACK }.surroundWithCircle(40f)
|
val addToQueueButton = ImageGetter.getImage("OtherIcons/New").apply { color = Color.BLACK }.surroundWithCircle(40f)
|
||||||
addToQueueButton.onClick(getConstructionSound(construction)) {
|
addToQueueButton.onClick(UncivSound.Silent) {
|
||||||
addConstructionToQueue(construction, cityScreen.city.cityConstructions)
|
addConstructionToQueue(construction, cityScreen.city.cityConstructions)
|
||||||
}
|
}
|
||||||
pickConstructionButton.add(addToQueueButton)
|
pickConstructionButton.add(addToQueueButton)
|
||||||
@ -350,8 +350,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
|||||||
.colspan(pickConstructionButton.columns).fillX().left().padTop(2f)
|
.colspan(pickConstructionButton.columns).fillX().left().padTop(2f)
|
||||||
}
|
}
|
||||||
pickConstructionButton.onClick {
|
pickConstructionButton.onClick {
|
||||||
cityScreen.selectedConstruction = construction
|
cityScreen.selectConstruction(construction)
|
||||||
cityScreen.selectedTile = null
|
|
||||||
selectedQueueEntry = -1
|
selectedQueueEntry = -1
|
||||||
cityScreen.update()
|
cityScreen.update()
|
||||||
}
|
}
|
||||||
@ -381,7 +380,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
|||||||
else {
|
else {
|
||||||
button.onClick {
|
button.onClick {
|
||||||
cityConstructions.removeFromQueue(selectedQueueEntry, false)
|
cityConstructions.removeFromQueue(selectedQueueEntry, false)
|
||||||
cityScreen.selectedConstruction = null
|
cityScreen.clearSelection()
|
||||||
selectedQueueEntry = -1
|
selectedQueueEntry = -1
|
||||||
cityScreen.update()
|
cityScreen.update()
|
||||||
}
|
}
|
||||||
@ -392,7 +391,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
|||||||
|| cannotAddConstructionToQueue(construction, city, cityConstructions)) {
|
|| cannotAddConstructionToQueue(construction, city, cityConstructions)) {
|
||||||
button.disable()
|
button.disable()
|
||||||
} else {
|
} else {
|
||||||
button.onClick(getConstructionSound(construction)) {
|
button.onClick(UncivSound.Silent) {
|
||||||
addConstructionToQueue(construction, cityConstructions)
|
addConstructionToQueue(construction, cityConstructions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -404,17 +403,21 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
|||||||
|
|
||||||
private fun addConstructionToQueue(construction: IConstruction, cityConstructions: CityConstructions) {
|
private fun addConstructionToQueue(construction: IConstruction, cityConstructions: CityConstructions) {
|
||||||
// Some evil person decided to double tap real fast - #4977
|
// Some evil person decided to double tap real fast - #4977
|
||||||
if (cannotAddConstructionToQueue(construction, cityScreen.city, cityScreen.city.cityConstructions))
|
if (cannotAddConstructionToQueue(construction, cityScreen.city, cityConstructions))
|
||||||
return
|
return
|
||||||
if (construction is Building && construction.uniqueObjects.any { it.placeholderText == "Creates a [] improvement on a specific tile" }) {
|
|
||||||
cityScreen.selectedTile
|
// UniqueType.CreatesOneImprovement support - don't add yet, postpone until target tile for the improvement is selected
|
||||||
improvementBuildingToConstruct = construction
|
if (construction is Building && construction.hasCreateOneImprovementUnique()) {
|
||||||
|
cityScreen.startPickTileForCreatesOneImprovement(construction, Stat.Gold, false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
cityScreen.stopPickTileForCreatesOneImprovement()
|
||||||
|
|
||||||
|
Sounds.play(getConstructionSound(construction))
|
||||||
|
|
||||||
cityConstructions.addToQueue(construction.name)
|
cityConstructions.addToQueue(construction.name)
|
||||||
if (!construction.shouldBeDisplayed(cityConstructions)) // For buildings - unlike units which can be queued multiple times
|
if (!construction.shouldBeDisplayed(cityConstructions)) // For buildings - unlike units which can be queued multiple times
|
||||||
cityScreen.selectedConstruction = null
|
cityScreen.clearSelection()
|
||||||
cityScreen.update()
|
cityScreen.update()
|
||||||
cityScreen.game.settings.addCompletedTutorialTask("Pick construction")
|
cityScreen.game.settings.addCompletedTutorialTask("Pick construction")
|
||||||
}
|
}
|
||||||
@ -454,7 +457,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
|||||||
|
|
||||||
button.onClick {
|
button.onClick {
|
||||||
button.disable()
|
button.disable()
|
||||||
askToBuyConstruction(construction, stat)
|
buyButtonOnClick(construction, stat)
|
||||||
}
|
}
|
||||||
button.isEnabled = isConstructionPurchaseAllowed(construction, stat, constructionBuyCost)
|
button.isEnabled = isConstructionPurchaseAllowed(construction, stat, constructionBuyCost)
|
||||||
button.addTooltip('B') // The key binding is done in CityScreen constructor
|
button.addTooltip('B') // The key binding is done in CityScreen constructor
|
||||||
@ -466,12 +469,28 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
|||||||
return button
|
return button
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buyButtonOnClick(construction: INonPerpetualConstruction, stat: Stat = preferredBuyStat) {
|
||||||
|
if (construction !is Building || !construction.hasCreateOneImprovementUnique())
|
||||||
|
return askToBuyConstruction(construction, stat)
|
||||||
|
if (selectedQueueEntry < 0)
|
||||||
|
return cityScreen.startPickTileForCreatesOneImprovement(construction, stat, true)
|
||||||
|
// Buying a UniqueType.CreatesOneImprovement building from queue must pass down
|
||||||
|
// the already selected tile, otherwise a new one is chosen from Automation code.
|
||||||
|
val improvement = construction.getImprovementToCreate(cityScreen.city.getRuleset())!!
|
||||||
|
val tileForImprovement = cityScreen.city.cityConstructions.getTileForImprovement(improvement.name)
|
||||||
|
askToBuyConstruction(construction, stat, tileForImprovement)
|
||||||
|
}
|
||||||
|
|
||||||
/** Ask whether user wants to buy [construction] for [stat].
|
/** Ask whether user wants to buy [construction] for [stat].
|
||||||
*
|
*
|
||||||
* Used from onClick and keyboard dispatch, thus only minimal parameters are passed,
|
* Used from onClick and keyboard dispatch, thus only minimal parameters are passed,
|
||||||
* and it needs to do all checks and the sound as appropriate.
|
* and it needs to do all checks and the sound as appropriate.
|
||||||
*/
|
*/
|
||||||
fun askToBuyConstruction(construction: INonPerpetualConstruction, stat: Stat = preferredBuyStat) {
|
fun askToBuyConstruction(
|
||||||
|
construction: INonPerpetualConstruction,
|
||||||
|
stat: Stat = preferredBuyStat,
|
||||||
|
tile: TileInfo? = null
|
||||||
|
) {
|
||||||
if (!isConstructionPurchaseShown(construction, stat)) return
|
if (!isConstructionPurchaseShown(construction, stat)) return
|
||||||
val city = cityScreen.city
|
val city = cityScreen.city
|
||||||
val constructionBuyCost = construction.getStatBuyCost(city, stat)!!
|
val constructionBuyCost = construction.getStatBuyCost(city, stat)!!
|
||||||
@ -483,7 +502,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
|||||||
"Would you like to purchase [${construction.name}] for [$constructionBuyCost] [${stat.character}]?".tr()
|
"Would you like to purchase [${construction.name}] for [$constructionBuyCost] [${stat.character}]?".tr()
|
||||||
YesNoPopup(
|
YesNoPopup(
|
||||||
purchasePrompt,
|
purchasePrompt,
|
||||||
action = { purchaseConstruction(construction, stat) },
|
action = { purchaseConstruction(construction, stat, tile) },
|
||||||
screen = cityScreen,
|
screen = cityScreen,
|
||||||
restoreDefault = { cityScreen.update() }
|
restoreDefault = { cityScreen.update() }
|
||||||
).open()
|
).open()
|
||||||
@ -510,11 +529,17 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// called only by askToBuyConstruction's Yes answer
|
/** Called only by askToBuyConstruction's Yes answer - not to be confused with [CityConstructions.purchaseConstruction]
|
||||||
private fun purchaseConstruction(construction: INonPerpetualConstruction, stat: Stat = Stat.Gold) {
|
* @param tile supports [UniqueType.CreatesOneImprovement]
|
||||||
|
*/
|
||||||
|
private fun purchaseConstruction(
|
||||||
|
construction: INonPerpetualConstruction,
|
||||||
|
stat: Stat = Stat.Gold,
|
||||||
|
tile: TileInfo? = null
|
||||||
|
) {
|
||||||
Sounds.play(stat.purchaseSound)
|
Sounds.play(stat.purchaseSound)
|
||||||
val city = cityScreen.city
|
val city = cityScreen.city
|
||||||
if (!city.cityConstructions.purchaseConstruction(construction.name, selectedQueueEntry, false, stat)) {
|
if (!city.cityConstructions.purchaseConstruction(construction.name, selectedQueueEntry, false, stat, tile)) {
|
||||||
Popup(cityScreen).apply {
|
Popup(cityScreen).apply {
|
||||||
add("No space available to place [${construction.name}] near [${city.name}]".tr()).row()
|
add("No space available to place [${construction.name}] near [${city.name}]".tr()).row()
|
||||||
addCloseButton()
|
addCloseButton()
|
||||||
@ -524,12 +549,15 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
|||||||
}
|
}
|
||||||
if (isSelectedQueueEntry() || cityScreen.selectedConstruction?.isBuildable(city.cityConstructions) != true) {
|
if (isSelectedQueueEntry() || cityScreen.selectedConstruction?.isBuildable(city.cityConstructions) != true) {
|
||||||
selectedQueueEntry = -1
|
selectedQueueEntry = -1
|
||||||
cityScreen.selectedConstruction = null
|
cityScreen.clearSelection()
|
||||||
|
|
||||||
// Allow buying next queued or auto-assigned construction right away
|
// Allow buying next queued or auto-assigned construction right away
|
||||||
city.cityConstructions.chooseNextConstruction()
|
city.cityConstructions.chooseNextConstruction()
|
||||||
if (city.cityConstructions.currentConstructionFromQueue.isNotEmpty())
|
if (city.cityConstructions.currentConstructionFromQueue.isNotEmpty()) {
|
||||||
cityScreen.selectedConstruction = city.cityConstructions.getCurrentConstruction().takeIf { it is INonPerpetualConstruction }
|
val newConstruction = city.cityConstructions.getCurrentConstruction()
|
||||||
|
if (newConstruction is INonPerpetualConstruction)
|
||||||
|
cityScreen.selectConstruction(newConstruction)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cityScreen.update()
|
cityScreen.update()
|
||||||
}
|
}
|
||||||
@ -542,8 +570,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
|||||||
tab.onClick {
|
tab.onClick {
|
||||||
tab.touchable = Touchable.disabled
|
tab.touchable = Touchable.disabled
|
||||||
city.cityConstructions.raisePriority(constructionQueueIndex)
|
city.cityConstructions.raisePriority(constructionQueueIndex)
|
||||||
cityScreen.selectedConstruction = cityScreen.city.cityConstructions.getConstruction(name)
|
cityScreen.selectConstruction(name)
|
||||||
cityScreen.selectedTile = null
|
|
||||||
selectedQueueEntry = constructionQueueIndex - 1
|
selectedQueueEntry = constructionQueueIndex - 1
|
||||||
cityScreen.update()
|
cityScreen.update()
|
||||||
}
|
}
|
||||||
@ -559,8 +586,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
|||||||
tab.onClick {
|
tab.onClick {
|
||||||
tab.touchable = Touchable.disabled
|
tab.touchable = Touchable.disabled
|
||||||
city.cityConstructions.lowerPriority(constructionQueueIndex)
|
city.cityConstructions.lowerPriority(constructionQueueIndex)
|
||||||
cityScreen.selectedConstruction = cityScreen.city.cityConstructions.getConstruction(name)
|
cityScreen.selectConstruction(name)
|
||||||
cityScreen.selectedTile = null
|
|
||||||
selectedQueueEntry = constructionQueueIndex + 1
|
selectedQueueEntry = constructionQueueIndex + 1
|
||||||
cityScreen.update()
|
cityScreen.update()
|
||||||
}
|
}
|
||||||
@ -576,7 +602,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
|||||||
tab.onClick {
|
tab.onClick {
|
||||||
tab.touchable = Touchable.disabled
|
tab.touchable = Touchable.disabled
|
||||||
city.cityConstructions.removeFromQueue(constructionQueueIndex, false)
|
city.cityConstructions.removeFromQueue(constructionQueueIndex, false)
|
||||||
cityScreen.selectedConstruction = null
|
cityScreen.clearSelection()
|
||||||
cityScreen.update()
|
cityScreen.update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,20 +6,27 @@ import com.badlogic.gdx.scenes.scene2d.ui.Image
|
|||||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||||
import com.badlogic.gdx.utils.Align
|
import com.badlogic.gdx.utils.Align
|
||||||
import com.unciv.UncivGame
|
import com.unciv.UncivGame
|
||||||
|
import com.unciv.logic.automation.Automation
|
||||||
import com.unciv.logic.city.CityInfo
|
import com.unciv.logic.city.CityInfo
|
||||||
import com.unciv.logic.city.IConstruction
|
import com.unciv.logic.city.IConstruction
|
||||||
import com.unciv.logic.city.INonPerpetualConstruction
|
import com.unciv.logic.city.INonPerpetualConstruction
|
||||||
import com.unciv.logic.map.TileInfo
|
import com.unciv.logic.map.TileInfo
|
||||||
|
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.ui.images.ImageGetter
|
import com.unciv.ui.images.ImageGetter
|
||||||
import com.unciv.ui.map.TileGroupMap
|
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.tilegroups.TileSetStrings
|
||||||
import com.unciv.ui.utils.*
|
import com.unciv.ui.utils.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class CityScreen(
|
class CityScreen(
|
||||||
internal val city: CityInfo,
|
internal val city: CityInfo,
|
||||||
var selectedConstruction: IConstruction? = null,
|
initSelectedConstruction: IConstruction? = null,
|
||||||
var selectedTile: TileInfo? = null
|
initSelectedTile: TileInfo? = null
|
||||||
): BaseScreen() {
|
): BaseScreen() {
|
||||||
companion object {
|
companion object {
|
||||||
/** Distance from stage edges to floating widgets */
|
/** Distance from stage edges to floating widgets */
|
||||||
@ -73,6 +80,27 @@ class CityScreen(
|
|||||||
/** The ScrollPane for the background map view of the city surroundings */
|
/** The ScrollPane for the background map view of the city surroundings */
|
||||||
private val mapScrollPane = ZoomableScrollPane()
|
private val mapScrollPane = ZoomableScrollPane()
|
||||||
|
|
||||||
|
/** Support for [UniqueType.CreatesOneImprovement] - need user to pick a tile */
|
||||||
|
class PickTileForImprovementData (
|
||||||
|
val building: Building,
|
||||||
|
val improvement: TileImprovement,
|
||||||
|
val isBuying: Boolean,
|
||||||
|
val buyStat: Stat
|
||||||
|
)
|
||||||
|
|
||||||
|
// The following fields control what the user selects
|
||||||
|
var selectedConstruction: IConstruction? = initSelectedConstruction
|
||||||
|
private set
|
||||||
|
var selectedTile: TileInfo? = initSelectedTile
|
||||||
|
private set
|
||||||
|
/** If set, we are waiting for the user to pick a tile for [UniqueType.CreatesOneImprovement] */
|
||||||
|
var pickTileData: PickTileForImprovementData? = null
|
||||||
|
/** A [Building] with [UniqueType.CreatesOneImprovement] has been selected _in the queue_: show the tile it will place the improvement on */
|
||||||
|
private var selectedQueueEntryTargetTile: TileInfo? = null
|
||||||
|
/** Cached city.expansion.chooseNewTileToOwn() */
|
||||||
|
// val should be OK as buying tiles is what changes this, and that would re-create the whole CityScreen
|
||||||
|
private val nextTileToOwn = city.expansion.chooseNewTileToOwn()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
onBackButtonClicked { game.setWorldScreen() }
|
onBackButtonClicked { game.setWorldScreen() }
|
||||||
UncivGame.Current.settings.addCompletedTutorialTask("Enter city screen")
|
UncivGame.Current.settings.addCompletedTutorialTask("Enter city screen")
|
||||||
@ -160,21 +188,38 @@ class CityScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateTileGroups() {
|
private fun updateTileGroups() {
|
||||||
val nextTile = city.expansion.chooseNewTileToOwn()
|
fun isExistingImprovementValuable(tileInfo: TileInfo, improvementToPlace: TileImprovement): Boolean {
|
||||||
|
if (tileInfo.improvement == null) return false
|
||||||
|
val civInfo = city.civInfo
|
||||||
|
val existingStats = tileInfo.getImprovementStats(tileInfo.getTileImprovement()!!, civInfo, city)
|
||||||
|
val replacingStats = tileInfo.getImprovementStats(improvementToPlace, civInfo, city)
|
||||||
|
return Automation.rankStatsValue(existingStats, civInfo) > Automation.rankStatsValue(replacingStats, civInfo)
|
||||||
|
}
|
||||||
|
fun getPickImprovementColor(tileInfo: TileInfo): Pair<Color, Float> {
|
||||||
|
val improvementToPlace = pickTileData!!.improvement
|
||||||
|
return when {
|
||||||
|
tileInfo.isMarkedForCreatesOneImprovement() -> Color.BROWN to 0.7f
|
||||||
|
!tileInfo.canBuildImprovement(improvementToPlace, city.civInfo) -> Color.RED to 0.4f
|
||||||
|
isExistingImprovementValuable(tileInfo, improvementToPlace) -> Color.ORANGE to 0.5f
|
||||||
|
tileInfo.improvement != null -> Color.YELLOW to 0.6f
|
||||||
|
tileInfo.turnsToImprovement > 0 -> Color.YELLOW to 0.6f
|
||||||
|
else -> Color.GREEN to 0.5f
|
||||||
|
}
|
||||||
|
}
|
||||||
for (tileGroup in tileGroups) {
|
for (tileGroup in tileGroups) {
|
||||||
tileGroup.update()
|
tileGroup.update()
|
||||||
tileGroup.hideHighlight()
|
tileGroup.hideHighlight()
|
||||||
if (city.tiles.contains(tileGroup.tileInfo.position)
|
when {
|
||||||
&& constructionsTable.improvementBuildingToConstruct != null) {
|
tileGroup.tileInfo == nextTileToOwn -> {
|
||||||
val improvement = constructionsTable.improvementBuildingToConstruct!!.getImprovement(city.getRuleset())!!
|
|
||||||
if (tileGroup.tileInfo.canBuildImprovement(improvement, city.civInfo))
|
|
||||||
tileGroup.showHighlight(Color.GREEN)
|
|
||||||
else tileGroup.showHighlight(Color.RED)
|
|
||||||
}
|
|
||||||
if (tileGroup.tileInfo == nextTile) {
|
|
||||||
tileGroup.showHighlight(Color.PURPLE)
|
tileGroup.showHighlight(Color.PURPLE)
|
||||||
tileGroup.setColor(0f, 0f, 0f, 0.7f)
|
tileGroup.setColor(0f, 0f, 0f, 0.7f)
|
||||||
}
|
}
|
||||||
|
/** Support for [UniqueType.CreatesOneImprovement] */
|
||||||
|
tileGroup.tileInfo == selectedQueueEntryTargetTile ->
|
||||||
|
tileGroup.showHighlight(Color.BROWN, 0.7f)
|
||||||
|
pickTileData != null && city.tiles.contains(tileGroup.tileInfo.position) ->
|
||||||
|
getPickImprovementColor(tileGroup.tileInfo).run { tileGroup.showHighlight(first, second) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,50 +280,20 @@ class CityScreen(
|
|||||||
|
|
||||||
val tileSetStrings = TileSetStrings()
|
val tileSetStrings = TileSetStrings()
|
||||||
val cityTileGroups = cityInfo.getCenterTile().getTilesInDistance(5)
|
val cityTileGroups = cityInfo.getCenterTile().getTilesInDistance(5)
|
||||||
.filter { city.civInfo.exploredTiles.contains(it.position) }
|
.filter { cityInfo.civInfo.exploredTiles.contains(it.position) }
|
||||||
.map { CityTileGroup(cityInfo, it, tileSetStrings) }
|
.map { CityTileGroup(cityInfo, it, tileSetStrings) }
|
||||||
|
|
||||||
for (tileGroup in cityTileGroups) {
|
for (tileGroup in cityTileGroups) {
|
||||||
val tileInfo = tileGroup.tileInfo
|
|
||||||
|
|
||||||
tileGroup.onClick {
|
tileGroup.onClick {
|
||||||
if (city.isPuppet) return@onClick
|
tileGroupOnClick(tileGroup, cityInfo)
|
||||||
|
|
||||||
if (constructionsTable.improvementBuildingToConstruct != null) {
|
|
||||||
val improvement = constructionsTable.improvementBuildingToConstruct!!.getImprovement(city.getRuleset())!!
|
|
||||||
if (tileInfo.canBuildImprovement(improvement, cityInfo.civInfo)) {
|
|
||||||
tileInfo.improvementInProgress = improvement.name
|
|
||||||
tileInfo.turnsToImprovement = -1
|
|
||||||
constructionsTable.improvementBuildingToConstruct = null
|
|
||||||
cityInfo.cityConstructions.addToQueue(improvement.name)
|
|
||||||
update()
|
|
||||||
} else {
|
|
||||||
constructionsTable.improvementBuildingToConstruct = null
|
|
||||||
update()
|
|
||||||
}
|
}
|
||||||
return@onClick
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedTile = tileInfo
|
|
||||||
selectedConstruction = null
|
|
||||||
if (tileGroup.isWorkable && canChangeState) {
|
|
||||||
if (!tileInfo.providesYield() && city.population.getFreePopulation() > 0) {
|
|
||||||
city.workedTiles.add(tileInfo.position)
|
|
||||||
game.settings.addCompletedTutorialTask("Reassign worked tiles")
|
|
||||||
} else if (tileInfo.isWorked() && !tileInfo.isLocked())
|
|
||||||
city.workedTiles.remove(tileInfo.position)
|
|
||||||
city.cityStats.update()
|
|
||||||
}
|
|
||||||
update()
|
|
||||||
}
|
|
||||||
|
|
||||||
tileGroups.add(tileGroup)
|
tileGroups.add(tileGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
val tilesToUnwrap = ArrayList<CityTileGroup>()
|
val tilesToUnwrap = ArrayList<CityTileGroup>()
|
||||||
for (tileGroup in tileGroups) {
|
for (tileGroup in tileGroups) {
|
||||||
val xDifference = city.getCenterTile().position.x - tileGroup.tileInfo.position.x
|
val xDifference = cityInfo.getCenterTile().position.x - tileGroup.tileInfo.position.x
|
||||||
val yDifference = city.getCenterTile().position.y - tileGroup.tileInfo.position.y
|
val yDifference = cityInfo.getCenterTile().position.y - tileGroup.tileInfo.position.y
|
||||||
//if difference is bigger than 5 the tileGroup we are looking for is on the other side of the map
|
//if difference is bigger than 5 the tileGroup we are looking for is on the other side of the map
|
||||||
if (xDifference > 5 || xDifference < -5 || yDifference > 5 || yDifference < -5) {
|
if (xDifference > 5 || xDifference < -5 || yDifference > 5 || yDifference < -5) {
|
||||||
//so we want to unwrap its position
|
//so we want to unwrap its position
|
||||||
@ -299,6 +314,78 @@ class CityScreen(
|
|||||||
mapScrollPane.updateVisualScroll()
|
mapScrollPane.updateVisualScroll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun tileGroupOnClick(tileGroup: CityTileGroup, cityInfo: CityInfo) {
|
||||||
|
if (cityInfo.isPuppet) return
|
||||||
|
val tileInfo = tileGroup.tileInfo
|
||||||
|
|
||||||
|
/** [UniqueType.CreatesOneImprovement] support - select tile for improvement */
|
||||||
|
if (pickTileData != null) {
|
||||||
|
val pickTileData = this.pickTileData!!
|
||||||
|
this.pickTileData = null
|
||||||
|
val improvement = pickTileData.improvement
|
||||||
|
if (tileInfo.canBuildImprovement(improvement, cityInfo.civInfo)) {
|
||||||
|
if (pickTileData.isBuying) {
|
||||||
|
constructionsTable.askToBuyConstruction(pickTileData.building, pickTileData.buyStat, tileInfo)
|
||||||
|
} else {
|
||||||
|
// This way to store where the improvement a CreatesOneImprovement Building will create goes
|
||||||
|
// might get a bit fragile if several buildings constructing the same improvement type
|
||||||
|
// were to be allowed in the queue - or a little nontransparent to the user why they
|
||||||
|
// won't reorder - maybe one day redesign to have the target tiles attached to queue entries.
|
||||||
|
tileInfo.markForCreatesOneImprovement(improvement.name)
|
||||||
|
cityInfo.cityConstructions.addToQueue(pickTileData.building.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
selectTile(tileInfo)
|
||||||
|
if (tileGroup.isWorkable && canChangeState) {
|
||||||
|
if (!tileInfo.providesYield() && cityInfo.population.getFreePopulation() > 0) {
|
||||||
|
cityInfo.workedTiles.add(tileInfo.position)
|
||||||
|
game.settings.addCompletedTutorialTask("Reassign worked tiles")
|
||||||
|
} else if (tileInfo.isWorked() && !tileInfo.isLocked())
|
||||||
|
cityInfo.workedTiles.remove(tileInfo.position)
|
||||||
|
cityInfo.cityStats.update()
|
||||||
|
}
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectConstruction(name: String) {
|
||||||
|
selectConstruction(city.cityConstructions.getConstruction(name))
|
||||||
|
}
|
||||||
|
fun selectConstruction(newConstruction: IConstruction) {
|
||||||
|
selectedConstruction = newConstruction
|
||||||
|
if (newConstruction is Building && newConstruction.hasCreateOneImprovementUnique()) {
|
||||||
|
val improvement = newConstruction.getImprovementToCreate(city.getRuleset())
|
||||||
|
selectedQueueEntryTargetTile = if (improvement == null) null
|
||||||
|
else city.cityConstructions.getTileForImprovement(improvement.name)
|
||||||
|
} else {
|
||||||
|
selectedQueueEntryTargetTile = null
|
||||||
|
pickTileData = null
|
||||||
|
}
|
||||||
|
selectedTile = null
|
||||||
|
}
|
||||||
|
private fun selectTile(newTile: TileInfo?) {
|
||||||
|
selectedConstruction = null
|
||||||
|
selectedQueueEntryTargetTile = null
|
||||||
|
pickTileData = null
|
||||||
|
selectedTile = newTile
|
||||||
|
}
|
||||||
|
fun clearSelection() = selectTile(null)
|
||||||
|
|
||||||
|
fun startPickTileForCreatesOneImprovement(construction: Building, stat: Stat, isBuying: Boolean) {
|
||||||
|
val improvement = construction.getImprovementToCreate(city.getRuleset()) ?: return
|
||||||
|
pickTileData = PickTileForImprovementData(construction, improvement, isBuying, stat)
|
||||||
|
updateTileGroups()
|
||||||
|
ToastPopup("Please select a tile for this building's [${improvement.name}]", this)
|
||||||
|
}
|
||||||
|
fun stopPickTileForCreatesOneImprovement() {
|
||||||
|
if (pickTileData == null) return
|
||||||
|
pickTileData = null
|
||||||
|
updateTileGroups()
|
||||||
|
}
|
||||||
|
|
||||||
fun exit() {
|
fun exit() {
|
||||||
game.setWorldScreen()
|
game.setWorldScreen()
|
||||||
game.worldScreen.mapHolder.setCenterPosition(city.location)
|
game.worldScreen.mapHolder.setCenterPosition(city.location)
|
||||||
|
@ -111,7 +111,7 @@ class CityScreenTileTable(private val cityScreen: CityScreen): Table() {
|
|||||||
Sounds.play(UncivSound.Coin)
|
Sounds.play(UncivSound.Coin)
|
||||||
city.expansion.buyTile(selectedTile)
|
city.expansion.buyTile(selectedTile)
|
||||||
// preselect the next tile on city screen rebuild so bulk buying can go faster
|
// preselect the next tile on city screen rebuild so bulk buying can go faster
|
||||||
UncivGame.Current.setScreen(CityScreen(city, selectedTile = city.expansion.chooseNewTileToOwn()))
|
UncivGame.Current.setScreen(CityScreen(city, initSelectedTile = city.expansion.chooseNewTileToOwn()))
|
||||||
},
|
},
|
||||||
screen = cityScreen,
|
screen = cityScreen,
|
||||||
restoreDefault = { cityScreen.update() }
|
restoreDefault = { cityScreen.update() }
|
||||||
|
@ -26,12 +26,14 @@ class ImprovementPickerScreen(
|
|||||||
private val gameInfo = tileInfo.tileMap.gameInfo
|
private val gameInfo = tileInfo.tileMap.gameInfo
|
||||||
private val ruleSet = gameInfo.ruleSet
|
private val ruleSet = gameInfo.ruleSet
|
||||||
private val currentPlayerCiv = gameInfo.getCurrentPlayerCivilization()
|
private val currentPlayerCiv = gameInfo.getCurrentPlayerCivilization()
|
||||||
|
// Support for UniqueType.CreatesOneImprovement
|
||||||
|
private val tileMarkedForCreatesOneImprovement = tileInfo.isMarkedForCreatesOneImprovement()
|
||||||
|
|
||||||
private fun getRequiredTechColumn(improvement: TileImprovement) =
|
private fun getRequiredTechColumn(improvement: TileImprovement) =
|
||||||
ruleSet.technologies[improvement.techRequired]?.column?.columnNumber ?: -1
|
ruleSet.technologies[improvement.techRequired]?.column?.columnNumber ?: -1
|
||||||
|
|
||||||
fun accept(improvement: TileImprovement?) {
|
fun accept(improvement: TileImprovement?) {
|
||||||
if (improvement == null) return
|
if (improvement == null || tileMarkedForCreatesOneImprovement) return
|
||||||
if (improvement.name == Constants.cancelImprovementOrder) {
|
if (improvement.name == Constants.cancelImprovementOrder) {
|
||||||
tileInfo.stopWorkingOnImprovement()
|
tileInfo.stopWorkingOnImprovement()
|
||||||
// no onAccept() - Worker can stay selected
|
// no onAccept() - Worker can stay selected
|
||||||
@ -108,8 +110,9 @@ class ImprovementPickerScreen(
|
|||||||
|
|
||||||
val pickNow = when {
|
val pickNow = when {
|
||||||
suggestRemoval -> "${Constants.remove}[${tileInfo.getLastTerrain().name}] first".toLabel()
|
suggestRemoval -> "${Constants.remove}[${tileInfo.getLastTerrain().name}] first".toLabel()
|
||||||
tileInfo.improvementInProgress != improvement.name -> "Pick now!".toLabel().onClick { accept(improvement) }
|
tileInfo.improvementInProgress == improvement.name -> "Current construction".toLabel()
|
||||||
else -> "Current construction".toLabel()
|
tileMarkedForCreatesOneImprovement -> null
|
||||||
|
else -> "Pick now!".toLabel().onClick { accept(improvement) }
|
||||||
}
|
}
|
||||||
|
|
||||||
val statIcons = getStatIconsTable(provideResource, removeImprovement)
|
val statIcons = getStatIconsTable(provideResource, removeImprovement)
|
||||||
@ -126,7 +129,6 @@ class ImprovementPickerScreen(
|
|||||||
val statsTable = getStatsTable(stats)
|
val statsTable = getStatsTable(stats)
|
||||||
statIcons.add(statsTable).padLeft(13f)
|
statIcons.add(statsTable).padLeft(13f)
|
||||||
|
|
||||||
|
|
||||||
regularImprovements.add(statIcons).align(Align.right)
|
regularImprovements.add(statIcons).align(Align.right)
|
||||||
|
|
||||||
val improvementButton = getPickerOptionButton(image, labelText)
|
val improvementButton = getPickerOptionButton(image, labelText)
|
||||||
@ -137,16 +139,14 @@ class ImprovementPickerScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (improvement.name == tileInfo.improvementInProgress) improvementButton.color = Color.GREEN
|
if (improvement.name == tileInfo.improvementInProgress) improvementButton.color = Color.GREEN
|
||||||
if (suggestRemoval){
|
if (suggestRemoval || tileMarkedForCreatesOneImprovement) {
|
||||||
improvementButton.disable()
|
improvementButton.disable()
|
||||||
}
|
} else if (shortcutKey != null) {
|
||||||
regularImprovements.add(improvementButton)
|
|
||||||
|
|
||||||
if (shortcutKey != null) {
|
|
||||||
keyPressDispatcher[shortcutKey] = { accept(improvement) }
|
keyPressDispatcher[shortcutKey] = { accept(improvement) }
|
||||||
improvementButton.addTooltip(shortcutKey)
|
improvementButton.addTooltip(shortcutKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
regularImprovements.add(improvementButton)
|
||||||
regularImprovements.add(pickNow).padLeft(10f).fillY()
|
regularImprovements.add(pickNow).padLeft(10f).fillY()
|
||||||
regularImprovements.row()
|
regularImprovements.row()
|
||||||
}
|
}
|
||||||
|
@ -280,13 +280,7 @@ object UnitActions {
|
|||||||
|
|
||||||
return UnitAction(UnitActionType.Pillage,
|
return UnitAction(UnitActionType.Pillage,
|
||||||
action = {
|
action = {
|
||||||
// http://well-of-souls.com/civ/civ5_improvements.html says that naval improvements are destroyed upon pillage
|
tile.setPillaged()
|
||||||
// and I can't find any other sources so I'll go with that
|
|
||||||
if (tile.isLand) {
|
|
||||||
tile.improvementInProgress = tile.improvement
|
|
||||||
tile.turnsToImprovement = 2
|
|
||||||
}
|
|
||||||
tile.improvement = null
|
|
||||||
unit.civInfo.lastSeenImprovement.remove(tile.position)
|
unit.civInfo.lastSeenImprovement.remove(tile.position)
|
||||||
if (tile.resource != null) tile.getOwner()?.updateDetailedCivResources() // this might take away a resource
|
if (tile.resource != null) tile.getOwner()?.updateDetailedCivResources() // this might take away a resource
|
||||||
|
|
||||||
@ -408,7 +402,6 @@ object UnitActions {
|
|||||||
&& unit.canBuildImprovement(it)
|
&& unit.canBuildImprovement(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
actionList += UnitAction(UnitActionType.ConstructImprovement,
|
actionList += UnitAction(UnitActionType.ConstructImprovement,
|
||||||
isCurrentAction = unit.currentTile.hasImprovementInProgress(),
|
isCurrentAction = unit.currentTile.hasImprovementInProgress(),
|
||||||
action = {
|
action = {
|
||||||
@ -652,11 +645,10 @@ object UnitActions {
|
|||||||
val improvement = tile.ruleset.tileImprovements[improvementName]
|
val improvement = tile.ruleset.tileImprovements[improvementName]
|
||||||
?: continue
|
?: continue
|
||||||
|
|
||||||
var resourcesAvailable = true
|
val resourcesAvailable = improvement.uniqueObjects.none {
|
||||||
if (improvement.uniqueObjects.any {
|
it.isOfType(UniqueType.ConsumesResources) &&
|
||||||
it.isOfType(UniqueType.ConsumesResources) && civResources[unique.params[1]] ?: 0 < unique.params[0].toInt()
|
civResources[unique.params[1]] ?: 0 < unique.params[0].toInt()
|
||||||
})
|
}
|
||||||
resourcesAvailable = false
|
|
||||||
|
|
||||||
finalActions += UnitAction(UnitActionType.Create,
|
finalActions += UnitAction(UnitActionType.Create,
|
||||||
title = "Create [$improvementName]",
|
title = "Create [$improvementName]",
|
||||||
@ -670,10 +662,10 @@ object UnitActions {
|
|||||||
it in improvement.terrainsCanBeBuiltOn
|
it in improvement.terrainsCanBeBuiltOn
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
unitTile.removeCreatesOneImprovementMarker()
|
||||||
unitTile.improvement = improvementName
|
unitTile.improvement = improvementName
|
||||||
unitTile.improvementInProgress = null
|
unitTile.stopWorkingOnImprovement()
|
||||||
unitTile.turnsToImprovement = 0
|
if (improvement.hasUnique(UniqueType.TakeOverTilesAroundWhenBuilt))
|
||||||
if (improvementName == Constants.citadel)
|
|
||||||
takeOverTilesAround(unit)
|
takeOverTilesAround(unit)
|
||||||
val city = unitTile.getCity()
|
val city = unitTile.getCity()
|
||||||
if (city != null) {
|
if (city != null) {
|
||||||
@ -687,6 +679,9 @@ object UnitActions {
|
|||||||
resourcesAvailable
|
resourcesAvailable
|
||||||
&& unit.currentMovement > 0f
|
&& unit.currentMovement > 0f
|
||||||
&& tile.canBuildImprovement(improvement, unit.civInfo)
|
&& tile.canBuildImprovement(improvement, unit.civInfo)
|
||||||
|
// Next test is to prevent interfering with UniqueType.CreatesOneImprovement -
|
||||||
|
// not pretty, but users *can* remove the building from the city queue an thus clear this:
|
||||||
|
&& !tile.isMarkedForCreatesOneImprovement()
|
||||||
&& !tile.isImpassible() // Not 100% sure that this check is necessary...
|
&& !tile.isImpassible() // Not 100% sure that this check is necessary...
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user