Console: Simplified input validation, with exception throwing

This commit is contained in:
Yair Morgenstern
2023-12-24 23:51:00 +02:00
parent 46e5d7e245
commit 30c9876e2f
2 changed files with 68 additions and 81 deletions

View File

@ -12,9 +12,18 @@ interface ConsoleCommand {
fun autocomplete(params: List<String>): String? = ""
}
class ConsoleHintException(val hint:String):Exception()
class ConsoleErrorException(val error:String):Exception()
class ConsoleAction(val action: (console: DevConsolePopup, params: List<String>) -> DevConsoleResponse) : ConsoleCommand {
override fun handle(console: DevConsolePopup, params: List<String>): DevConsoleResponse {
return action(console, params)
return try {
action(console, params)
} catch (hintException: ConsoleHintException) {
DevConsoleResponse.hint(hintException.hint)
} catch (errorException: ConsoleErrorException) {
DevConsoleResponse.error(errorException.error)
}
}
}
@ -56,16 +65,22 @@ class ConsoleCommandRoot : ConsoleCommandNode {
)
}
fun validateFormat(format: String, params:List<String>){
val allParams = format.split(" ")
val requiredParamsAmount = allParams.count { it.startsWith('<') }
val optionalParamsAmount = allParams.count { it.startsWith('[') }
if (params.size < requiredParamsAmount || params.size > requiredParamsAmount + optionalParamsAmount)
throw ConsoleHintException("Format: $format")
}
class ConsoleUnitCommands : ConsoleCommandNode {
override val subcommands = hashMapOf<String, ConsoleCommand>(
"add" to ConsoleAction { console, params ->
if (params.size != 2)
return@ConsoleAction DevConsoleResponse.hint("Format: unit add <civName> <unitName>")
val selectedTile = console.screen.mapHolder.selectedTile
?: return@ConsoleAction DevConsoleResponse.error("No tile selected")
validateFormat("unit add <civName> <unitName>", params)
val selectedTile = console.getSelectedTile()
val civ = console.getCivByName(params[0])
?: return@ConsoleAction DevConsoleResponse.error("Unknown civ")
val baseUnit = console.gameInfo.ruleset.units.values.firstOrNull { it.name.toCliInput() == params[1] }
?: return@ConsoleAction DevConsoleResponse.error("Unknown unit")
civ.units.placeUnitNearTile(selectedTile.position, baseUnit)
@ -73,19 +88,15 @@ class ConsoleUnitCommands : ConsoleCommandNode {
},
"remove" to ConsoleAction { console, params ->
if (params.isNotEmpty())
return@ConsoleAction DevConsoleResponse.hint("Format: unit remove")
validateFormat("unit remove", params)
val unit = console.getSelectedUnit()
?: return@ConsoleAction DevConsoleResponse.error("Select tile with unit")
unit.destroy()
return@ConsoleAction DevConsoleResponse.OK
},
"addpromotion" to ConsoleAction { console, params ->
if (params.size != 1)
return@ConsoleAction DevConsoleResponse.hint("Format: unit addpromotion <promotionName>")
validateFormat("unit addpromotion <promotionName>", params)
val unit = console.getSelectedUnit()
?: return@ConsoleAction DevConsoleResponse.error("Select tile with unit")
val promotion = console.gameInfo.ruleset.unitPromotions.values.firstOrNull { it.name.toCliInput() == params[0] }
?: return@ConsoleAction DevConsoleResponse.error("Unknown promotion")
unit.promotions.addPromotion(promotion.name, true)
@ -93,10 +104,8 @@ class ConsoleUnitCommands : ConsoleCommandNode {
},
"removepromotion" to ConsoleAction { console, params ->
if (params.size != 1)
return@ConsoleAction DevConsoleResponse.hint("Format: unit removepromotion <promotionName>")
validateFormat("unit removepromotion <promotionName>", params)
val unit = console.getSelectedUnit()
?: return@ConsoleAction DevConsoleResponse.error("Select tile with unit")
val promotion = unit.promotions.getPromotions().firstOrNull { it.name.toCliInput() == params[0] }
?: return@ConsoleAction DevConsoleResponse.error("Promotion not found on unit")
// No such action in-game so we need to manually update
@ -107,12 +116,10 @@ class ConsoleUnitCommands : ConsoleCommandNode {
},
"setmovement" to ConsoleAction { console, params ->
if (params.size != 1)
return@ConsoleAction DevConsoleResponse.hint("Format: unit setmovement <amount>")
validateFormat("unit setmovement <amount>", params)
val movement = params[0].toFloatOrNull()
if (movement == null || movement < 0) return@ConsoleAction DevConsoleResponse.error("Invalid number")
val unit = console.getSelectedUnit()
?: return@ConsoleAction DevConsoleResponse.error("Select tile with unit")
unit.currentMovement = movement
return@ConsoleAction DevConsoleResponse.OK
}
@ -123,12 +130,9 @@ class ConsoleCityCommands : ConsoleCommandNode {
override val subcommands = hashMapOf<String, ConsoleCommand>(
"add" to ConsoleAction { console, params ->
if (params.size != 1)
return@ConsoleAction DevConsoleResponse.hint("Format: city add <civName>")
validateFormat("city add <civName>", params)
val civ = console.getCivByName(params[0])
?: return@ConsoleAction DevConsoleResponse.error("Unknown civ")
val selectedTile = console.screen.mapHolder.selectedTile
?: return@ConsoleAction DevConsoleResponse.error("No tile selected")
val selectedTile = console.getSelectedTile()
if (selectedTile.isCityCenter())
return@ConsoleAction DevConsoleResponse.error("Tile already contains a city center")
civ.addCity(selectedTile.position)
@ -136,34 +140,25 @@ class ConsoleCityCommands : ConsoleCommandNode {
},
"remove" to ConsoleAction { console, params ->
if (params.isNotEmpty())
return@ConsoleAction DevConsoleResponse.hint("Format: city remove")
val selectedTile = console.screen.mapHolder.selectedTile
?: return@ConsoleAction DevConsoleResponse.error("No tile selected")
val city = selectedTile.getCity()
?: return@ConsoleAction DevConsoleResponse.error("No city in selected tile")
validateFormat("city remove", params)
val city = console.getSelectedCity()
city.destroyCity(overrideSafeties = true)
return@ConsoleAction DevConsoleResponse.OK
},
"setpop" to ConsoleAction { console, params ->
if (params.size != 2)
return@ConsoleAction DevConsoleResponse.hint("Format: city setpop <cityName> <amount>")
val newPop = params[1].toIntOrNull() ?: return@ConsoleAction DevConsoleResponse.error("Invalid amount " + params[1])
if (newPop < 1) return@ConsoleAction DevConsoleResponse.error("Invalid amount $newPop")
val city = console.gameInfo.getCities().firstOrNull { it.name.toCliInput() == params[0] }
?: return@ConsoleAction DevConsoleResponse.error("Unknown city")
validateFormat("city setpop <amount>", params)
val city = console.getSelectedCity()
val newPop = console.getInt(params[0])
if (newPop < 1) return@ConsoleAction DevConsoleResponse.error("Population must be at least 1")
city.population.setPopulation(newPop)
return@ConsoleAction DevConsoleResponse.OK
},
"addtile" to ConsoleAction { console, params ->
if (params.size != 1)
return@ConsoleAction DevConsoleResponse.hint("Format: city addtile <cityName>")
val selectedTile = console.screen.mapHolder.selectedTile
?: return@ConsoleAction DevConsoleResponse.error("No tile selected")
val city = console.gameInfo.getCities().firstOrNull { it.name.toCliInput() == params[0] }
?: return@ConsoleAction DevConsoleResponse.error("Unknown city")
validateFormat("city addtile <cityName>", params)
val selectedTile = console.getSelectedTile()
val city = console.getCity(params[0])
if (selectedTile.neighbors.none { it.getCity() == city })
return@ConsoleAction DevConsoleResponse.error("Tile is not adjacent to any tile already owned by the city")
city.expansion.takeOwnership(selectedTile)
@ -171,24 +166,19 @@ class ConsoleCityCommands : ConsoleCommandNode {
},
"removetile" to ConsoleAction { console, params ->
if (params.isNotEmpty())
return@ConsoleAction DevConsoleResponse.hint("Format: city removetile")
val selectedTile = console.screen.mapHolder.selectedTile
?: return@ConsoleAction DevConsoleResponse.error("No tile selected")
val city = selectedTile.getCity() ?: return@ConsoleAction DevConsoleResponse.error("No city for selected tile")
validateFormat("city removetile", params)
val selectedTile = console.getSelectedTile()
val city = console.getSelectedCity()
city.expansion.relinquishOwnership(selectedTile)
return@ConsoleAction DevConsoleResponse.OK
},
"religion" to ConsoleAction { console, params ->
if (params.size != 2)
return@ConsoleAction DevConsoleResponse.hint("Format: city religion <name> <±pressure>")
val city = console.screen.bottomUnitTable.selectedCity
?: return@ConsoleAction DevConsoleResponse.hint("Select a city first")
validateFormat("city religion <name> <±pressure>", params)
val city = console.getSelectedCity()
val religion = city.civ.gameInfo.religions.keys.firstOrNull { it.toCliInput() == params[0] }
?: return@ConsoleAction DevConsoleResponse.error("'${params[0]}' is not a known religion")
val pressure = params[1].toIntOrNull()
?: return@ConsoleAction DevConsoleResponse.error("'${params[1]}' is not an integer")
val pressure = console.getInt(params[1])
city.religion.addPressure(religion, pressure.coerceAtLeast(-city.religion.getPressures()[religion]))
city.religion.updatePressureOnPopulationChange(0)
return@ConsoleAction DevConsoleResponse.OK
@ -200,36 +190,29 @@ class ConsoleTileCommands: ConsoleCommandNode {
override val subcommands = hashMapOf<String, ConsoleCommand>(
"setimprovement" to ConsoleAction { console, params ->
if (params.size != 1 && params.size != 2)
return@ConsoleAction DevConsoleResponse.hint("Format: tile setimprovement <improvementName> [<civName>]")
val selectedTile = console.screen.mapHolder.selectedTile
?: return@ConsoleAction DevConsoleResponse.error("No tile selected")
validateFormat("tile setimprovement <improvementName> [civName]", params)
val selectedTile = console.getSelectedTile()
val improvement = console.gameInfo.ruleset.tileImprovements.values.firstOrNull {
it.name.toCliInput() == params[0]
} ?: return@ConsoleAction DevConsoleResponse.error("Unknown improvement")
var civ:Civilization? = null
var civ: Civilization? = null
if (params.size == 2){
civ = console.getCivByName(params[1])
?: return@ConsoleAction DevConsoleResponse.error("Unknown civ")
}
selectedTile.improvementFunctions.changeImprovement(improvement.name, civ)
return@ConsoleAction DevConsoleResponse.OK
},
"removeimprovement" to ConsoleAction { console, params ->
if (params.isNotEmpty())
return@ConsoleAction DevConsoleResponse.hint("Format: tile removeimprovement")
val selectedTile = console.screen.mapHolder.selectedTile
?: return@ConsoleAction DevConsoleResponse.error("No tile selected")
validateFormat("tile removeimprovement", params)
val selectedTile = console.getSelectedTile()
selectedTile.improvementFunctions.changeImprovement(null)
return@ConsoleAction DevConsoleResponse.OK
},
"addfeature" to ConsoleAction { console, params ->
if (params.size != 1)
return@ConsoleAction DevConsoleResponse.hint("Format: tile addfeature <featureName>")
val selectedTile = console.screen.mapHolder.selectedTile
?: return@ConsoleAction DevConsoleResponse.error("No tile selected")
validateFormat("tile addfeature <featureName>", params)
val selectedTile = console.getSelectedTile()
val feature = console.gameInfo.ruleset.terrains.values
.firstOrNull { it.type == TerrainType.TerrainFeature && it.name.toCliInput() == params[0] }
?: return@ConsoleAction DevConsoleResponse.error("Unknown feature")
@ -238,10 +221,8 @@ class ConsoleTileCommands: ConsoleCommandNode {
},
"removefeature" to ConsoleAction { console, params ->
if (params.size != 1)
return@ConsoleAction DevConsoleResponse.hint("Format: tile addfeature <featureName>")
val selectedTile = console.screen.mapHolder.selectedTile
?: return@ConsoleAction DevConsoleResponse.error("No tile selected")
validateFormat("tile addfeature <featureName>", params)
val selectedTile = console.getSelectedTile()
val feature = console.gameInfo.ruleset.terrains.values
.firstOrNull { it.type == TerrainType.TerrainFeature && it.name.toCliInput() == params[0] }
?: return@ConsoleAction DevConsoleResponse.error("Unknown feature")
@ -255,16 +236,13 @@ class ConsoleCivCommands : ConsoleCommandNode {
override val subcommands = hashMapOf<String, ConsoleCommand>(
"add" to ConsoleAction { console, params ->
var statPos = 0
if (params.size !in 2..3)
return@ConsoleAction DevConsoleResponse.hint("Format: civ add [civ] <stat> <amount>")
validateFormat("civ add [civ] <stat> <amount>", params)
val civ = if (params.size == 2) console.screen.selectedCiv
else {
statPos++
console.getCivByName(params[0])
?: return@ConsoleAction DevConsoleResponse.error("Unknown civ")
}
val amount = params[statPos+1].toIntOrNull()
?: return@ConsoleAction DevConsoleResponse.error("Whut? \"${params[statPos+1]}\" is not a number!")
val amount = console.getInt(params[statPos+1])
val stat = Stat.safeValueOf(params[statPos].replaceFirstChar(Char::titlecase))
?: return@ConsoleAction DevConsoleResponse.error("Whut? \"${params[statPos]}\" is not a Stat!")
if (stat !in Stat.statsWithCivWideField)
@ -274,10 +252,8 @@ class ConsoleCivCommands : ConsoleCommandNode {
},
"setplayertype" to ConsoleAction { console, params ->
if (params.size != 2) return@ConsoleAction DevConsoleResponse.hint("Format: civ setplayertype <civName> <ai/human>")
validateFormat("civ setplayertype <civName> <ai/human>", params)
val civ = console.getCivByName(params[0])
?: return@ConsoleAction DevConsoleResponse.error("Unknown civ")
val playerType = PlayerType.values().firstOrNull { it.name.lowercase() == params[1].lowercase() }
?: return@ConsoleAction DevConsoleResponse.error("Invalid player type, valid options are 'ai' or 'human'")
civ.playerType = playerType

View File

@ -85,14 +85,25 @@ class DevConsolePopup(val screen: WorldScreen) : Popup(screen) {
}
internal fun getCivByName(name: String) = gameInfo.civilizations.firstOrNull { it.civName.toCliInput() == name }
?: throw ConsoleErrorException("Unknown civ: $name")
internal fun getSelectedUnit(): MapUnit? {
val selectedTile = screen.mapHolder.selectedTile ?: return null
if (selectedTile.getFirstUnit() == null) return null
internal fun getSelectedTile() = screen.mapHolder.selectedTile ?: throw ConsoleErrorException("Select tile")
/** Gets city by selected tile */
internal fun getSelectedCity() = getSelectedTile().getCity() ?: throw ConsoleErrorException("Select tile belonging to city")
internal fun getCity(cityName:String) = gameInfo.getCities().firstOrNull { it.name.toCliInput() == cityName }
?: throw ConsoleErrorException("Unknown city: $cityName")
internal fun getSelectedUnit(): MapUnit {
val selectedTile = getSelectedTile()
if (selectedTile.getFirstUnit() == null) throw ConsoleErrorException("Select tile with units")
val units = selectedTile.getUnits().toList()
val selectedUnit = screen.bottomUnitTable.selectedUnit
return if (selectedUnit != null && selectedUnit.getTile() == selectedTile) selectedUnit
else units.first()
}
internal fun getInt(param: String) = param.toIntOrNull() ?: throw ConsoleErrorException("$param is not a valid number")
}