mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-14 17:59:11 +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.cityscreen.CityScreen
|
||||
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.newgamescreen.NewGameScreen
|
||||
import com.unciv.ui.screens.overviewscreen.EmpireOverviewCategories
|
||||
@ -189,6 +190,14 @@ class WorldScreen(
|
||||
|
||||
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
|
||||
addKeyboardPresses() // shortcut keys like F1
|
||||
|
||||
|
Reference in New Issue
Block a user