From 6fad057ced2a40476b50ffa4820228c045d8604a Mon Sep 17 00:00:00 2001 From: SomeTroglodyte <63000004+SomeTroglodyte@users.noreply.github.com> Date: Sun, 21 Jan 2024 18:13:58 +0100 Subject: [PATCH] Improved Console autocomplete (#10962) * Console: History remembers only _changed_ command lines * Console: New command sethealth, allow setmovement without argument to set max movement * Console: All object name comparisons on the "CliInput" level so multiword or varying-case input is recognized properly * Console: Autocomplete change so continuation is easier: Don't add space if there were alternatives, output CliInput-style * Console: Autocomplete religions - only from the actually founded ones (but we could easily add ruleset.religions - a '+' would be distinct already) * Console: Intelligent autocomplete promotions in `unit addpromotion` * Console: Treat all warnings * Console: Split off a few classes * Console: Forgot to treat a reminder - fix crash on `unit addp` * Console: setterrain, setresource, consequences of tile changes * Console: Revert normalizeToRuleset 'safety net' calls --- .../screens/devconsole/ConsoleCityCommands.kt | 73 ++++++ .../screens/devconsole/ConsoleCivCommands.kt | 50 ++++ .../screens/devconsole/ConsoleTileCommands.kt | 93 +++++++ .../screens/devconsole/ConsoleUnitCommands.kt | 70 ++++++ .../screens/devconsole/DevConsoleCommand.kt | 238 ++---------------- .../ui/screens/devconsole/DevConsolePopup.kt | 7 +- 6 files changed, 314 insertions(+), 217 deletions(-) create mode 100644 core/src/com/unciv/ui/screens/devconsole/ConsoleCityCommands.kt create mode 100644 core/src/com/unciv/ui/screens/devconsole/ConsoleCivCommands.kt create mode 100644 core/src/com/unciv/ui/screens/devconsole/ConsoleTileCommands.kt create mode 100644 core/src/com/unciv/ui/screens/devconsole/ConsoleUnitCommands.kt diff --git a/core/src/com/unciv/ui/screens/devconsole/ConsoleCityCommands.kt b/core/src/com/unciv/ui/screens/devconsole/ConsoleCityCommands.kt new file mode 100644 index 0000000000..29f5fcfb50 --- /dev/null +++ b/core/src/com/unciv/ui/screens/devconsole/ConsoleCityCommands.kt @@ -0,0 +1,73 @@ +package com.unciv.ui.screens.devconsole + +class ConsoleCityCommands : ConsoleCommandNode { + override val subcommands = hashMapOf( + + "add" to ConsoleAction("city add ") { console, params -> + val civ = console.getCivByName(params[0]) + val selectedTile = console.getSelectedTile() + if (selectedTile.isCityCenter()) + throw ConsoleErrorException("Tile already contains a city center") + civ.addCity(selectedTile.position) + DevConsoleResponse.OK + }, + + "remove" to ConsoleAction("city remove") { console, _ -> + val city = console.getSelectedCity() + city.destroyCity(overrideSafeties = true) + DevConsoleResponse.OK + }, + + "setpop" to ConsoleAction("city setpop ") { console, params -> + val city = console.getSelectedCity() + val newPop = console.getInt(params[0]) + if (newPop < 1) throw ConsoleErrorException("Population must be at least 1") + city.population.setPopulation(newPop) + DevConsoleResponse.OK + }, + + "setname" to ConsoleAction("city setname <\"name\">") { console, params -> + val city = console.getSelectedCity() + city.name = params[0] + DevConsoleResponse.OK + }, + + "addtile" to ConsoleAction("city addtile ") { console, params -> + val selectedTile = console.getSelectedTile() + val city = console.getCity(params[0]) + if (selectedTile.neighbors.none { it.getCity() == city }) + throw ConsoleErrorException("Tile is not adjacent to any tile already owned by the city") + if (selectedTile.isCityCenter()) throw ConsoleErrorException("Cannot tranfer city center") + city.expansion.takeOwnership(selectedTile) + DevConsoleResponse.OK + }, + + "removetile" to ConsoleAction("city removetile") { console, _ -> + val selectedTile = console.getSelectedTile() + val city = console.getSelectedCity() + city.expansion.relinquishOwnership(selectedTile) + DevConsoleResponse.OK + }, + + "religion" to ConsoleAction("city religion <±pressure>") { console, params -> + val city = console.getSelectedCity() + val religion = city.civ.gameInfo.religions.keys.findCliInput(params[0]) + ?: throw ConsoleErrorException("'${params[0]}' is not a known religion") + val pressure = console.getInt(params[1]) + city.religion.addPressure(religion, pressure.coerceAtLeast(-city.religion.getPressures()[religion])) + city.religion.updatePressureOnPopulationChange(0) + DevConsoleResponse.OK + }, + + "sethealth" to ConsoleAction("city sethealth [amount]") { console, params -> + val city = console.getSelectedCity() + val maxHealth = city.getMaxHealth() + val health = params.firstOrNull()?.run { + toIntOrNull() ?: throw ConsoleErrorException("Invalid number") + } ?: maxHealth + if (health !in 1..maxHealth) throw ConsoleErrorException("Number out of range") + city.health = health + DevConsoleResponse.OK + }, + ) +} diff --git a/core/src/com/unciv/ui/screens/devconsole/ConsoleCivCommands.kt b/core/src/com/unciv/ui/screens/devconsole/ConsoleCivCommands.kt new file mode 100644 index 0000000000..c3796a0a68 --- /dev/null +++ b/core/src/com/unciv/ui/screens/devconsole/ConsoleCivCommands.kt @@ -0,0 +1,50 @@ +package com.unciv.ui.screens.devconsole + +import com.unciv.logic.civilization.PlayerType +import com.unciv.models.ruleset.unique.Unique +import com.unciv.models.ruleset.unique.UniqueTriggerActivation +import com.unciv.models.stats.Stat + +class ConsoleCivCommands : ConsoleCommandNode { + override val subcommands = hashMapOf( + "addstat" to ConsoleAction("civ addstat [civ]") { console, params -> + val stat = Stat.safeValueOf(params[0].replaceFirstChar(Char::titlecase)) + ?: throw ConsoleErrorException("\"${params[0]}\" is not an acceptable Stat") + if (stat !in Stat.statsWithCivWideField) + throw ConsoleErrorException("$stat is not civ-wide") + + val amount = console.getInt(params[1]) + + val civ = if (params.size == 2) console.screen.selectedCiv + else console.getCivByName(params[2]) + + civ.addStat(stat, amount) + DevConsoleResponse.OK + }, + + "setplayertype" to ConsoleAction("civ setplayertype ") { console, params -> + val civ = console.getCivByName(params[0]) + val playerType = PlayerType.values().firstOrNull { it.name.lowercase() == params[1].lowercase() } + ?: throw ConsoleErrorException("Invalid player type, valid options are 'ai' or 'human'") + civ.playerType = playerType + DevConsoleResponse.OK + }, + + "revealmap" to ConsoleAction("civ revealmap ") { console, params -> + val civ = console.getCivByName(params[0]) + civ.gameInfo.tileMap.values.asSequence() + .forEach { it.setExplored(civ, true) } + DevConsoleResponse.OK + }, + + "activatetrigger" to ConsoleAction("civ activatetrigger <\"trigger\">") { console, params -> + val civ = console.getCivByName(params[0]) + val unique = Unique(params[1]) + if (unique.type == null) throw ConsoleErrorException("Unrecognized trigger") + val tile = console.screen.mapHolder.selectedTile + val city = tile?.getCity() + UniqueTriggerActivation.triggerCivwideUnique(unique, civ, city, tile) + DevConsoleResponse.OK + } + ) +} diff --git a/core/src/com/unciv/ui/screens/devconsole/ConsoleTileCommands.kt b/core/src/com/unciv/ui/screens/devconsole/ConsoleTileCommands.kt new file mode 100644 index 0000000000..52f1fbc465 --- /dev/null +++ b/core/src/com/unciv/ui/screens/devconsole/ConsoleTileCommands.kt @@ -0,0 +1,93 @@ +package com.unciv.ui.screens.devconsole + +import com.unciv.logic.civilization.Civilization +import com.unciv.logic.map.tile.RoadStatus +import com.unciv.models.ruleset.tile.TerrainType + +class ConsoleTileCommands: ConsoleCommandNode { + // Note: We *don't* call `TileInfoNormalizer.normalizeToRuleset(selectedTile, console.gameInfo.ruleset)` + // - we want the console to allow invalid tile configurations. + + override val subcommands = hashMapOf( + + "setimprovement" to ConsoleAction("tile setimprovement [civName]") { console, params -> + val selectedTile = console.getSelectedTile() + val improvement = console.gameInfo.ruleset.tileImprovements.values.findCliInput(params[0]) + ?: throw ConsoleErrorException("Unknown improvement") + var civ: Civilization? = null + if (params.size == 2) { + civ = console.getCivByName(params[1]) + } + selectedTile.improvementFunctions.changeImprovement(improvement.name, civ) + selectedTile.getCity()?.reassignPopulation() + DevConsoleResponse.OK + }, + + "removeimprovement" to ConsoleAction("tile removeimprovement") { console, _ -> + val selectedTile = console.getSelectedTile() + selectedTile.improvementFunctions.changeImprovement(null) + selectedTile.getCity()?.reassignPopulation() + DevConsoleResponse.OK + }, + + "removeroad" to ConsoleAction("tile removeroad") { console, _ -> + val selectedTile = console.getSelectedTile() + selectedTile.roadStatus = RoadStatus.None + //todo this covers many cases but not all - do we really need to loop over all civs? + selectedTile.getOwner()?.cache?.updateCitiesConnectedToCapital() + DevConsoleResponse.OK + }, + + "addfeature" to ConsoleAction("tile addfeature ") { console, params -> + val selectedTile = console.getSelectedTile() + val feature = getTerrainFeature(console, params[0]) + selectedTile.addTerrainFeature(feature.name) + selectedTile.getCity()?.reassignPopulation() + DevConsoleResponse.OK + }, + + "removefeature" to ConsoleAction("tile addfeature ") { console, params -> + val selectedTile = console.getSelectedTile() + val feature = getTerrainFeature(console, params[0]) + selectedTile.removeTerrainFeature(feature.name) + selectedTile.getCity()?.reassignPopulation() + DevConsoleResponse.OK + }, + + "setterrain" to ConsoleAction("tile setterrain ") { console, params -> + val selectedTile = console.getSelectedTile() + val terrain = console.gameInfo.ruleset.terrains.values.findCliInput(params[0]) + ?: throw ConsoleErrorException("Unknown terrain") + if (terrain.type != selectedTile.getBaseTerrain().type) + throw ConsoleErrorException("Changing terrain type is not allowed") + selectedTile.baseTerrain = terrain.name + selectedTile.setTerrainTransients() + selectedTile.getCity()?.reassignPopulation() + DevConsoleResponse.OK + }, + + "setresource" to ConsoleAction("tile setresource ") { console, params -> + val selectedTile = console.getSelectedTile() + val resource = console.gameInfo.ruleset.tileResources.values.findCliInput(params[0]) + ?: throw ConsoleErrorException("Unknown resource") + selectedTile.resource = resource.name + selectedTile.setTerrainTransients() + selectedTile.getCity()?.reassignPopulation() + DevConsoleResponse.OK + }, + + "removeresource" to ConsoleAction("tile removeresource") { console, _ -> + val selectedTile = console.getSelectedTile() + selectedTile.resource = null + selectedTile.setTerrainTransients() + selectedTile.getCity()?.reassignPopulation() + DevConsoleResponse.OK + }, + + ) + + private fun getTerrainFeature(console: DevConsolePopup, param: String) = + console.gameInfo.ruleset.terrains.values.asSequence() + .filter { it.type == TerrainType.TerrainFeature }.findCliInput(param) + ?: throw ConsoleErrorException("Unknown feature") +} diff --git a/core/src/com/unciv/ui/screens/devconsole/ConsoleUnitCommands.kt b/core/src/com/unciv/ui/screens/devconsole/ConsoleUnitCommands.kt new file mode 100644 index 0000000000..9a5e91b222 --- /dev/null +++ b/core/src/com/unciv/ui/screens/devconsole/ConsoleUnitCommands.kt @@ -0,0 +1,70 @@ +package com.unciv.ui.screens.devconsole + +class ConsoleUnitCommands : ConsoleCommandNode { + override val subcommands = hashMapOf( + + "add" to ConsoleAction("unit add ") { console, params -> + val selectedTile = console.getSelectedTile() + val civ = console.getCivByName(params[0]) + val baseUnit = console.gameInfo.ruleset.units.values.findCliInput(params[1]) + ?: throw ConsoleErrorException("Unknown unit") + civ.units.placeUnitNearTile(selectedTile.position, baseUnit) + DevConsoleResponse.OK + }, + + "remove" to ConsoleAction("unit remove") { console, _ -> + val unit = console.getSelectedUnit() + unit.destroy() + DevConsoleResponse.OK + }, + + "addpromotion" to object : ConsoleAction("unit addpromotion ", { console, params -> + val unit = console.getSelectedUnit() + val promotion = console.gameInfo.ruleset.unitPromotions.values.findCliInput(params[0]) + ?: throw ConsoleErrorException("Unknown promotion") + unit.promotions.addPromotion(promotion.name, true) + DevConsoleResponse.OK + }) { + override fun autocomplete(console: DevConsolePopup, params: List): String? { + if (params.isEmpty()) return null + val promotions = console.getSelectedUnit().promotions.promotions + val options = console.gameInfo.ruleset.unitPromotions.keys.asSequence() + .filter { it !in promotions } + .asIterable() + return getAutocompleteString(params.last(), options) + } + }, + + "removepromotion" to ConsoleAction("unit removepromotion ") { console, params -> + val unit = console.getSelectedUnit() + val promotion = unit.promotions.getPromotions().findCliInput(params[0]) + ?: throw ConsoleErrorException("Promotion not found on unit") + // No such action in-game so we need to manually update + unit.promotions.promotions.remove(promotion.name) + unit.updateUniques() + unit.updateVisibleTiles() + DevConsoleResponse.OK + }, + + "setmovement" to ConsoleAction("unit setmovement [amount]") { console, params -> + // Note amount defaults to maxMovement, but is not limited by it - it's an arbitrary choice to allow that + val unit = console.getSelectedUnit() + val movement = params.firstOrNull()?.run { + toFloatOrNull() ?: throw ConsoleErrorException("Invalid number") + } ?: unit.getMaxMovement().toFloat() + if (movement < 0f) throw ConsoleErrorException("Number out of range") + unit.currentMovement = movement + DevConsoleResponse.OK + }, + + "sethealth" to ConsoleAction("unit sethealth [amount]") { console, params -> + val health = params.firstOrNull()?.run { + toIntOrNull() ?: throw ConsoleErrorException("Invalid number") + } ?: 100 + if (health !in 1..100) throw ConsoleErrorException("Number out of range") + val unit = console.getSelectedUnit() + unit.health = health + DevConsoleResponse.OK + } + ) +} diff --git a/core/src/com/unciv/ui/screens/devconsole/DevConsoleCommand.kt b/core/src/com/unciv/ui/screens/devconsole/DevConsoleCommand.kt index 349212863f..851933e340 100644 --- a/core/src/com/unciv/ui/screens/devconsole/DevConsoleCommand.kt +++ b/core/src/com/unciv/ui/screens/devconsole/DevConsoleCommand.kt @@ -1,20 +1,26 @@ package com.unciv.ui.screens.devconsole -import com.unciv.logic.civilization.Civilization -import com.unciv.logic.civilization.PlayerType -import com.unciv.logic.map.tile.RoadStatus +import com.unciv.models.ruleset.IRulesetObject import com.unciv.models.ruleset.tile.TerrainType -import com.unciv.models.ruleset.unique.Unique -import com.unciv.models.ruleset.unique.UniqueTriggerActivation import com.unciv.models.stats.Stat internal fun String.toCliInput() = this.lowercase().replace(" ","-") +internal fun Iterable.findCliInput(param: String): String? { + val paramCli = param.toCliInput() + return firstOrNull { it.toCliInput() == paramCli } +} +internal fun Iterable.findCliInput(param: String): T? { + val paramCli = param.toCliInput() + return firstOrNull { it.name.toCliInput() == paramCli } +} +internal fun Sequence.findCliInput(param: String) = asIterable().findCliInput(param) + /** Returns the string to *add* to the existing command */ -fun getAutocompleteString(lastWord: String, allOptions: Collection):String? { - val matchingOptions = allOptions.filter { it.toCliInput().startsWith(lastWord.toCliInput()) } +internal fun getAutocompleteString(lastWord: String, allOptions: Iterable):String? { + val matchingOptions = allOptions.map { it.toCliInput() }.filter { it.startsWith(lastWord.toCliInput()) } if (matchingOptions.isEmpty()) return null - if (matchingOptions.size == 1) return matchingOptions.first().drop(lastWord.length) + if (matchingOptions.size == 1) return matchingOptions.first().drop(lastWord.length) + " " val firstOption = matchingOptions.first() for ((index, char) in firstOption.withIndex()) { @@ -22,19 +28,22 @@ fun getAutocompleteString(lastWord: String, allOptions: Collection):Stri matchingOptions.any { it[index] != char }) return firstOption.substring(0, index).drop(lastWord.length) } - return firstOption.drop(lastWord.length) + return firstOption.drop(lastWord.length) // don't add space, e.g. found drill-i and user might want drill-ii } interface ConsoleCommand { fun handle(console: DevConsolePopup, params: List): DevConsoleResponse - /** Returns the string to *add* to the existing command */ + /** Returns the string to *add* to the existing command. + * The function should add a space at the end if and only if the "match" is an unambiguous choice! + * Returning `null` means there was no match at all, while returning an empty string means the last word is a match as-is. + */ fun autocomplete(console: DevConsolePopup, params: List): String? = "" } -class ConsoleHintException(val hint:String):Exception() -class ConsoleErrorException(val error:String):Exception() +class ConsoleHintException(val hint: String) : Exception() +class ConsoleErrorException(val error: String) : Exception() -class ConsoleAction(val format: String, val action: (console: DevConsolePopup, params: List) -> DevConsoleResponse) : ConsoleCommand { +open class ConsoleAction(val format: String, val action: (console: DevConsolePopup, params: List) -> DevConsoleResponse) : ConsoleCommand { override fun handle(console: DevConsolePopup, params: List): DevConsoleResponse { return try { validateFormat(format, params) @@ -62,7 +71,10 @@ class ConsoleAction(val format: String, val action: (console: DevConsolePopup, p "promotionName" -> console.gameInfo.ruleset.unitPromotions.keys "improvementName" -> console.gameInfo.ruleset.tileImprovements.keys "featureName" -> console.gameInfo.ruleset.terrains.values.filter { it.type == TerrainType.TerrainFeature }.map { it.name } + "terrainName" -> console.gameInfo.ruleset.terrains.values.filter { it.type.isBaseTerrain }.map { it.name } + "resourceName" -> console.gameInfo.ruleset.tileResources.keys "stat" -> Stat.names() + "religionName" -> console.gameInfo.religions.keys else -> listOf() } return getAutocompleteString(lastParam, options) @@ -104,203 +116,3 @@ class ConsoleCommandRoot : ConsoleCommandNode { "civ" to ConsoleCivCommands() ) } - -class ConsoleUnitCommands : ConsoleCommandNode { - override val subcommands = hashMapOf( - - "add" to ConsoleAction("unit add ") { console, params -> - val selectedTile = console.getSelectedTile() - val civ = console.getCivByName(params[0]) - val baseUnit = console.gameInfo.ruleset.units.values.firstOrNull { it.name.toCliInput() == params[1].toCliInput() } - ?: throw ConsoleErrorException("Unknown unit") - civ.units.placeUnitNearTile(selectedTile.position, baseUnit) - DevConsoleResponse.OK - }, - - "remove" to ConsoleAction("unit remove") { console, params -> - val unit = console.getSelectedUnit() - unit.destroy() - DevConsoleResponse.OK - }, - - "addpromotion" to ConsoleAction("unit addpromotion ") { console, params -> - val unit = console.getSelectedUnit() - val promotion = console.gameInfo.ruleset.unitPromotions.values.firstOrNull { it.name.toCliInput() == params[0] } - ?: throw ConsoleErrorException("Unknown promotion") - unit.promotions.addPromotion(promotion.name, true) - DevConsoleResponse.OK - }, - - "removepromotion" to ConsoleAction("unit removepromotion ") { console, params -> - val unit = console.getSelectedUnit() - val promotion = unit.promotions.getPromotions().firstOrNull { it.name.toCliInput() == params[0] } - ?: throw ConsoleErrorException("Promotion not found on unit") - // No such action in-game so we need to manually update - unit.promotions.promotions.remove(promotion.name) - unit.updateUniques() - unit.updateVisibleTiles() - DevConsoleResponse.OK - }, - - "setmovement" to ConsoleAction("unit setmovement ") { console, params -> - val movement = params[0].toFloatOrNull() - if (movement == null || movement < 0) throw ConsoleErrorException("Invalid number") - val unit = console.getSelectedUnit() - unit.currentMovement = movement - DevConsoleResponse.OK - } - ) -} - -class ConsoleCityCommands : ConsoleCommandNode { - override val subcommands = hashMapOf( - - "add" to ConsoleAction("city add ") { console, params -> - val civ = console.getCivByName(params[0]) - val selectedTile = console.getSelectedTile() - if (selectedTile.isCityCenter()) - throw ConsoleErrorException("Tile already contains a city center") - civ.addCity(selectedTile.position) - DevConsoleResponse.OK - }, - - "remove" to ConsoleAction("city remove") { console, params -> - val city = console.getSelectedCity() - city.destroyCity(overrideSafeties = true) - DevConsoleResponse.OK - }, - - "setpop" to ConsoleAction("city setpop ") { console, params -> - val city = console.getSelectedCity() - val newPop = console.getInt(params[0]) - if (newPop < 1) throw ConsoleErrorException("Population must be at least 1") - city.population.setPopulation(newPop) - DevConsoleResponse.OK - }, - - "setname" to ConsoleAction("city setname <\"name\">") { console, params -> - val city = console.getSelectedCity() - city.name = params[0] - DevConsoleResponse.OK - }, - - "addtile" to ConsoleAction("city addtile ") { console, params -> - val selectedTile = console.getSelectedTile() - val city = console.getCity(params[0]) - if (selectedTile.neighbors.none { it.getCity() == city }) - throw ConsoleErrorException("Tile is not adjacent to any tile already owned by the city") - if (selectedTile.isCityCenter()) throw ConsoleErrorException("Cannot tranfer city center") - city.expansion.takeOwnership(selectedTile) - DevConsoleResponse.OK - }, - - "removetile" to ConsoleAction("city removetile") { console, params -> - val selectedTile = console.getSelectedTile() - val city = console.getSelectedCity() - city.expansion.relinquishOwnership(selectedTile) - DevConsoleResponse.OK - }, - - "religion" to ConsoleAction("city religion <±pressure>") { console, params -> - val city = console.getSelectedCity() - val religion = city.civ.gameInfo.religions.keys.firstOrNull { it.toCliInput() == params[0] } - ?: throw ConsoleErrorException("'${params[0]}' is not a known religion") - val pressure = console.getInt(params[1]) - city.religion.addPressure(religion, pressure.coerceAtLeast(-city.religion.getPressures()[religion])) - city.religion.updatePressureOnPopulationChange(0) - DevConsoleResponse.OK - }, - ) -} - -class ConsoleTileCommands: ConsoleCommandNode { - override val subcommands = hashMapOf( - - "setimprovement" to ConsoleAction("tile setimprovement [civName]") { console, params -> - val selectedTile = console.getSelectedTile() - val improvement = console.gameInfo.ruleset.tileImprovements.values.firstOrNull { - it.name.toCliInput() == params[0] - } ?: throw ConsoleErrorException("Unknown improvement") - var civ: Civilization? = null - if (params.size == 2){ - civ = console.getCivByName(params[1]) - } - selectedTile.improvementFunctions.changeImprovement(improvement.name, civ) - DevConsoleResponse.OK - }, - - "removeimprovement" to ConsoleAction("tile removeimprovement") { console, params -> - val selectedTile = console.getSelectedTile() - selectedTile.improvementFunctions.changeImprovement(null) - DevConsoleResponse.OK - }, - - "removeroad" to ConsoleAction("tile removeroad") { console, params -> - val selectedTile = console.getSelectedTile() - selectedTile.roadStatus = RoadStatus.None - DevConsoleResponse.OK - }, - - "addfeature" to ConsoleAction("tile addfeature ") { console, params -> - val selectedTile = console.getSelectedTile() - val feature = console.gameInfo.ruleset.terrains.values - .firstOrNull { it.type == TerrainType.TerrainFeature && it.name.toCliInput() == params[0] } - ?: throw ConsoleErrorException("Unknown feature") - selectedTile.addTerrainFeature(feature.name) - DevConsoleResponse.OK - }, - - "removefeature" to ConsoleAction("tile addfeature ") { console, params -> - val selectedTile = console.getSelectedTile() - val feature = console.gameInfo.ruleset.terrains.values - .firstOrNull { it.type == TerrainType.TerrainFeature && it.name.toCliInput() == params[0] } - ?: throw ConsoleErrorException("Unknown feature") - selectedTile.removeTerrainFeature(feature.name) - DevConsoleResponse.OK - } - ) -} - -class ConsoleCivCommands : ConsoleCommandNode { - override val subcommands = hashMapOf( - "addstat" to ConsoleAction("civ addstat [civ]") { console, params -> - val stat = Stat.safeValueOf(params[0].replaceFirstChar(Char::titlecase)) - ?: throw ConsoleErrorException("\"${params[0]}\" is not an acceptable Stat") - if (stat !in Stat.statsWithCivWideField) - throw ConsoleErrorException("$stat is not civ-wide") - - val amount = console.getInt(params[1]) - - val civ = if (params.size == 2) console.screen.selectedCiv - else console.getCivByName(params[2]) - - civ.addStat(stat, amount) - DevConsoleResponse.OK - }, - - "setplayertype" to ConsoleAction("civ setplayertype ") { console, params -> - val civ = console.getCivByName(params[0]) - val playerType = PlayerType.values().firstOrNull { it.name.lowercase() == params[1].lowercase() } - ?: throw ConsoleErrorException("Invalid player type, valid options are 'ai' or 'human'") - civ.playerType = playerType - DevConsoleResponse.OK - }, - - "revealmap" to ConsoleAction("civ revealmap ") { console, params -> - val civ = console.getCivByName(params[0]) - civ.gameInfo.tileMap.values.asSequence() - .forEach { it.setExplored(civ, true) } - DevConsoleResponse.OK - }, - - "activatetrigger" to ConsoleAction("civ activatetrigger <\"trigger\">") { console, params -> - val civ = console.getCivByName(params[0]) - val unique = Unique(params[1]) - if (unique.type == null) throw ConsoleErrorException("Unrecognized trigger") - val tile = console.screen.mapHolder.selectedTile - val city = tile?.getCity() - UniqueTriggerActivation.triggerCivwideUnique(unique, civ, city, tile) - DevConsoleResponse.OK - } - ) -} diff --git a/core/src/com/unciv/ui/screens/devconsole/DevConsolePopup.kt b/core/src/com/unciv/ui/screens/devconsole/DevConsolePopup.kt index eac2ff6030..7c2738f13c 100644 --- a/core/src/com/unciv/ui/screens/devconsole/DevConsolePopup.kt +++ b/core/src/com/unciv/ui/screens/devconsole/DevConsolePopup.kt @@ -64,7 +64,8 @@ class DevConsolePopup(val screen: WorldScreen) : Popup(screen) { val handleCommandResponse = handleCommand() if (handleCommandResponse.isOK) { screen.shouldUpdate = true - history.add(textField.text) + if (history.isEmpty() || history.last() != textField.text) + history.add(textField.text) close() return } @@ -85,9 +86,7 @@ class DevConsolePopup(val screen: WorldScreen) : Popup(screen) { private fun getAutocomplete(): String { val params = getParams(textField.text) - val result = commandRoot.autocomplete(this, params) - if (result.isNullOrEmpty()) return "" - return "$result " + return commandRoot.autocomplete(this, params).orEmpty() } internal fun getCivByName(name: String) = gameInfo.civilizations.firstOrNull { it.civName.toCliInput() == name.toCliInput() }