From 8e3ebc772467b8528548ee261c405604e8727da0 Mon Sep 17 00:00:00 2001 From: SomeTroglodyte <63000004+SomeTroglodyte@users.noreply.github.com> Date: Wed, 13 Sep 2023 09:27:43 +0200 Subject: [PATCH] Global Constructions Blacklist (#10061) * Improved handling of "Nothing" construction * Suppress "All" entries when you have only one city * Global Construction Blacklist * Reviews * Reviews - missed renames --- .../jsons/translations/template.properties | 2 ++ .../automation/city/ConstructionAutomation.kt | 7 +++++ .../com/unciv/logic/city/CityConstructions.kt | 20 ++++++------ .../com/unciv/models/metadata/GameSettings.kt | 7 +++++ .../ui/components/input/KeyboardBinding.kt | 1 + .../ui/popups/CityScreenConstructionMenu.kt | 31 ++++++++++++++++--- .../cityscreen/CityConstructionsTable.kt | 17 ++++++++-- 7 files changed, 70 insertions(+), 15 deletions(-) diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index c40c660988..a117216708 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -1245,6 +1245,8 @@ Add to the top of the queue = Add to the queue in all cities = Add or move to the top in all cities = Remove from the queue in all cities = +Disable = +Enable = # Specialized Popups - Ask for text or numbers, file picker diff --git a/core/src/com/unciv/logic/automation/city/ConstructionAutomation.kt b/core/src/com/unciv/logic/automation/city/ConstructionAutomation.kt index 2c0294209b..08f96c6448 100644 --- a/core/src/com/unciv/logic/automation/city/ConstructionAutomation.kt +++ b/core/src/com/unciv/logic/automation/city/ConstructionAutomation.kt @@ -1,5 +1,6 @@ package com.unciv.logic.automation.city +import com.unciv.GUI import com.unciv.logic.automation.Automation import com.unciv.logic.automation.civilization.NextTurnAutomation import com.unciv.logic.city.CityConstructions @@ -24,9 +25,14 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ private val city = cityConstructions.city private val civInfo = city.civ + private val disabledAutoAssignConstructions: Set = + if (civInfo.isHuman()) GUI.getSettings().disabledAutoAssignConstructions + else emptySet() + private val buildableBuildings = hashMapOf() private val buildableUnits = hashMapOf() private val buildings = city.getRuleset().buildings.values.asSequence() + .filterNot { it.name in disabledAutoAssignConstructions } private val nonWonders = buildings.filterNot { it.isAnyWonder() } .filterNot { buildableBuildings[it.name] == false } // if we already know that this building can't be built here then don't even consider it @@ -35,6 +41,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ private val units = city.getRuleset().units.values.asSequence() .filterNot { buildableUnits[it.name] == false } // if we already know that this unit can't be built here then don't even consider it + .filterNot { it.name in disabledAutoAssignConstructions } private val civUnits = civInfo.units.getCivUnits() private val militaryUnits = civUnits.count { it.baseUnit.isMilitary() } diff --git a/core/src/com/unciv/logic/city/CityConstructions.kt b/core/src/com/unciv/logic/city/CityConstructions.kt index 83cf37e19b..49e21c5eb7 100644 --- a/core/src/com/unciv/logic/city/CityConstructions.kt +++ b/core/src/com/unciv/logic/city/CityConstructions.kt @@ -232,7 +232,7 @@ class CityConstructions : IsPartOfGameInfoSerialization { internal fun getConstruction(constructionName: String): IConstruction { val gameBasics = city.getRuleset() when { - constructionName == "" -> return getConstruction("Nothing") + constructionName == "" -> return PerpetualConstruction.idle gameBasics.buildings.containsKey(constructionName) -> return gameBasics.buildings[constructionName]!! gameBasics.units.containsKey(constructionName) -> return gameBasics.units[constructionName]!! else -> { @@ -735,10 +735,9 @@ class CityConstructions : IsPartOfGameInfoSerialization { fun chooseNextConstruction() { validateConstructionQueue() - if (constructionQueue.isNotEmpty()) { - if (currentConstructionFromQueue != "" - // If the USER set a perpetual construction, then keep it! - && (getConstruction(currentConstructionFromQueue) !is PerpetualConstruction || currentConstructionIsUserSet)) return + if (!isQueueEmptyOrIdle()) { + // If the USER set a perpetual construction, then keep it! + if (getConstruction(currentConstructionFromQueue) !is PerpetualConstruction || currentConstructionIsUserSet) return } val isCurrentPlayersTurn = city.civ.gameInfo.isUsersTurn() @@ -765,6 +764,9 @@ class CityConstructions : IsPartOfGameInfoSerialization { PerpetualConstruction.isNamePerpetual(constructionQueue.last()) // `getConstruction(constructionQueue.last()) is PerpetualConstruction` is clear but more expensive + fun isQueueEmptyOrIdle() = currentConstructionFromQueue.isEmpty() + || currentConstructionFromQueue == PerpetualConstruction.idle.name + /** Add [construction] to the end or top (controlled by [addToTop]) of the queue with all checks (does nothing if not possible) * * Note: Overload with string parameter `constructionName` exists as well. @@ -773,7 +775,7 @@ class CityConstructions : IsPartOfGameInfoSerialization { if (!canAddToQueue(construction)) return val constructionName = construction.name when { - currentConstructionFromQueue.isEmpty() || currentConstructionFromQueue == "Nothing" -> + isQueueEmptyOrIdle() -> currentConstructionFromQueue = constructionName addToTop && construction is PerpetualConstruction && PerpetualConstruction.isNamePerpetual(currentConstructionFromQueue) -> currentConstructionFromQueue = constructionName // perpetual constructions will replace each other @@ -817,7 +819,7 @@ class CityConstructions : IsPartOfGameInfoSerialization { currentConstructionIsUserSet = if (constructionQueue.isEmpty()) { if (automatic) chooseNextConstruction() - else constructionQueue.add("Nothing") // To prevent Construction Automation + else constructionQueue.add(PerpetualConstruction.idle.name) // To prevent Construction Automation false } else true // we're just continuing the regular queue } @@ -828,7 +830,7 @@ class CityConstructions : IsPartOfGameInfoSerialization { * If the queue is emptied, no automatic: getSettings().autoAssignCityProduction is ignored! (parameter to be added when needed) */ fun removeAllByName(constructionName: String) { - while (true) { + while (!isQueueEmptyOrIdle()) { val index = constructionQueue.indexOf(constructionName) if (index < 0) return removeFromQueue(index, false) @@ -904,7 +906,7 @@ class CityConstructions : IsPartOfGameInfoSerialization { constructionQueue.removeAt(indexToRemove) currentConstructionIsUserSet = if (constructionQueue.isEmpty()) { - constructionQueue.add("Nothing") + constructionQueue.add(PerpetualConstruction.idle.name) false } else true } diff --git a/core/src/com/unciv/models/metadata/GameSettings.kt b/core/src/com/unciv/models/metadata/GameSettings.kt index 033d3d0d30..0baf070438 100644 --- a/core/src/com/unciv/models/metadata/GameSettings.kt +++ b/core/src/com/unciv/models/metadata/GameSettings.kt @@ -68,6 +68,13 @@ class GameSettings { var skin: String = Constants.defaultSkin var showTutorials: Boolean = true var autoAssignCityProduction: Boolean = true + + /** This set of construction names has two effects: + * * Matching constructions are no longer candidates for [autoAssignCityProduction] + * * Matching constructions are offered in a separate 'Disabled' category in CityScreen + */ + var disabledAutoAssignConstructions = HashSet() + var autoBuildingRoads: Boolean = true var automatedWorkersReplaceImprovements = true var automatedUnitsMoveOnTurnStart: Boolean = false diff --git a/core/src/com/unciv/ui/components/input/KeyboardBinding.kt b/core/src/com/unciv/ui/components/input/KeyboardBinding.kt index 2c15aa0459..2e434409d6 100644 --- a/core/src/com/unciv/ui/components/input/KeyboardBinding.kt +++ b/core/src/com/unciv/ui/components/input/KeyboardBinding.kt @@ -136,6 +136,7 @@ enum class KeyboardBinding( BuildWonders(Category.CityScreen, "Buildable Wonders", 'w'), BuildNationalWonders(Category.CityScreen, "Buildable National Wonders", 'n'), BuildOther(Category.CityScreen, "Other Constructions", 'o'), + BuildDisabled(Category.CityScreen, "Disabled Constructions", KeyCharAndCode.ctrl('h')), NextCity(Category.CityScreen, Input.Keys.RIGHT), PreviousCity(Category.CityScreen, Input.Keys.LEFT), ShowStats(Category.CityScreen, 's'), diff --git a/core/src/com/unciv/ui/popups/CityScreenConstructionMenu.kt b/core/src/com/unciv/ui/popups/CityScreenConstructionMenu.kt index cdb508204f..029d598958 100644 --- a/core/src/com/unciv/ui/popups/CityScreenConstructionMenu.kt +++ b/core/src/com/unciv/ui/popups/CityScreenConstructionMenu.kt @@ -3,6 +3,7 @@ package com.unciv.ui.popups import com.badlogic.gdx.scenes.scene2d.Actor import com.badlogic.gdx.scenes.scene2d.Stage import com.badlogic.gdx.scenes.scene2d.ui.Table +import com.unciv.GUI import com.unciv.logic.city.City import com.unciv.logic.city.CityConstructions import com.unciv.models.ruleset.Building @@ -36,12 +37,17 @@ class CityScreenConstructionMenu( cityConstructions.constructionQueue .count { it !in PerpetualConstruction.perpetualConstructionsMap } private val myIndex = cityConstructions.constructionQueue.indexOf(constructionName) - private fun anyCity(predicate: (CityConstructions) -> Boolean) = + /** Check whether an "All cities" menu makes sense: `true` if there's more than one city, it's not a Wonder, and any city's queue matches [predicate]. */ + private fun allCitiesEntryValid(predicate: (CityConstructions) -> Boolean) = + city.civ.cities.size > 1 && (construction as? Building)?.isAnyWonder() != true && city.civ.cities.map { it.cityConstructions }.any(predicate) private fun forAllCities(action: (CityConstructions) -> Unit) = city.civ.cities.map { it.cityConstructions }.forEach(action) + private val settings = GUI.getSettings() + private val disabledAutoAssignConstructions = settings.disabledAutoAssignConstructions + init { closeListeners.add { if (anyButtonWasClicked) onButtonClicked() @@ -62,6 +68,10 @@ class CityScreenConstructionMenu( table.add(getButton("Add or move to the top in all cities", KeyboardBinding.AddConstructionAllTop, ::addAllQueuesTop)).row() if (canRemoveAllQueues()) table.add(getButton("Remove from the queue in all cities", KeyboardBinding.RemoveConstructionAll, ::removeAllQueues)).row() + if (canDisable()) + table.add(getButton("Disable", KeyboardBinding.BuildDisabled, ::disableEntry)).row() + if (canEnable()) + table.add(getButton("Enable", KeyboardBinding.BuildDisabled, ::enableEntry)).row() return table.takeUnless { it.cells.isEmpty } } @@ -83,7 +93,7 @@ class CityScreenConstructionMenu( cityConstructions.canAddToQueue(construction) private fun addQueueTop() = cityConstructions.addToQueue(construction, addToTop = true) - private fun canAddAllQueues() = anyCity { + private fun canAddAllQueues() = allCitiesEntryValid { it.canAddToQueue(construction) && // A Perpetual that is already queued can still be added says canAddToQueue, but here we don't want to count that !(construction is PerpetualConstruction && it.isBeingConstructedOrEnqueued(constructionName)) @@ -91,7 +101,7 @@ class CityScreenConstructionMenu( private fun addAllQueues() = forAllCities { it.addToQueue(construction) } private fun canAddAllQueuesTop() = construction !is PerpetualConstruction && - anyCity { it.canAddToQueue(construction) || it.isEnqueuedForLater(constructionName) } + allCitiesEntryValid { it.canAddToQueue(construction) || it.isEnqueuedForLater(constructionName) } private fun addAllQueuesTop() = forAllCities { val index = it.constructionQueue.indexOf(constructionName) if (index > 0) @@ -100,6 +110,19 @@ class CityScreenConstructionMenu( it.addToQueue(construction, true) } - private fun canRemoveAllQueues() = anyCity { it.isBeingConstructedOrEnqueued(constructionName) } + private fun canRemoveAllQueues() = allCitiesEntryValid { it.isBeingConstructedOrEnqueued(constructionName) } private fun removeAllQueues() = forAllCities { it.removeAllByName(constructionName) } + + private fun canDisable() = constructionName !in disabledAutoAssignConstructions && + construction != PerpetualConstruction.idle + private fun disableEntry() { + disabledAutoAssignConstructions.add(constructionName) + settings.save() + } + + private fun canEnable() = constructionName in disabledAutoAssignConstructions + private fun enableEntry() { + disabledAutoAssignConstructions.remove(constructionName) + settings.save() + } } diff --git a/core/src/com/unciv/ui/screens/cityscreen/CityConstructionsTable.kt b/core/src/com/unciv/ui/screens/cityscreen/CityConstructionsTable.kt index 371fe110ac..f2684789ce 100644 --- a/core/src/com/unciv/ui/screens/cityscreen/CityConstructionsTable.kt +++ b/core/src/com/unciv/ui/screens/cityscreen/CityConstructionsTable.kt @@ -8,6 +8,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.utils.Align import com.unciv.Constants +import com.unciv.GUI import com.unciv.logic.city.City import com.unciv.logic.city.CityConstructions import com.unciv.logic.map.tile.Tile @@ -255,6 +256,8 @@ class CityConstructionsTable(private val cityScreen: CityScreen) { val buildableNationalWonders = ArrayList() val buildableBuildings = ArrayList
() val specialConstructions = ArrayList
() + val blacklisted = ArrayList
() + val disabledAutoAssignConstructions: Set = GUI.getSettings().disabledAutoAssignConstructions var maxButtonWidth = constructionsQueueTable.width for (dto in constructionButtonDTOList) { @@ -268,7 +271,9 @@ class CityConstructionsTable(private val cityScreen: CityScreen) { continue val constructionButton = getConstructionButton(dto) - when (dto.construction) { + if (dto.construction.name in disabledAutoAssignConstructions) + blacklisted.add(constructionButton) + else when (dto.construction) { is BaseUnit -> units.add(constructionButton) is Building -> { when { @@ -290,6 +295,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) { addCategory("Wonders", buildableWonders, maxButtonWidth, KeyboardBinding.BuildWonders) addCategory("National Wonders", buildableNationalWonders, maxButtonWidth, KeyboardBinding.BuildNationalWonders) addCategory("Other", specialConstructions, maxButtonWidth, KeyboardBinding.BuildOther) + addCategory("Disabled", blacklisted, maxButtonWidth, KeyboardBinding.BuildDisabled, startsOutOpened = false) pack() } @@ -799,12 +805,19 @@ class CityConstructionsTable(private val cityScreen: CityScreen) { lowerTable.pack() } - private fun Table.addCategory(title: String, list: ArrayList
, prefWidth: Float, toggleKey: KeyboardBinding) { + private fun Table.addCategory( + title: String, + list: ArrayList
, + prefWidth: Float, + toggleKey: KeyboardBinding, + startsOutOpened: Boolean = true + ) { if (list.isEmpty()) return if (rows > 0) addSeparator() val expander = ExpanderTab( title, + startsOutOpened = startsOutOpened, defaultPad = 0f, expanderWidth = prefWidth, persistenceID = "CityConstruction.$title",