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 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

View File

@ -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<String> =
if (civInfo.isHuman()) GUI.getSettings().disabledAutoAssignConstructions
else emptySet()
private val buildableBuildings = hashMapOf<String, Boolean>()
private val buildableUnits = hashMapOf<String, Boolean>()
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() }

View File

@ -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
}

View File

@ -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<String>()
var autoBuildingRoads: Boolean = true
var automatedWorkersReplaceImprovements = true
var automatedUnitsMoveOnTurnStart: Boolean = false

View File

@ -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'),

View File

@ -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()
}
}

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.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<Table>()
val buildableBuildings = ArrayList<Table>()
val specialConstructions = ArrayList<Table>()
val blacklisted = ArrayList<Table>()
val disabledAutoAssignConstructions: Set<String> = 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<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 (rows > 0) addSeparator()
val expander = ExpanderTab(
title,
startsOutOpened = startsOutOpened,
defaultPad = 0f,
expanderWidth = prefWidth,
persistenceID = "CityConstruction.$title",