Console autocompletion can display *all* possibilities (#11461)

* RiverDirections is now namespaced to RiverGenerator with some pertinent helpers

* Autocomplete of RiverDirections is now handled in the default autocomplete

* Allow display of possible autocompletions for empty "start"

* Brighten that autocomplete possibilities message

* Fix a missed spot

* Improve removepromotion autocomplete
This commit is contained in:
SomeTroglodyte 2024-04-15 22:55:13 +02:00 committed by GitHub
parent 7ce2913293
commit 12adc0d65a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 45 additions and 37 deletions

View File

@ -9,6 +9,12 @@ import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.utils.debug
import kotlin.math.roundToInt
/**
* Handles River generation for [MapGenerator], [UniqueType.OneTimeChangeTerrain] and console.
*
* Map generation follows the [vertices of map hexes][RiverCoordinate]: [spawnRivers].
* In-game new rivers work on edges: [continueRiverOn]
*/
class RiverGenerator(
private val tileMap: TileMap,
private val randomness: MapGenerationRandomness,
@ -191,6 +197,20 @@ class RiverGenerator(
}.count { it }
}
enum class RiverDirections(private val clockPosition: Int) {
North(12),
NorthEast(2),
SouthEast(4),
South(6),
SouthWest(8),
NorthWest(10);
fun getNeighborTile(selectedTile: Tile): Tile? =
selectedTile.tileMap.getClockPositionNeighborTile(selectedTile, clockPosition)
companion object {
val names get() = values().map { it.name }
}
}
companion object {
/** [UniqueType.OneTimeChangeTerrain] tries to place a "River" feature.
*

View File

@ -3,6 +3,7 @@ package com.unciv.ui.screens.devconsole
import com.unciv.Constants
import com.unciv.logic.civilization.Civilization
import com.unciv.logic.map.mapgenerator.RiverGenerator
import com.unciv.logic.map.mapgenerator.RiverGenerator.RiverDirections
import com.unciv.logic.map.tile.RoadStatus
import com.unciv.models.ruleset.tile.TerrainType
@ -102,32 +103,16 @@ class ConsoleTileCommands: ConsoleCommandNode {
.filter { it.type == TerrainType.TerrainFeature }.findCliInput(param)
?: throw ConsoleErrorException("Unknown feature")
private enum class RiverDirections(val clockPosition: Int) {
North(12),
NorthEast(2),
SouthEast(4),
South(6),
SouthWest(8),
NorthWest(10)
}
private class ConsoleRiverAction(format: String, newValue: Boolean) : ConsoleAction(
format,
action = { console, params -> action(console, params, newValue) }
) {
override fun autocomplete(console: DevConsolePopup, params: List<String>): String? {
if (params.isEmpty()) return null
// Note this could filter which directions are allowed on the selected tile... too lazy
val options = RiverDirections.values().map { it.name }
return getAutocompleteString(params.last(), options, console)
}
companion object {
private fun action(console: DevConsolePopup, params: List<String>, newValue: Boolean): DevConsoleResponse {
val selectedTile = console.getSelectedTile()
val direction = findCliInput<RiverDirections>(params[0])
?: throw ConsoleErrorException("Unknown direction - use " + RiverDirections.values().joinToString { it.name })
val otherTile = selectedTile.tileMap.getClockPositionNeighborTile(selectedTile, direction.clockPosition)
?: throw ConsoleErrorException("Unknown direction - use " + RiverDirections.names.joinToString())
val otherTile = direction.getNeighborTile(selectedTile)
?: throw ConsoleErrorException("tile has no neighbor to the " + direction.name)
if (!otherTile.isLand)
throw ConsoleErrorException("there's no land to the " + direction.name)

View File

@ -25,17 +25,18 @@ class ConsoleUnitCommands : ConsoleCommandNode {
unit.promotions.addPromotion(promotion.name, true)
DevConsoleResponse.OK
}) {
override fun autocomplete(console: DevConsolePopup, params: List<String>): String? {
if (params.isEmpty()) return null
override fun autocomplete(console: DevConsolePopup, params: List<String>): String {
// Note: filtering by unit.type.name in promotion.unitTypes sounds good (No [Zero]-Ability on an Archer),
// but would also prevent promotions that can be legally obtained like Morale and Rejuvenation
val promotions = console.getSelectedUnit().promotions.promotions
val options = console.gameInfo.ruleset.unitPromotions.keys.asSequence()
.filter { it !in promotions }
.asIterable()
return getAutocompleteString(params.last(), options, console)
return getAutocompleteString(params.lastOrNull().orEmpty(), options, console)
}
},
"removepromotion" to ConsoleAction("unit removepromotion <promotionName>") { console, params ->
"removepromotion" to object : ConsoleAction("unit removepromotion <promotionName>", { console, params ->
val unit = console.getSelectedUnit()
val promotion = unit.promotions.getPromotions().findCliInput(params[0])
?: throw ConsoleErrorException("Promotion not found on unit")
@ -44,6 +45,9 @@ class ConsoleUnitCommands : ConsoleCommandNode {
unit.updateUniques()
unit.updateVisibleTiles()
DevConsoleResponse.OK
}) {
override fun autocomplete(console: DevConsolePopup, params: List<String>) =
getAutocompleteString(params.lastOrNull().orEmpty(), console.getSelectedUnit().promotions.promotions, console)
},
"setmovement" to ConsoleAction("unit setmovement [amount]") { console, params ->

View File

@ -1,6 +1,7 @@
package com.unciv.ui.screens.devconsole
import com.badlogic.gdx.graphics.Color
import com.unciv.logic.map.mapgenerator.RiverGenerator
import com.unciv.models.ruleset.IRulesetObject
import com.unciv.models.ruleset.tile.TerrainType
import com.unciv.models.stats.Stat
@ -25,14 +26,14 @@ internal inline fun <reified T: Enum<T>> findCliInput(param: String): T? {
}
/** Returns the string to *add* to the existing command */
internal fun getAutocompleteString(lastWord: String, allOptions: Iterable<String>, console: DevConsolePopup): String? {
internal fun getAutocompleteString(lastWord: String, allOptions: Iterable<String>, console: DevConsolePopup): String {
console.showResponse(null, Color.WHITE)
val matchingOptions = allOptions.map { it.toCliInput() }.filter { it.startsWith(lastWord.toCliInput()) }
if (matchingOptions.isEmpty()) return null
if (matchingOptions.isEmpty()) return ""
if (matchingOptions.size == 1) return matchingOptions.first().drop(lastWord.length) + " "
console.showResponse("Matching completions: " + matchingOptions.joinToString(), Color.FOREST)
console.showResponse("Matching completions: " + matchingOptions.joinToString(), Color.LIME.lerp(Color.OLIVE.cpy(), 0.5f))
val firstOption = matchingOptions.first()
for ((index, char) in firstOption.withIndex()) {
@ -45,11 +46,11 @@ internal fun getAutocompleteString(lastWord: String, allOptions: Iterable<String
interface ConsoleCommand {
fun handle(console: DevConsolePopup, params: List<String>): DevConsoleResponse
/** 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>): String? = ""
fun autocomplete(console: DevConsolePopup, params: List<String>): String = ""
}
class ConsoleHintException(val hint: String) : Exception()
@ -67,13 +68,11 @@ open class ConsoleAction(val format: String, val action: (console: DevConsolePop
}
}
override fun autocomplete(console: DevConsolePopup, params: List<String>): String? {
if (params.isEmpty()) return null
override fun autocomplete(console: DevConsolePopup, params: List<String>): String {
val formatParams = format.split(" ").drop(2).map {
it.removeSurrounding("<",">").removeSurrounding("[","]").removeSurrounding("\"")
}
if (formatParams.size < params.size) return null
if (formatParams.size < params.size) return ""
val formatParam = formatParams[params.lastIndex]
val lastParam = params.last()
@ -88,6 +87,7 @@ open class ConsoleAction(val format: String, val action: (console: DevConsolePop
"stat" -> Stat.names()
"religionName" -> console.gameInfo.religions.keys
"buildingName" -> console.gameInfo.ruleset.buildings.keys
"direction" -> RiverGenerator.RiverDirections.names
else -> listOf()
}
return getAutocompleteString(lastParam, options, console)
@ -113,9 +113,8 @@ interface ConsoleCommandNode : ConsoleCommand {
return handler.handle(console, params.drop(1))
}
override fun autocomplete(console: DevConsolePopup, params: List<String>): String? {
if (params.isEmpty()) return null
val firstParam = params[0]
override fun autocomplete(console: DevConsolePopup, params: List<String>): String {
val firstParam = params.firstOrNull().orEmpty()
if (firstParam in subcommands) return subcommands[firstParam]!!.autocomplete(console, params.drop(1))
return getAutocompleteString(firstParam, subcommands.keys, console)
}

View File

@ -20,7 +20,7 @@ class DevConsolePopup(val screen: WorldScreen) : Popup(screen) {
private var currentHistoryEntry = history.size
private val textField = TextField("", BaseScreen.skin)
private val responseLabel = "".toLabel(Color.RED)
private val responseLabel = "".toLabel(Color.RED).apply { wrap = true }
private val commandRoot = ConsoleCommandRoot()
internal val gameInfo = screen.gameInfo
@ -32,7 +32,7 @@ class DevConsolePopup(val screen: WorldScreen) : Popup(screen) {
// Without this, console popup will always contain a `
textField.addAction(Actions.delay(0.05f, Actions.run { textField.text = "" }))
add(responseLabel)
add(responseLabel).maxWidth(screen.stage.width * 0.8f)
open(true)
@ -89,7 +89,7 @@ class DevConsolePopup(val screen: WorldScreen) : Popup(screen) {
private fun getAutocomplete(): String {
val params = getParams(textField.text)
return commandRoot.autocomplete(this, params).orEmpty()
return commandRoot.autocomplete(this, params)
}
internal fun getCivByName(name: String) = gameInfo.civilizations.firstOrNull { it.civName.toCliInput() == name.toCliInput() }