WIP Construction Queue (#1648)

* Construction Queue

* Added constructionQueue in addition to currentConstruction: if currentConstruction is done, next construction from Queue is started; if Queue is empty invoke ConstructionAutomation
* Queue utility methods: add, remove, higher prio, lower prio
* Icons to move constructions in queue
* Top left city stats moved to top right panel
* Added current construction and queue construction to top left
* Extended selected construction (containing description) moved to bottom right, it is now displayed alternatively to selected tile
* Max queue size, cannot change queue in puppet city or in other player turn
* Queue and current construction reset on puppeting city

* Cleanup again

* CityStatsTable is a separate class so we can play around with where it's located with minimal code changes

* Rejection reason wrap & removing nothing on enqueue a construction

* Crude UI proposal

* Tutorial updates for queues

Co-authored-by: Yair Morgenstern <yairm210@hotmail.com>
This commit is contained in:
r3versi 2020-01-10 10:41:43 +01:00 committed by Yair Morgenstern
parent 12a98aa4bb
commit 2daf27278d
11 changed files with 625 additions and 550 deletions

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 884 KiB

After

Width:  |  Height:  |  Size: 885 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 KiB

After

Width:  |  Height:  |  Size: 317 KiB

View File

@ -5,7 +5,7 @@ Move a unit!\nClick on a unit > Click on a destination > Click the arrow popup =
Found a city!\nSelect the Settler (flag unit) > Click on 'Found city' (bottom-left corner) =
Enter the city screen!\nClick the city button twice =
Pick a technology to research!\nClick on the tech button (greenish, top left) > \n select technology > click 'Research' (bottom right) =
Pick a construction!\nEnter city screen > Click on a unit or building (left side) =
Pick a construction!\nEnter city screen > Click on a unit or building (bottom left side) > \n click 'add to queue' =
Pass a turn!\nCycle through units with 'Next unit' > Click 'Next turn' =
Reassign worked tiles!\nEnter city screen > click the assigned (green) tile to unassign > \n click an unassigned tile to assign population =
Meet another civilization!\nExplore the map until you encounter another civilization! =

View File

@ -37,7 +37,7 @@ class UncivGame(
/** For when you need to test something in an advanced game and don't have time to faff around */
val superchargedForDebug = false
var rewriteTranslationFiles = true
var rewriteTranslationFiles = false
lateinit var worldScreen: WorldScreen

View File

@ -107,6 +107,7 @@ class CityConstructions {
fun isEnqueued(constructionName: String): Boolean = constructionQueue.contains(constructionName)
fun isQueueFull(): Boolean = constructionQueue.size == queueMaxSize
fun isQueueEmpty(): Boolean = constructionQueue.isEmpty()
fun isBuildingWonder(): Boolean {
val currentConstruction = getCurrentConstruction()
@ -309,8 +310,13 @@ class CityConstructions {
}
fun addToQueue(constructionName: String) {
if (!isQueueFull())
constructionQueue.add(constructionName)
if (!isQueueFull()) {
if (isQueueEmpty() && currentConstruction == "Nothing") {
currentConstruction = constructionName
currentConstructionIsUserSet = true
} else
constructionQueue.add(constructionName)
}
}
fun removeFromQueue(idx: Int) {

View File

@ -2,47 +2,57 @@ package com.unciv.ui.cityscreen
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.Image
import com.badlogic.gdx.scenes.scene2d.ui.Skin
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
import com.badlogic.gdx.scenes.scene2d.ui.Label
import com.badlogic.gdx.scenes.scene2d.ui.*
import com.badlogic.gdx.utils.Align
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.GreatPersonManager
import com.unciv.models.ruleset.Building
import com.unciv.models.translations.tr
import com.unciv.models.stats.Stat
import com.unciv.models.stats.Stats
import com.unciv.models.translations.tr
import com.unciv.ui.utils.*
import com.unciv.ui.worldscreen.optionstable.YesNoPopupTable
import java.text.DecimalFormat
import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.round
class CityInfoTable(private val cityScreen: CityScreen) : Table(CameraStageBaseScreen.skin) {
val pad = 5f
private val pad = 10f
private val showConstructionsTableButton = TextButton("Show construction queue", skin)
private val cityInfoScrollPane: ScrollPane
private val cityInfoTable = Table(skin)
init {
defaults().pad(pad)
width = cityScreen.stage.width/4
showConstructionsTableButton.onClick {
cityScreen.showConstructionsTable = true
cityScreen.update()
}
cityInfoTable.width = cityScreen.stage.width/4
cityInfoTable.background = ImageGetter.getBackground(ImageGetter.getBlue().lerp(Color.BLACK,0.5f))
cityInfoScrollPane = ScrollPane(cityInfoTable.addBorder(2f, Color.WHITE))
cityInfoScrollPane.setOverscroll(false, false)
add(showConstructionsTableButton).left().padLeft(pad).padBottom(pad).row()
add(cityInfoScrollPane).left().row()
}
internal fun update() {
clear()
val cityInfo = cityScreen.city
addCityStats(cityInfo)
addBuildingsInfo(cityInfo)
addStatInfo()
addGreatPersonPointInfo(cityInfo)
cityInfoTable.clear()
//TODO: Sorry, this is a hack, i'm getting tired and needed some content to test the idea
cityInfoTable.apply {
addBuildingsInfo(cityInfo)
addStatInfo()
addGreatPersonPointInfo(cityInfo)
}
getCell(cityInfoScrollPane).maxHeight(stage.height - showConstructionsTableButton.height - pad - 10f)
pack()
}
private fun addCategory(str: String, showHideTable: Table) {
private fun Table.addCategory(str: String, showHideTable: Table) {
val titleTable = Table().background(ImageGetter.getBackground(ImageGetter.getBlue()))
val width = cityScreen.stage.width/4 - 2*pad
val showHideTableWrapper = Table()
@ -56,7 +66,7 @@ class CityInfoTable(private val cityScreen: CityScreen) : Table(CameraStageBaseS
add(showHideTableWrapper).row()
}
fun addBuildingInfo(building: Building, wondersTable: Table){
private fun Table.addBuildingInfo(building: Building, wondersTable: Table){
val wonderNameAndIconTable = Table()
wonderNameAndIconTable.touchable = Touchable.enabled
wonderNameAndIconTable.add(ImageGetter.getConstructionImage(building.name).surroundWithCircle(30f))
@ -96,66 +106,7 @@ class CityInfoTable(private val cityScreen: CityScreen) : Table(CameraStageBaseS
}
}
private fun addCityStats(cityInfo: CityInfo) {
val statsTable = Table().align(Align.center)
addCategory("Stats", statsTable)
val columns = Stats().toHashMap().size
statsTable.add(Label("{Unassigned population}:".tr()
+" "+cityInfo.population.getFreePopulation().toString() + "/" + cityInfo.population.population, skin))
.pad(5f).row()
val turnsToExpansionString : String
if (cityInfo.cityStats.currentCityStats.culture > 0) {
var turnsToExpansion = ceil((cityInfo.expansion.getCultureToNextTile() - cityInfo.expansion.cultureStored)
/ cityInfo.cityStats.currentCityStats.culture).toInt()
if (turnsToExpansion < 1) turnsToExpansion = 1
turnsToExpansionString = "[$turnsToExpansion] turns to expansion".tr()
} else {
turnsToExpansionString = "Stopped expansion".tr()
}
statsTable.add(Label(turnsToExpansionString + " (" + cityInfo.expansion.cultureStored + "/" + cityInfo.expansion.getCultureToNextTile() + ")",
skin)).pad(5f).row()
val turnsToPopString : String
if (cityInfo.cityStats.currentCityStats.food > 0) {
if (cityInfo.cityConstructions.currentConstruction == Constants.settler) {
turnsToPopString = "Food converts to production".tr()
} else {
var turnsToPopulation = ceil((cityInfo.population.getFoodToNextPopulation()-cityInfo.population.foodStored)
/ cityInfo.cityStats.currentCityStats.food).toInt()
if (turnsToPopulation < 1) turnsToPopulation = 1
turnsToPopString = "[$turnsToPopulation] turns to new population".tr()
}
} else if (cityInfo.cityStats.currentCityStats.food < 0) {
val turnsToStarvation = floor(cityInfo.population.foodStored / -cityInfo.cityStats.currentCityStats.food).toInt() + 1
turnsToPopString = "[$turnsToStarvation] turns to lose population".tr()
} else {
turnsToPopString = "Stopped population growth".tr()
}
statsTable.add(Label(turnsToPopString + " (" + cityInfo.population.foodStored + "/" + cityInfo.population.getFoodToNextPopulation() + ")"
,skin)).pad(5f).row()
if (cityInfo.resistanceCounter > 0) {
statsTable.add(Label("In resistance for another [${cityInfo.resistanceCounter}] turns".tr(),skin)).pad(5f).row()
}
statsTable.addSeparator()
val ministatsTable = Table().pad(5f)
ministatsTable.defaults()
statsTable.add(ministatsTable)
for(stat in cityInfo.cityStats.currentCityStats.toHashMap()) {
if(stat.key == Stat.Happiness) continue
ministatsTable.add(ImageGetter.getStatIcon(stat.key.name)).size(20f).padRight(3f)
ministatsTable.add(round(stat.value).toInt().toString().toLabel()).padRight(13f)
}
}
private fun addBuildingsInfo(cityInfo: CityInfo) {
private fun Table.addBuildingsInfo(cityInfo: CityInfo) {
val wonders = mutableListOf<Building>()
val specialistBuildings = mutableListOf<Building>()
val otherBuildings = mutableListOf<Building>()
@ -200,7 +151,7 @@ class CityInfoTable(private val cityScreen: CityScreen) : Table(CameraStageBaseS
}
}
private fun addStatInfo() {
private fun Table.addStatInfo() {
val cityStats = cityScreen.city.cityStats
for(stat in Stat.values().filter { it!=Stat.Happiness }){
@ -257,7 +208,7 @@ class CityInfoTable(private val cityScreen: CityScreen) : Table(CameraStageBaseS
}
}
private fun addGreatPersonPointInfo(cityInfo: CityInfo) {
private fun Table.addGreatPersonPointInfo(cityInfo: CityInfo) {
val greatPersonPoints = cityInfo.getGreatPersonMap()
val statToGreatPerson = GreatPersonManager().statToGreatPersonMapping
for (stat in Stat.values()) {
@ -276,7 +227,7 @@ class CityInfoTable(private val cityScreen: CityScreen) : Table(CameraStageBaseS
}
}
private fun addSpecialistAllocation(skin: Skin, cityInfo: CityInfo) {
private fun Table.addSpecialistAllocation(skin: Skin, cityInfo: CityInfo) {
val specialistAllocationTable = Table()
addCategory("Specialist Allocation", specialistAllocationTable) // todo WRONG, BAD - table should contain all the below specialist stuff
@ -346,5 +297,4 @@ class CityInfoTable(private val cityScreen: CityScreen) : Table(CameraStageBaseS
return specialist
}
}

View File

@ -16,23 +16,26 @@ import com.unciv.ui.map.TileGroupMap
import com.unciv.ui.tilegroups.TileSetStrings
import com.unciv.ui.utils.*
import java.util.*
import kotlin.math.ceil
import kotlin.math.round
class CityScreen(internal val city: CityInfo): CameraStageBaseScreen() {
var selectedTile: TileInfo? = null
var selectedConstruction: IConstruction? = null
/** Toggle between Constructions and cityInfo (buildings, specialists etc. */
var showConstructionsTable = true
// Clockwise from the top-left
/** Displays current production, production queue and available productions list - sits on LEFT */
private var constructionsTable = ConstructionsTable(this)
/** Displays stats, buildings, specialists and stats drilldown - sits on TOP RIGHT */
private var cityInfoTable = CityInfoTable(this)
/** Displays raze city button - sits on TOP CENTER */
private var razeCityButtonHolder = Table()
/** Displays stats, buildings, specialists and stats drilldown - sits on TOP RIGHT */
private var cityInfoTable = CityInfoTable(this)
/** Displays city stats info */
private var cityStatsTable = CityStatsTable(this.city)
/** Displays tile info, alternate with selectedConstructionTable - sits on BOTTOM RIGHT */
private var tileTable = CityScreenTileTable(city)
@ -49,34 +52,36 @@ class CityScreen(internal val city: CityInfo): CameraStageBaseScreen() {
init {
onBackButtonClicked { game.setWorldScreen() }
UncivGame.Current.settings.addCompletedTutorialTask("Enter city screen")
addTiles()
var buildingsTableContainer = Table()
buildingsTableContainer.pad(3f)
buildingsTableContainer.background = ImageGetter.getBackground(ImageGetter.getBlue().lerp(Color.BLACK,0.5f))
cityInfoTable.update()
val buildingsScroll = ScrollPane(cityInfoTable)
buildingsTableContainer.add(buildingsScroll).size(stage.width/4,stage.height / 2)
buildingsTableContainer = buildingsTableContainer.addBorder(2f, Color.WHITE)
buildingsTableContainer.setPosition( stage.width - 5f, stage.height - 5f, Align.topRight)
//stage.setDebugTableUnderMouse(true)
stage.addActor(cityStatsTable)
stage.addActor(constructionsTable)
stage.addActor(tileTable)
stage.addActor(selectedConstructionTable)
stage.addActor(cityPickerTable)
stage.addActor(buildingsTableContainer)
stage.addActor(cityInfoTable)
update()
}
internal fun update() {
if (showConstructionsTable) {
constructionsTable.isVisible = true
cityInfoTable.isVisible = false
} else {
constructionsTable.isVisible = false
cityInfoTable.isVisible = true
}
city.cityStats.update()
constructionsTable.update(selectedConstruction)
constructionsTable.setPosition(5f, stage.height - 5f, Align.topLeft)
cityInfoTable.update()
cityInfoTable.setPosition(5f, stage.height - 5f, Align.topLeft)
cityPickerTable.update()
cityPickerTable.centerX(stage)
@ -86,7 +91,8 @@ class CityScreen(internal val city: CityInfo): CameraStageBaseScreen() {
selectedConstructionTable.update(selectedConstruction)
selectedConstructionTable.setPosition(stage.width - 5f, 5f, Align.bottomRight)
cityInfoTable.update()
cityStatsTable.update()
cityStatsTable.setPosition( stage.width - 5f, stage.height - 5f, Align.topRight)
updateAnnexAndRazeCityButton()
updateTileGroups()

View File

@ -0,0 +1,76 @@
package com.unciv.ui.cityscreen
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.Constants
import com.unciv.logic.city.CityInfo
import com.unciv.models.stats.Stat
import com.unciv.models.translations.tr
import com.unciv.ui.utils.ImageGetter
import com.unciv.ui.utils.addSeparator
import com.unciv.ui.utils.colorFromRGB
import com.unciv.ui.utils.toLabel
import kotlin.math.ceil
import kotlin.math.round
class CityStatsTable(val cityInfo: CityInfo): Table() {
private val innerTable = Table()
init {
pad(2f)
background = ImageGetter.getBackground(colorFromRGB(194,180,131))
innerTable.pad(5f)
innerTable.defaults().pad(5f)
innerTable.background = ImageGetter.getBackground(Color.BLACK.cpy().apply { a=0.8f })
add(innerTable).fill()
}
fun update() {
innerTable.clear()
val unassignedPopString = "{Unassigned population}:".tr()+
" "+cityInfo.population.getFreePopulation().toString() + "/" + cityInfo.population.population
var turnsToExpansionString =
if (cityInfo.cityStats.currentCityStats.culture > 0) {
val remainingCulture = cityInfo.expansion.getCultureToNextTile() - cityInfo.expansion.cultureStored
var turnsToExpansion = ceil(remainingCulture / cityInfo.cityStats.currentCityStats.culture).toInt()
if (turnsToExpansion < 1) turnsToExpansion = 1
"[$turnsToExpansion] turns to expansion".tr()
} else {
"Stopped expansion".tr()
}
turnsToExpansionString += " (" + cityInfo.expansion.cultureStored + "/" +
cityInfo.expansion.getCultureToNextTile() + ")"
var turnsToPopString =
when {
cityInfo.isGrowing() -> "[${cityInfo.getNumTurnsToNewPopulation()}] turns to new population".tr()
cityInfo.isStarving() -> "[${cityInfo.getNumTurnsToStarvation()}] turns to lose population".tr()
cityInfo.cityConstructions.currentConstruction == Constants.settler -> "Food converts to production".tr()
else -> "Stopped population growth".tr()
}
turnsToPopString += " (" + cityInfo.population.foodStored + "/" + cityInfo.population.getFoodToNextPopulation() + ")"
val ministatsTable = Table().pad(5f)
ministatsTable.defaults()
for(stat in cityInfo.cityStats.currentCityStats.toHashMap()) {
if(stat.key == Stat.Happiness) continue
ministatsTable.add(ImageGetter.getStatIcon(stat.key.name)).size(20f).padRight(3f)
ministatsTable.add(round(stat.value).toInt().toString().toLabel()).padRight(13f)
}
innerTable.add(unassignedPopString.toLabel()).row()
innerTable.add(turnsToExpansionString.toLabel()).row()
innerTable.add(turnsToPopString.toLabel()).row()
if (cityInfo.isInResistance())
innerTable.add("In resistance for another [${cityInfo.resistanceCounter}] turns".toLabel()).row()
innerTable.addSeparator()
innerTable.add(ministatsTable)
pack()
}
}

View File

@ -2,17 +2,17 @@ package com.unciv.ui.cityscreen
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.*
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
import com.badlogic.gdx.utils.Align
import com.unciv.UncivGame
import com.unciv.logic.city.CityInfo
import com.unciv.logic.city.IConstruction
import com.unciv.logic.city.SpecialConstruction
import com.unciv.models.UncivSound
import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.translations.tr
import com.unciv.models.stats.Stat
import com.unciv.models.translations.tr
import com.unciv.ui.utils.*
import com.unciv.ui.worldscreen.optionstable.YesNoPopupTable
@ -20,6 +20,7 @@ class ConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBaseScre
/* -2 = Nothing, -1 = current construction, >= 0 queue entry */
private var selectedQueueEntry = -2 // None
private val showCityInfoTableButton: TextButton
private val constructionsQueueScrollPane: ScrollPane
private val availableConstructionsScrollPane: ScrollPane
@ -30,12 +31,21 @@ class ConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBaseScre
private val pad = 10f
init {
showCityInfoTableButton = TextButton("Show stats drilldown", skin)
showCityInfoTableButton.onClick {
cityScreen.showConstructionsTable = false
cityScreen.update()
}
constructionsQueueScrollPane = ScrollPane(constructionsQueueTable.addBorder(2f, Color.WHITE))
constructionsQueueScrollPane.setOverscroll(false,false)
availableConstructionsScrollPane = ScrollPane(availableConstructionsTable.addBorder(2f, Color.WHITE))
availableConstructionsScrollPane.setOverscroll(false,false)
constructionsQueueTable.background = ImageGetter.getBackground(Color.BLACK)
availableConstructionsTable.background = ImageGetter.getBackground(Color.BLACK)
add(showCityInfoTableButton).left().padLeft(pad).padBottom(pad).row()
add(constructionsQueueScrollPane).left().padBottom(pad).row()
add(buttons).center().bottom().padBottom(pad).row()
add(availableConstructionsScrollPane).left().bottom().row()
@ -57,7 +67,7 @@ class ConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBaseScre
// Need to pack before computing space left for bottom panel
pack()
val usedHeight = constructionsQueueScrollPane.height + buttons.height + 2f * pad + 10f
val usedHeight = showCityInfoTableButton.height + constructionsQueueScrollPane.height + buttons.height + 3f * pad + 10f
updateAvailableConstructions()
availableConstructionsScrollPane.layout()
@ -103,7 +113,8 @@ class ConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBaseScre
queue.forEachIndexed { i, constructionName ->
constructionsQueueTable.add(getQueueEntry(i, constructionName, i == queue.size - 1, i == selectedQueueEntry))
.expandX().fillX().row()
constructionsQueueTable.addSeparator()
if (i != queue.size - 1)
constructionsQueueTable.addSeparator()
}
} else
constructionsQueueTable.add("Queue empty".toLabel()).pad(2f).row()
@ -206,7 +217,8 @@ class ConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBaseScre
} else {
pickProductionButton.color = Color.GRAY
pickProductionButton.row()
pickProductionButton.add(rejectionReason.toLabel(Color.RED)).colspan(pickProductionButton.columns).fillX().left().padTop(2f)
pickProductionButton.add(rejectionReason.toLabel(Color.RED).apply{ setWrap(true)} )
.colspan(pickProductionButton.columns).fillX().left().padTop(2f)
}
return pickProductionButton
@ -259,10 +271,14 @@ class ConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBaseScre
if (construction != null
&& construction.canBePurchased()
&& UncivGame.Current.worldScreen.isPlayersTurn
&& !city.isPuppet) {
&& !city.isPuppet
&& !city.isInResistance()) {
val constructionGoldCost = construction.getGoldCost(city.civInfo)
purchaseConstructionButton = TextButton("Buy for [$constructionGoldCost] gold".tr(), CameraStageBaseScreen.skin)
purchaseConstructionButton.onClick(UncivSound.Coin) {
button.setText("Buy".tr() + " " + constructionGoldCost)
button.add(ImageGetter.getStatIcon(Stat.Gold.name)).size(20f).padBottom(2f)
button.onClick(UncivSound.Coin) {
YesNoPopupTable("Would you like to purchase [${construction.name}] for [$constructionGoldCost] gold?".tr(), {
cityConstructions.purchaseConstruction(construction.name)
if (isSelectedQueueEntry()) {

View File

@ -26,15 +26,7 @@ import com.unciv.ui.pickerscreens.PolicyPickerScreen
import com.unciv.ui.pickerscreens.TechButton
import com.unciv.ui.pickerscreens.TechPickerScreen
import com.unciv.ui.trade.DiplomacyScreen
import com.unciv.ui.utils.CameraStageBaseScreen
import com.unciv.ui.utils.ImageGetter
import com.unciv.ui.utils.centerX
import com.unciv.ui.utils.colorFromRGB
import com.unciv.ui.utils.disable
import com.unciv.ui.utils.enable
import com.unciv.ui.utils.onClick
import com.unciv.ui.utils.setFontSize
import com.unciv.ui.utils.toLabel
import com.unciv.ui.utils.*
import com.unciv.ui.worldscreen.bottombar.BattleTable
import com.unciv.ui.worldscreen.bottombar.TileInfoTable
import com.unciv.ui.worldscreen.optionstable.OnlineMultiplayer
@ -234,7 +226,8 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
return "Pick a technology to research!\nClick on the tech button (greenish, top left) > " +
"\n select technology > click 'Research' (bottom right)"
if(!completedTasks.contains("Pick construction"))
return "Pick a construction!\nEnter city screen > Click on a unit or building (left side)"
return "Pick a construction!\nEnter city screen > Click on a unit or building (bottom left side) >" +
" \n click 'add to queue'"
if(!completedTasks.contains("Pass a turn"))
return "Pass a turn!\nCycle through units with 'Next unit' > Click 'Next turn'"
if(!completedTasks.contains("Reassign worked tiles"))