mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-22 22:00:24 +07:00
Initial scenario/dev console, with 1 command :D (#10540)
* Initial console, with 1 command :D * add/remove city tiles * add/remove units! * Added command history, accessible with up and down keys * 'add promotion' cli command * 'remove promotion' cli command * Enumified * Converted enums into classes - enums are weirdly restricted in all kinds of uncomfortable ways * Added autocomplete!
This commit is contained in:
130
core/src/com/unciv/ui/screens/devconsole/DevConsoleCommand.kt
Normal file
130
core/src/com/unciv/ui/screens/devconsole/DevConsoleCommand.kt
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
package com.unciv.ui.screens.devconsole
|
||||||
|
|
||||||
|
fun String.toCliInput() = this.lowercase().replace(" ","-")
|
||||||
|
|
||||||
|
interface ConsoleCommand {
|
||||||
|
fun handle(console: DevConsolePopup, params: List<String>): String?
|
||||||
|
fun autocomplete(params: List<String>): String? = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConsoleAction(val action: (console: DevConsolePopup, params: List<String>)->String?):ConsoleCommand{
|
||||||
|
override fun handle(console: DevConsolePopup, params: List<String>): String? {
|
||||||
|
return action(console, params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConsoleCommandNode:ConsoleCommand{
|
||||||
|
val subcommands: HashMap<String, ConsoleCommand>
|
||||||
|
|
||||||
|
override fun handle(console: DevConsolePopup, params: List<String>): String? {
|
||||||
|
if (params.isEmpty())
|
||||||
|
return "Available commands: " + subcommands.keys.joinToString()
|
||||||
|
val handler = subcommands[params[0]] ?: return "Invalid command"
|
||||||
|
return handler.handle(console, params.drop(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun autocomplete(params: List<String>): String? {
|
||||||
|
if (params.isEmpty()) return null
|
||||||
|
val firstParam = params[0]
|
||||||
|
if (firstParam in subcommands) return subcommands[firstParam]!!.autocomplete(params.drop(1))
|
||||||
|
val possibleSubcommands = subcommands.keys.filter { it.startsWith(firstParam) }
|
||||||
|
if (possibleSubcommands.isEmpty()) return null
|
||||||
|
if (possibleSubcommands.size == 1) return possibleSubcommands.first().removePrefix(firstParam)
|
||||||
|
|
||||||
|
val firstSubcommand = possibleSubcommands.first()
|
||||||
|
for ((index, char) in firstSubcommand.withIndex()){
|
||||||
|
if (possibleSubcommands.any { it.lastIndex < index } ||
|
||||||
|
possibleSubcommands.any { it[index] != char })
|
||||||
|
return firstSubcommand.substring(0,index).removePrefix(firstParam)
|
||||||
|
}
|
||||||
|
return firstSubcommand.removePrefix(firstParam)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConsoleCommandRoot:ConsoleCommandNode{
|
||||||
|
override val subcommands = hashMapOf<String, ConsoleCommand>(
|
||||||
|
"unit" to ConsoleUnitCommands(),
|
||||||
|
"city" to ConsoleCityCommands()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConsoleUnitCommands:ConsoleCommandNode {
|
||||||
|
override val subcommands = hashMapOf<String, ConsoleCommand>(
|
||||||
|
"add" to ConsoleAction { console, params ->
|
||||||
|
if (params.size != 2)
|
||||||
|
return@ConsoleAction "Format: unit add <civName> <unitName>"
|
||||||
|
val selectedTile = console.screen.mapHolder.selectedTile
|
||||||
|
?: return@ConsoleAction "No tile selected"
|
||||||
|
val civ = console.getCivByName(params[0])
|
||||||
|
?: return@ConsoleAction "Unknown civ"
|
||||||
|
val baseUnit = console.gameInfo.ruleset.units.values.firstOrNull { it.name.toCliInput() == params[3] }
|
||||||
|
?: return@ConsoleAction "Unknown unit"
|
||||||
|
civ.units.placeUnitNearTile(selectedTile.position, baseUnit)
|
||||||
|
return@ConsoleAction null
|
||||||
|
},
|
||||||
|
|
||||||
|
"remove" to ConsoleAction { console, params ->
|
||||||
|
val unit = console.getSelectedUnit()
|
||||||
|
?: return@ConsoleAction "Select tile with unit"
|
||||||
|
unit.destroy()
|
||||||
|
return@ConsoleAction null
|
||||||
|
},
|
||||||
|
|
||||||
|
"addpromotion" to ConsoleAction { console, params ->
|
||||||
|
if (params.size != 1)
|
||||||
|
return@ConsoleAction "Format: unit addpromotion <promotionName>"
|
||||||
|
val unit = console.getSelectedUnit()
|
||||||
|
?: return@ConsoleAction "Select tile with unit"
|
||||||
|
val promotion = console.gameInfo.ruleset.unitPromotions.values.firstOrNull { it.name.toCliInput() == params[2] }
|
||||||
|
?: return@ConsoleAction "Unknown promotion"
|
||||||
|
unit.promotions.addPromotion(promotion.name, true)
|
||||||
|
return@ConsoleAction null
|
||||||
|
},
|
||||||
|
|
||||||
|
"removepromotion" to ConsoleAction { console, params ->
|
||||||
|
if (params.size != 1)
|
||||||
|
return@ConsoleAction "Format: unit removepromotion <promotionName>"
|
||||||
|
val unit = console.getSelectedUnit()
|
||||||
|
?: return@ConsoleAction "Select tile with unit"
|
||||||
|
val promotion = unit.promotions.getPromotions().firstOrNull { it.name.toCliInput() == params[2] }
|
||||||
|
?: return@ConsoleAction "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()
|
||||||
|
return@ConsoleAction null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConsoleCityCommands:ConsoleCommandNode {
|
||||||
|
override val subcommands = hashMapOf<String, ConsoleCommand>(
|
||||||
|
"setpop" to ConsoleAction { console, params ->
|
||||||
|
if (params.size != 2) return@ConsoleAction "Format: city setpop <cityName> <amount>"
|
||||||
|
val newPop = params[1].toIntOrNull() ?: return@ConsoleAction "Invalid amount " + params[1]
|
||||||
|
if (newPop < 1) return@ConsoleAction "Invalid amount $newPop"
|
||||||
|
val city = console.gameInfo.getCities().firstOrNull { it.name.toCliInput() == params[0] }
|
||||||
|
?: return@ConsoleAction "Unknown city"
|
||||||
|
city.population.setPopulation(newPop)
|
||||||
|
return@ConsoleAction null
|
||||||
|
},
|
||||||
|
|
||||||
|
"addtile" to ConsoleAction { console, params ->
|
||||||
|
val selectedTile = console.screen.mapHolder.selectedTile
|
||||||
|
?: return@ConsoleAction "No tile selected"
|
||||||
|
val city = console.gameInfo.getCities().firstOrNull { it.name.toCliInput() == params[0] }
|
||||||
|
?: return@ConsoleAction "Unknown city"
|
||||||
|
if (selectedTile.neighbors.none { it.getCity() == city })
|
||||||
|
return@ConsoleAction "Tile is not adjacent to city"
|
||||||
|
city.expansion.takeOwnership(selectedTile)
|
||||||
|
return@ConsoleAction null
|
||||||
|
},
|
||||||
|
|
||||||
|
"removetile" to ConsoleAction { console, params ->
|
||||||
|
val selectedTile = console.screen.mapHolder.selectedTile
|
||||||
|
?: return@ConsoleAction "No tile selected"
|
||||||
|
val city = selectedTile.getCity() ?: return@ConsoleAction "No city for selected tile"
|
||||||
|
city.expansion.relinquishOwnership(selectedTile)
|
||||||
|
return@ConsoleAction null
|
||||||
|
})
|
||||||
|
}
|
87
core/src/com/unciv/ui/screens/devconsole/DevConsolePopup.kt
Normal file
87
core/src/com/unciv/ui/screens/devconsole/DevConsolePopup.kt
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package com.unciv.ui.screens.devconsole
|
||||||
|
|
||||||
|
import com.badlogic.gdx.Input
|
||||||
|
import com.badlogic.gdx.graphics.Color
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.actions.Actions
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.TextField
|
||||||
|
import com.unciv.logic.map.mapunit.MapUnit
|
||||||
|
import com.unciv.ui.components.extensions.toLabel
|
||||||
|
import com.unciv.ui.components.input.KeyCharAndCode
|
||||||
|
import com.unciv.ui.components.input.keyShortcuts
|
||||||
|
import com.unciv.ui.popups.Popup
|
||||||
|
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||||
|
import com.unciv.ui.screens.worldscreen.WorldScreen
|
||||||
|
|
||||||
|
|
||||||
|
class DevConsolePopup(val screen: WorldScreen) : Popup(screen) {
|
||||||
|
companion object {
|
||||||
|
val history = ArrayList<String>()
|
||||||
|
}
|
||||||
|
|
||||||
|
val textField = TextField("", BaseScreen.skin)
|
||||||
|
internal val gameInfo = screen.gameInfo
|
||||||
|
|
||||||
|
init {
|
||||||
|
add(textField).width(stageToShowOn.width / 2).row()
|
||||||
|
val label = "".toLabel(Color.RED)
|
||||||
|
add(label)
|
||||||
|
textField.keyShortcuts.add(Input.Keys.ENTER) {
|
||||||
|
val handleCommandResponse = handleCommand()
|
||||||
|
if (handleCommandResponse == null) {
|
||||||
|
screen.shouldUpdate = true
|
||||||
|
history.add(textField.text)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
else label.setText(handleCommandResponse)
|
||||||
|
}
|
||||||
|
// Without this, console popup will always contain a `
|
||||||
|
textField.addAction(Actions.delay(0.05f, Actions.run { textField.text = "" }))
|
||||||
|
open(true)
|
||||||
|
keyShortcuts.add(KeyCharAndCode.ESC) { close() }
|
||||||
|
|
||||||
|
keyShortcuts.add(KeyCharAndCode.TAB) {
|
||||||
|
val textToAdd = getAutocomplete()
|
||||||
|
textField.appendText(textToAdd)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (history.isNotEmpty()) {
|
||||||
|
var currentHistoryEntry = history.size
|
||||||
|
keyShortcuts.add(Input.Keys.UP) {
|
||||||
|
if (currentHistoryEntry > 0) currentHistoryEntry--
|
||||||
|
textField.text = history[currentHistoryEntry]
|
||||||
|
textField.cursorPosition = textField.text.length
|
||||||
|
}
|
||||||
|
keyShortcuts.add(Input.Keys.DOWN) {
|
||||||
|
if (currentHistoryEntry == history.size) currentHistoryEntry--
|
||||||
|
if (currentHistoryEntry < history.lastIndex) currentHistoryEntry++
|
||||||
|
textField.text = history[currentHistoryEntry]
|
||||||
|
textField.cursorPosition = textField.text.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getParams(text:String) = text.split(" ").filter { it.isNotEmpty() }.map { it.lowercase() }
|
||||||
|
|
||||||
|
private fun handleCommand(): String? {
|
||||||
|
val params = getParams(textField.text)
|
||||||
|
if (params.isEmpty()) return "No command"
|
||||||
|
return ConsoleCommandRoot().handle(this, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAutocomplete():String {
|
||||||
|
val params = getParams(textField.text)
|
||||||
|
return ConsoleCommandRoot().autocomplete(params) ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun getCivByName(name:String) = gameInfo.civilizations.firstOrNull { it.civName.toCliInput() == name }
|
||||||
|
|
||||||
|
internal fun getSelectedUnit(): MapUnit? {
|
||||||
|
val selectedTile = screen.mapHolder.selectedTile ?: return null
|
||||||
|
if (selectedTile.getFirstUnit() == null) return null
|
||||||
|
val units = selectedTile.getUnits().toList()
|
||||||
|
val selectedUnit = screen.bottomUnitTable.selectedUnit
|
||||||
|
return if (selectedUnit != null && selectedUnit.getTile() == selectedTile) selectedUnit
|
||||||
|
else units.first()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -43,6 +43,7 @@ import com.unciv.ui.popups.hasOpenPopups
|
|||||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||||
import com.unciv.ui.screens.cityscreen.CityScreen
|
import com.unciv.ui.screens.cityscreen.CityScreen
|
||||||
import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen
|
import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen
|
||||||
|
import com.unciv.ui.screens.devconsole.DevConsolePopup
|
||||||
import com.unciv.ui.screens.mainmenuscreen.MainMenuScreen
|
import com.unciv.ui.screens.mainmenuscreen.MainMenuScreen
|
||||||
import com.unciv.ui.screens.newgamescreen.NewGameScreen
|
import com.unciv.ui.screens.newgamescreen.NewGameScreen
|
||||||
import com.unciv.ui.screens.overviewscreen.EmpireOverviewCategories
|
import com.unciv.ui.screens.overviewscreen.EmpireOverviewCategories
|
||||||
@ -189,6 +190,14 @@ class WorldScreen(
|
|||||||
|
|
||||||
globalShortcuts.add(KeyCharAndCode.BACK) { backButtonAndESCHandler() }
|
globalShortcuts.add(KeyCharAndCode.BACK) { backButtonAndESCHandler() }
|
||||||
|
|
||||||
|
|
||||||
|
globalShortcuts.add('`'){
|
||||||
|
// No cheating unless you're by yourself
|
||||||
|
if (gameInfo.civilizations.count { it.isHuman() } > 1) return@add
|
||||||
|
val consolePopup = DevConsolePopup(this)
|
||||||
|
stage.keyboardFocus = consolePopup.textField
|
||||||
|
}
|
||||||
|
|
||||||
addKeyboardListener() // for map panning by W,S,A,D
|
addKeyboardListener() // for map panning by W,S,A,D
|
||||||
addKeyboardPresses() // shortcut keys like F1
|
addKeyboardPresses() // shortcut keys like F1
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user