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
This commit is contained in:
SomeTroglodyte 2023-09-13 09:27:43 +02:00 committed by GitHub
parent 8aeae30050
commit 8e3ebc7724
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 70 additions and 15 deletions

View File

@ -1245,6 +1245,8 @@ Add to the top of the queue =
Add to the queue in all cities = Add to the queue in all cities =
Add or move to the top in all cities = Add or move to the top in all cities =
Remove from the queue in all cities = Remove from the queue in all cities =
Disable =
Enable =
# Specialized Popups - Ask for text or numbers, file picker # Specialized Popups - Ask for text or numbers, file picker

View File

@ -1,5 +1,6 @@
package com.unciv.logic.automation.city package com.unciv.logic.automation.city
import com.unciv.GUI
import com.unciv.logic.automation.Automation import com.unciv.logic.automation.Automation
import com.unciv.logic.automation.civilization.NextTurnAutomation import com.unciv.logic.automation.civilization.NextTurnAutomation
import com.unciv.logic.city.CityConstructions import com.unciv.logic.city.CityConstructions
@ -24,9 +25,14 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
private val city = cityConstructions.city private val city = cityConstructions.city
private val civInfo = city.civ private val civInfo = city.civ
private val disabledAutoAssignConstructions: Set<String> =
if (civInfo.isHuman()) GUI.getSettings().disabledAutoAssignConstructions
else emptySet()
private val buildableBuildings = hashMapOf<String, Boolean>() private val buildableBuildings = hashMapOf<String, Boolean>()
private val buildableUnits = hashMapOf<String, Boolean>() private val buildableUnits = hashMapOf<String, Boolean>()
private val buildings = city.getRuleset().buildings.values.asSequence() private val buildings = city.getRuleset().buildings.values.asSequence()
.filterNot { it.name in disabledAutoAssignConstructions }
private val nonWonders = buildings.filterNot { it.isAnyWonder() } 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 .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() 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 { 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 civUnits = civInfo.units.getCivUnits()
private val militaryUnits = civUnits.count { it.baseUnit.isMilitary() } private val militaryUnits = civUnits.count { it.baseUnit.isMilitary() }

View File

@ -232,7 +232,7 @@ class CityConstructions : IsPartOfGameInfoSerialization {
internal fun getConstruction(constructionName: String): IConstruction { internal fun getConstruction(constructionName: String): IConstruction {
val gameBasics = city.getRuleset() val gameBasics = city.getRuleset()
when { when {
constructionName == "" -> return getConstruction("Nothing") constructionName == "" -> return PerpetualConstruction.idle
gameBasics.buildings.containsKey(constructionName) -> return gameBasics.buildings[constructionName]!! gameBasics.buildings.containsKey(constructionName) -> return gameBasics.buildings[constructionName]!!
gameBasics.units.containsKey(constructionName) -> return gameBasics.units[constructionName]!! gameBasics.units.containsKey(constructionName) -> return gameBasics.units[constructionName]!!
else -> { else -> {
@ -735,10 +735,9 @@ class CityConstructions : IsPartOfGameInfoSerialization {
fun chooseNextConstruction() { fun chooseNextConstruction() {
validateConstructionQueue() validateConstructionQueue()
if (constructionQueue.isNotEmpty()) { if (!isQueueEmptyOrIdle()) {
if (currentConstructionFromQueue != "" // If the USER set a perpetual construction, then keep it!
// If the USER set a perpetual construction, then keep it! if (getConstruction(currentConstructionFromQueue) !is PerpetualConstruction || currentConstructionIsUserSet) return
&& (getConstruction(currentConstructionFromQueue) !is PerpetualConstruction || currentConstructionIsUserSet)) return
} }
val isCurrentPlayersTurn = city.civ.gameInfo.isUsersTurn() val isCurrentPlayersTurn = city.civ.gameInfo.isUsersTurn()
@ -765,6 +764,9 @@ class CityConstructions : IsPartOfGameInfoSerialization {
PerpetualConstruction.isNamePerpetual(constructionQueue.last()) PerpetualConstruction.isNamePerpetual(constructionQueue.last())
// `getConstruction(constructionQueue.last()) is PerpetualConstruction` is clear but more expensive // `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) /** 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. * Note: Overload with string parameter `constructionName` exists as well.
@ -773,7 +775,7 @@ class CityConstructions : IsPartOfGameInfoSerialization {
if (!canAddToQueue(construction)) return if (!canAddToQueue(construction)) return
val constructionName = construction.name val constructionName = construction.name
when { when {
currentConstructionFromQueue.isEmpty() || currentConstructionFromQueue == "Nothing" -> isQueueEmptyOrIdle() ->
currentConstructionFromQueue = constructionName currentConstructionFromQueue = constructionName
addToTop && construction is PerpetualConstruction && PerpetualConstruction.isNamePerpetual(currentConstructionFromQueue) -> addToTop && construction is PerpetualConstruction && PerpetualConstruction.isNamePerpetual(currentConstructionFromQueue) ->
currentConstructionFromQueue = constructionName // perpetual constructions will replace each other currentConstructionFromQueue = constructionName // perpetual constructions will replace each other
@ -817,7 +819,7 @@ class CityConstructions : IsPartOfGameInfoSerialization {
currentConstructionIsUserSet = if (constructionQueue.isEmpty()) { currentConstructionIsUserSet = if (constructionQueue.isEmpty()) {
if (automatic) chooseNextConstruction() if (automatic) chooseNextConstruction()
else constructionQueue.add("Nothing") // To prevent Construction Automation else constructionQueue.add(PerpetualConstruction.idle.name) // To prevent Construction Automation
false false
} else true // we're just continuing the regular queue } 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) * If the queue is emptied, no automatic: getSettings().autoAssignCityProduction is ignored! (parameter to be added when needed)
*/ */
fun removeAllByName(constructionName: String) { fun removeAllByName(constructionName: String) {
while (true) { while (!isQueueEmptyOrIdle()) {
val index = constructionQueue.indexOf(constructionName) val index = constructionQueue.indexOf(constructionName)
if (index < 0) return if (index < 0) return
removeFromQueue(index, false) removeFromQueue(index, false)
@ -904,7 +906,7 @@ class CityConstructions : IsPartOfGameInfoSerialization {
constructionQueue.removeAt(indexToRemove) constructionQueue.removeAt(indexToRemove)
currentConstructionIsUserSet = if (constructionQueue.isEmpty()) { currentConstructionIsUserSet = if (constructionQueue.isEmpty()) {
constructionQueue.add("Nothing") constructionQueue.add(PerpetualConstruction.idle.name)
false false
} else true } else true
} }

View File

@ -68,6 +68,13 @@ class GameSettings {
var skin: String = Constants.defaultSkin var skin: String = Constants.defaultSkin
var showTutorials: Boolean = true var showTutorials: Boolean = true
var autoAssignCityProduction: 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<String>()
var autoBuildingRoads: Boolean = true var autoBuildingRoads: Boolean = true
var automatedWorkersReplaceImprovements = true var automatedWorkersReplaceImprovements = true
var automatedUnitsMoveOnTurnStart: Boolean = false var automatedUnitsMoveOnTurnStart: Boolean = false

View File

@ -136,6 +136,7 @@ enum class KeyboardBinding(
BuildWonders(Category.CityScreen, "Buildable Wonders", 'w'), BuildWonders(Category.CityScreen, "Buildable Wonders", 'w'),
BuildNationalWonders(Category.CityScreen, "Buildable National Wonders", 'n'), BuildNationalWonders(Category.CityScreen, "Buildable National Wonders", 'n'),
BuildOther(Category.CityScreen, "Other Constructions", 'o'), BuildOther(Category.CityScreen, "Other Constructions", 'o'),
BuildDisabled(Category.CityScreen, "Disabled Constructions", KeyCharAndCode.ctrl('h')),
NextCity(Category.CityScreen, Input.Keys.RIGHT), NextCity(Category.CityScreen, Input.Keys.RIGHT),
PreviousCity(Category.CityScreen, Input.Keys.LEFT), PreviousCity(Category.CityScreen, Input.Keys.LEFT),
ShowStats(Category.CityScreen, 's'), ShowStats(Category.CityScreen, 's'),

View File

@ -3,6 +3,7 @@ package com.unciv.ui.popups
import com.badlogic.gdx.scenes.scene2d.Actor import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.Stage import com.badlogic.gdx.scenes.scene2d.Stage
import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.GUI
import com.unciv.logic.city.City import com.unciv.logic.city.City
import com.unciv.logic.city.CityConstructions import com.unciv.logic.city.CityConstructions
import com.unciv.models.ruleset.Building import com.unciv.models.ruleset.Building
@ -36,12 +37,17 @@ class CityScreenConstructionMenu(
cityConstructions.constructionQueue cityConstructions.constructionQueue
.count { it !in PerpetualConstruction.perpetualConstructionsMap } .count { it !in PerpetualConstruction.perpetualConstructionsMap }
private val myIndex = cityConstructions.constructionQueue.indexOf(constructionName) 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 && (construction as? Building)?.isAnyWonder() != true &&
city.civ.cities.map { it.cityConstructions }.any(predicate) city.civ.cities.map { it.cityConstructions }.any(predicate)
private fun forAllCities(action: (CityConstructions) -> Unit) = private fun forAllCities(action: (CityConstructions) -> Unit) =
city.civ.cities.map { it.cityConstructions }.forEach(action) city.civ.cities.map { it.cityConstructions }.forEach(action)
private val settings = GUI.getSettings()
private val disabledAutoAssignConstructions = settings.disabledAutoAssignConstructions
init { init {
closeListeners.add { closeListeners.add {
if (anyButtonWasClicked) onButtonClicked() 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() table.add(getButton("Add or move to the top in all cities", KeyboardBinding.AddConstructionAllTop, ::addAllQueuesTop)).row()
if (canRemoveAllQueues()) if (canRemoveAllQueues())
table.add(getButton("Remove from the queue in all cities", KeyboardBinding.RemoveConstructionAll, ::removeAllQueues)).row() 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 } return table.takeUnless { it.cells.isEmpty }
} }
@ -83,7 +93,7 @@ class CityScreenConstructionMenu(
cityConstructions.canAddToQueue(construction) cityConstructions.canAddToQueue(construction)
private fun addQueueTop() = cityConstructions.addToQueue(construction, addToTop = true) private fun addQueueTop() = cityConstructions.addToQueue(construction, addToTop = true)
private fun canAddAllQueues() = anyCity { private fun canAddAllQueues() = allCitiesEntryValid {
it.canAddToQueue(construction) && it.canAddToQueue(construction) &&
// A Perpetual that is already queued can still be added says canAddToQueue, but here we don't want to count that // 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)) !(construction is PerpetualConstruction && it.isBeingConstructedOrEnqueued(constructionName))
@ -91,7 +101,7 @@ class CityScreenConstructionMenu(
private fun addAllQueues() = forAllCities { it.addToQueue(construction) } private fun addAllQueues() = forAllCities { it.addToQueue(construction) }
private fun canAddAllQueuesTop() = construction !is PerpetualConstruction && private fun canAddAllQueuesTop() = construction !is PerpetualConstruction &&
anyCity { it.canAddToQueue(construction) || it.isEnqueuedForLater(constructionName) } allCitiesEntryValid { it.canAddToQueue(construction) || it.isEnqueuedForLater(constructionName) }
private fun addAllQueuesTop() = forAllCities { private fun addAllQueuesTop() = forAllCities {
val index = it.constructionQueue.indexOf(constructionName) val index = it.constructionQueue.indexOf(constructionName)
if (index > 0) if (index > 0)
@ -100,6 +110,19 @@ class CityScreenConstructionMenu(
it.addToQueue(construction, true) 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 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()
}
} }

View File

@ -8,6 +8,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextButton 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.GUI
import com.unciv.logic.city.City import com.unciv.logic.city.City
import com.unciv.logic.city.CityConstructions import com.unciv.logic.city.CityConstructions
import com.unciv.logic.map.tile.Tile import com.unciv.logic.map.tile.Tile
@ -255,6 +256,8 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
val buildableNationalWonders = ArrayList<Table>() val buildableNationalWonders = ArrayList<Table>()
val buildableBuildings = ArrayList<Table>() val buildableBuildings = ArrayList<Table>()
val specialConstructions = ArrayList<Table>() val specialConstructions = ArrayList<Table>()
val blacklisted = ArrayList<Table>()
val disabledAutoAssignConstructions: Set<String> = GUI.getSettings().disabledAutoAssignConstructions
var maxButtonWidth = constructionsQueueTable.width var maxButtonWidth = constructionsQueueTable.width
for (dto in constructionButtonDTOList) { for (dto in constructionButtonDTOList) {
@ -268,7 +271,9 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
continue continue
val constructionButton = getConstructionButton(dto) 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 BaseUnit -> units.add(constructionButton)
is Building -> { is Building -> {
when { when {
@ -290,6 +295,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
addCategory("Wonders", buildableWonders, maxButtonWidth, KeyboardBinding.BuildWonders) addCategory("Wonders", buildableWonders, maxButtonWidth, KeyboardBinding.BuildWonders)
addCategory("National Wonders", buildableNationalWonders, maxButtonWidth, KeyboardBinding.BuildNationalWonders) addCategory("National Wonders", buildableNationalWonders, maxButtonWidth, KeyboardBinding.BuildNationalWonders)
addCategory("Other", specialConstructions, maxButtonWidth, KeyboardBinding.BuildOther) addCategory("Other", specialConstructions, maxButtonWidth, KeyboardBinding.BuildOther)
addCategory("Disabled", blacklisted, maxButtonWidth, KeyboardBinding.BuildDisabled, startsOutOpened = false)
pack() pack()
} }
@ -799,12 +805,19 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
lowerTable.pack() lowerTable.pack()
} }
private fun Table.addCategory(title: String, list: ArrayList<Table>, prefWidth: Float, toggleKey: KeyboardBinding) { private fun Table.addCategory(
title: String,
list: ArrayList<Table>,
prefWidth: Float,
toggleKey: KeyboardBinding,
startsOutOpened: Boolean = true
) {
if (list.isEmpty()) return if (list.isEmpty()) return
if (rows > 0) addSeparator() if (rows > 0) addSeparator()
val expander = ExpanderTab( val expander = ExpanderTab(
title, title,
startsOutOpened = startsOutOpened,
defaultPad = 0f, defaultPad = 0f,
expanderWidth = prefWidth, expanderWidth = prefWidth,
persistenceID = "CityConstruction.$title", persistenceID = "CityConstruction.$title",