mirror of
https://github.com/yairm210/Unciv.git
synced 2025-02-11 11:28:03 +07:00
Add Replay feature in VictoryScreen (#8844)
* Add Replay feature in VictoryScreen.
* Add Replay feature in VictoryScreen.
* Support for i18n
* Extract year to text conversion into common util to be used from VictoryScreen.kt and WorldScreenTopBar.kt
* Remove ReplayMapTile and modify MinimapTile so that it can support both use cases.
* Reuse code for spreading out tiles onto tile layer between Minimap and ReplayMap by factoring it out into a new MinimapTileUtil
* Revert "Reuse code for spreading out tiles onto tile layer between Minimap and ReplayMap by factoring it out into a new MinimapTileUtil"
This reverts commit d4cddb4312
.
* Add Replay feature in VictoryScreen.
* Add Replay feature in VictoryScreen.
* Support for i18n
* Extract year to text conversion into common util to be used from VictoryScreen.kt and WorldScreenTopBar.kt
* Remove ReplayMapTile and modify MinimapTile so that it can support both use cases.
* Revert some unintentional indentation changes
* Refactor some common logic of Minimap and ReplayMap into MinimapTileUtil
* Slightly increase ReplayMap size and simplify logic to calculate tile size since input is static.
* Indentation again... :|
* Unify isCityCenter & isCapital into an enum in TileHistory and shorten identifiers
* Use city.getTiles() instead of city.tiles in CityInfoConquestFunctions.kt
* Improve tileSize calculation in ReplayMap.kt
* Remove extra padding in VictoryScreen -> Replay to prevent WorldScreenTopBar from acting up on the next turn.
* Make return value of MinimapTileUtil.spreadOutMinimapTiles more useful to callers
* Cancel Replay timer when VictoryScreen is disposed or when Replay is opened again.
* Cancel replay map timer task whenever tab is switched in VictoryScreen
* Improve serialization for TileHistory by using a custom serializer. This removes the need for holding two copies of the same thing and to use String based keys.
* Add backwards compatibility for replay. The replay will start at the turn where it came into play.
* Remove debugging code :|
* Use gameInfo field rather than going throug the global UncivGame...
This commit is contained in:
parent
680da3232f
commit
f4dca2281e
@ -1314,6 +1314,7 @@ Vote for [civilizationName] =
|
||||
Continue =
|
||||
Abstained =
|
||||
Vote for World Leader =
|
||||
Replay =
|
||||
|
||||
# Capturing a city
|
||||
|
||||
|
@ -4,6 +4,7 @@ import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.files.FileHandle
|
||||
import com.badlogic.gdx.utils.Json
|
||||
import com.badlogic.gdx.utils.SerializationException
|
||||
import com.unciv.logic.map.tile.TileHistory
|
||||
import com.unciv.ui.components.KeyCharAndCode
|
||||
import com.unciv.ui.components.KeyboardBindings
|
||||
import java.time.Duration
|
||||
@ -25,6 +26,7 @@ fun json() = Json().apply {
|
||||
setSerializer(Duration::class.java, DurationSerializer())
|
||||
setSerializer(KeyCharAndCode::class.java, KeyCharAndCode.Serializer())
|
||||
setSerializer(KeyboardBindings::class.java, KeyboardBindings.Serializer())
|
||||
setSerializer(TileHistory::class.java, TileHistory.Serializer())
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -207,4 +207,12 @@ object BackwardCompatibility {
|
||||
gameParameters.gameSpeed = ""
|
||||
}
|
||||
}
|
||||
|
||||
fun GameInfo.migrateToTileHistory() {
|
||||
if (historyStartTurn >= 0) return
|
||||
for (tile in getCities().flatMap { it.getTiles() }) {
|
||||
tile.history.recordTakeOwnership(tile)
|
||||
}
|
||||
historyStartTurn = turns
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import com.unciv.logic.BackwardCompatibility.convertFortify
|
||||
import com.unciv.logic.BackwardCompatibility.convertOldGameSpeed
|
||||
import com.unciv.logic.BackwardCompatibility.guaranteeUnitPromotions
|
||||
import com.unciv.logic.BackwardCompatibility.migrateBarbarianCamps
|
||||
import com.unciv.logic.BackwardCompatibility.migrateToTileHistory
|
||||
import com.unciv.logic.BackwardCompatibility.removeMissingModReferences
|
||||
import com.unciv.logic.GameInfo.Companion.CURRENT_COMPATIBILITY_NUMBER
|
||||
import com.unciv.logic.GameInfo.Companion.FIRST_WITHOUT
|
||||
@ -103,6 +104,17 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
|
||||
// Set to false whenever the results still need te be processed
|
||||
var diplomaticVictoryVotesProcessed = false
|
||||
|
||||
/** The turn the replay history started recording.
|
||||
*
|
||||
* * `-1` means the game was serialized with an older version without replay
|
||||
* * `0` would be the normal value in any newer game
|
||||
* (remember gameParameters.startingEra is not implemented through turns starting > 0)
|
||||
* * `>0` would be set by compatibility migration, handled in [BackwardCompatibility.migrateToTileHistory]
|
||||
*
|
||||
* @see [com.unciv.logic.map.tile.TileHistory]
|
||||
*/
|
||||
var historyStartTurn = -1
|
||||
|
||||
/**
|
||||
* Keep track of a custom location this game was saved to _or_ loaded from, using it as the default custom location for any further save/load attempts.
|
||||
*/
|
||||
@ -164,6 +176,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
|
||||
toReturn.oneMoreTurnMode = oneMoreTurnMode
|
||||
toReturn.customSaveLocation = customSaveLocation
|
||||
toReturn.victoryData = victoryData
|
||||
toReturn.historyStartTurn = historyStartTurn
|
||||
|
||||
return toReturn
|
||||
}
|
||||
@ -591,6 +604,8 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
|
||||
cityDistances.game = this
|
||||
|
||||
guaranteeUnitPromotions()
|
||||
|
||||
migrateToTileHistory()
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
@ -98,6 +98,8 @@ object GameStarter {
|
||||
}
|
||||
|
||||
runAndMeasure("setTransients") {
|
||||
// mark as no migrateToTileHistory necessary
|
||||
gameInfo.historyStartTurn = 0
|
||||
tileMap.setTransients(ruleset) // if we're starting from a map with pre-placed units, they need the civs to exist first
|
||||
tileMap.setStartingLocationsTransients()
|
||||
|
||||
|
@ -149,6 +149,8 @@ class CityExpansionManager : IsPartOfGameInfoSerialization {
|
||||
|
||||
city.civ.cache.updateCivResources()
|
||||
city.cityStats.update()
|
||||
|
||||
tile.history.recordRelinquishOwnership(tile)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -175,6 +177,8 @@ class CityExpansionManager : IsPartOfGameInfoSerialization {
|
||||
unit.movement.teleportToClosestMoveableTile()
|
||||
|
||||
city.civ.cache.updateViewableTiles()
|
||||
|
||||
tile.history.recordTakeOwnership(tile)
|
||||
}
|
||||
|
||||
fun nextTurn(culture: Float) {
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
import com.unciv.Constants
|
||||
import com.unciv.GUI
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.battle.Battle
|
||||
import com.unciv.logic.city.City
|
||||
import com.unciv.logic.city.CityFlags
|
||||
@ -324,6 +323,11 @@ class CityInfoConquestFunctions(val city: City){
|
||||
// Update proximity rankings
|
||||
civ.updateProximity(oldCiv,
|
||||
oldCiv.updateProximity(civ))
|
||||
|
||||
// Update history
|
||||
city.getTiles().forEach { tile ->
|
||||
tile.history.recordTakeOwnership(tile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,6 +130,8 @@ open class Tile : IsPartOfGameInfoSerialization {
|
||||
var hasBottomRiver = false
|
||||
var hasBottomLeftRiver = false
|
||||
|
||||
var history: TileHistory = TileHistory()
|
||||
|
||||
private var continent = -1
|
||||
|
||||
val latitude: Float
|
||||
@ -169,6 +171,7 @@ open class Tile : IsPartOfGameInfoSerialization {
|
||||
toReturn.hasBottomRiver = hasBottomRiver
|
||||
toReturn.continent = continent
|
||||
toReturn.exploredBy.addAll(exploredBy)
|
||||
toReturn.history = history.clone()
|
||||
return toReturn
|
||||
}
|
||||
|
||||
|
93
core/src/com/unciv/logic/map/tile/TileHistory.kt
Normal file
93
core/src/com/unciv/logic/map/tile/TileHistory.kt
Normal file
@ -0,0 +1,93 @@
|
||||
package com.unciv.logic.map.tile
|
||||
|
||||
import com.badlogic.gdx.utils.Json
|
||||
import com.badlogic.gdx.utils.JsonValue
|
||||
import com.unciv.logic.IsPartOfGameInfoSerialization
|
||||
import com.unciv.logic.map.tile.TileHistory.TileHistoryState.CityCenterType
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Records events throughout the game related to a tile.
|
||||
*
|
||||
* Used for end of game replay.
|
||||
*
|
||||
* @see com.unciv.ui.screens.victoryscreen.ReplayMap
|
||||
*/
|
||||
open class TileHistory : IsPartOfGameInfoSerialization {
|
||||
|
||||
class TileHistoryState(
|
||||
/** The name of the civilization owning this tile or `null` if there is no owner. */
|
||||
var owningCivName: String? = null,
|
||||
/** `null` if this tile does not have a city center. Otherwise this field denotes of which type this city center is. */
|
||||
var cityCenterType: CityCenterType = CityCenterType.None
|
||||
) : IsPartOfGameInfoSerialization {
|
||||
enum class CityCenterType(val serializedRepresentation: String) {
|
||||
None("N"),
|
||||
Regular("R"),
|
||||
Capital("C");
|
||||
|
||||
companion object {
|
||||
fun deserialize(s: String): CityCenterType =
|
||||
values().firstOrNull { it.serializedRepresentation == s } ?: None
|
||||
}
|
||||
}
|
||||
|
||||
constructor(tile: Tile) : this(
|
||||
tile.getOwner()?.civName,
|
||||
when {
|
||||
!tile.isCityCenter() -> CityCenterType.None
|
||||
tile.getCity()?.isCapital() == true -> CityCenterType.Capital
|
||||
else -> CityCenterType.Regular
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/** History records by turn. */
|
||||
private var history: TreeMap<Int, TileHistoryState> = TreeMap()
|
||||
|
||||
fun recordTakeOwnership(tile: Tile) {
|
||||
history[tile.tileMap.gameInfo.turns] =
|
||||
TileHistoryState(tile)
|
||||
}
|
||||
|
||||
fun recordRelinquishOwnership(tile: Tile) {
|
||||
history[tile.tileMap.gameInfo.turns] =
|
||||
TileHistoryState()
|
||||
}
|
||||
|
||||
fun getState(turn: Int): TileHistoryState {
|
||||
return history.floorEntry(turn)?.value ?: TileHistoryState()
|
||||
}
|
||||
|
||||
fun clone(): TileHistory {
|
||||
val toReturn = TileHistory()
|
||||
toReturn.history = TreeMap(history)
|
||||
return toReturn
|
||||
}
|
||||
|
||||
/** Custom Json formatter for a [TileHistory].
|
||||
* Output looks like this: `history:{0:[Spain,C],12:[China,R]}`
|
||||
*/
|
||||
class Serializer : Json.Serializer<TileHistory> {
|
||||
override fun write(json: Json, `object`: TileHistory, knownType: Class<*>?) {
|
||||
json.writeObjectStart()
|
||||
for ((key, entry) in `object`.history) {
|
||||
json.writeArrayStart(key.toString())
|
||||
json.writeValue(entry.owningCivName)
|
||||
json.writeValue(entry.cityCenterType.serializedRepresentation)
|
||||
json.writeArrayEnd()
|
||||
}
|
||||
json.writeObjectEnd()
|
||||
}
|
||||
|
||||
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?) = TileHistory().apply {
|
||||
for (entry in jsonData) {
|
||||
val turn = entry.name.toInt()
|
||||
val owningCivName =
|
||||
(if (entry[0].isString) entry.getString(0) else "").takeUnless { it.isEmpty() }
|
||||
val cityCenterType = CityCenterType.deserialize(entry.getString(1))
|
||||
history[turn] = TileHistoryState(owningCivName, cityCenterType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
14
core/src/com/unciv/ui/components/YearTextUtil.kt
Normal file
14
core/src/com/unciv/ui/components/YearTextUtil.kt
Normal file
@ -0,0 +1,14 @@
|
||||
package com.unciv.ui.components
|
||||
|
||||
import com.unciv.models.translations.tr
|
||||
import kotlin.math.abs
|
||||
|
||||
object YearTextUtil {
|
||||
|
||||
/** Converts a year to a human-readable year (e.g. "1800 AD" or "3000 BC") while respecting the Maya calendar. */
|
||||
fun toYearText(year: Int, usesMayaCalendar: Boolean): String {
|
||||
val yearText = if (usesMayaCalendar) MayaCalendar.yearToMayaDate(year)
|
||||
else "[" + abs(year) + "] " + (if (year < 0) "BC" else "AD")
|
||||
return yearText.tr()
|
||||
}
|
||||
}
|
72
core/src/com/unciv/ui/screens/victoryscreen/ReplayMap.kt
Normal file
72
core/src/com/unciv/ui/screens/victoryscreen/ReplayMap.kt
Normal file
@ -0,0 +1,72 @@
|
||||
package com.unciv.ui.screens.victoryscreen
|
||||
|
||||
import com.badlogic.gdx.graphics.g2d.Batch
|
||||
import com.badlogic.gdx.scenes.scene2d.Group
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.map.TileMap
|
||||
import com.unciv.ui.screens.worldscreen.minimap.MinimapTile
|
||||
import com.unciv.ui.screens.worldscreen.minimap.MinimapTileUtil
|
||||
import kotlin.math.min
|
||||
|
||||
// Mostly copied from MiniMap
|
||||
class ReplayMap(val tileMap: TileMap) : Group() {
|
||||
private val tileLayer = Group()
|
||||
private val minimapTiles: List<MinimapTile>
|
||||
|
||||
init {
|
||||
// don't try to resize rotate etc - this table has a LOT of children so that's valuable
|
||||
// render time!
|
||||
isTransform = false
|
||||
|
||||
val tileSize = calcTileSize()
|
||||
minimapTiles = createReplayMap(tileSize)
|
||||
val tileExtension = MinimapTileUtil.spreadOutMinimapTiles(tileLayer, minimapTiles, tileSize)
|
||||
|
||||
for (group in tileLayer.children) {
|
||||
group.moveBy(-tileExtension.x, -tileExtension.y)
|
||||
}
|
||||
|
||||
// there are tiles "below the zero",
|
||||
// so we zero out the starting position of the whole board so they will be displayed as well
|
||||
tileLayer.setSize(tileExtension.width, tileExtension.height)
|
||||
setSize(tileLayer.width, tileLayer.height)
|
||||
addActor(tileLayer)
|
||||
}
|
||||
|
||||
private fun calcTileSize(): Float {
|
||||
val mapIsNotRectangular =
|
||||
tileMap.mapParameters.shape != com.unciv.logic.map.MapShape.rectangular
|
||||
val tileRows = with(tileMap.mapParameters.mapSize) {
|
||||
if (mapIsNotRectangular) radius * 2 + 1 else height
|
||||
}
|
||||
val tileColumns = with(tileMap.mapParameters.mapSize) {
|
||||
if (mapIsNotRectangular) radius * 2 + 1 else width
|
||||
}
|
||||
// 200 is about how much space we need for the top navigation and close button at the
|
||||
// bottom.
|
||||
val tileSizeToFitHeight = (UncivGame.Current.worldScreen!!.stage.height - 200) / tileRows
|
||||
val tileSizeToFitWidth = UncivGame.Current.worldScreen!!.stage.width / tileColumns
|
||||
return min(tileSizeToFitHeight, tileSizeToFitWidth)
|
||||
}
|
||||
|
||||
private fun createReplayMap(tileSize: Float): List<MinimapTile> {
|
||||
val tiles = ArrayList<MinimapTile>()
|
||||
for (tile in tileMap.values) {
|
||||
val minimapTile = MinimapTile(tile, tileSize) {}
|
||||
tiles.add(minimapTile)
|
||||
}
|
||||
return tiles
|
||||
}
|
||||
|
||||
|
||||
fun update(turn: Int) {
|
||||
for (minimapTile in minimapTiles) {
|
||||
minimapTile.updateColor(false, turn)
|
||||
minimapTile.updateBorders(turn).updateActorsIn(this)
|
||||
minimapTile.updateCityCircle(turn).updateActorsIn(this)
|
||||
}
|
||||
}
|
||||
|
||||
// For debugging purposes
|
||||
override fun draw(batch: Batch?, parentAlpha: Float) = super.draw(batch, parentAlpha)
|
||||
}
|
@ -1,22 +1,25 @@
|
||||
package com.unciv.ui.screens.victoryscreen
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Label
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.badlogic.gdx.utils.Align
|
||||
import com.badlogic.gdx.utils.Timer
|
||||
import com.unciv.Constants
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.civilization.Civilization
|
||||
import com.unciv.models.metadata.GameSetupInfo
|
||||
import com.unciv.models.ruleset.Victory
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.screens.newgamescreen.NewGameScreen
|
||||
import com.unciv.ui.screens.pickerscreens.PickerScreen
|
||||
import com.unciv.ui.components.YearTextUtil
|
||||
import com.unciv.ui.components.extensions.addSeparator
|
||||
import com.unciv.ui.components.extensions.enable
|
||||
import com.unciv.ui.components.extensions.onClick
|
||||
import com.unciv.ui.components.extensions.toLabel
|
||||
import com.unciv.ui.components.extensions.toTextButton
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.screens.newgamescreen.NewGameScreen
|
||||
import com.unciv.ui.screens.pickerscreens.PickerScreen
|
||||
import com.unciv.ui.screens.worldscreen.WorldScreen
|
||||
|
||||
class VictoryScreen(val worldScreen: WorldScreen) : PickerScreen() {
|
||||
@ -27,6 +30,8 @@ class VictoryScreen(val worldScreen: WorldScreen) : PickerScreen() {
|
||||
|
||||
private val contentsTable = Table()
|
||||
|
||||
private var replayTimer : Timer.Task? = null
|
||||
|
||||
init {
|
||||
val difficultyLabel = ("{Difficulty}: {${gameInfo.difficulty}}").toLabel()
|
||||
difficultyLabel.setPosition(10f, stage.height - 10, Align.topLeft)
|
||||
@ -41,9 +46,6 @@ class VictoryScreen(val worldScreen: WorldScreen) : PickerScreen() {
|
||||
val rankingLabel = if (UncivGame.Current.settings.useDemographics) "Demographics" else "Rankings"
|
||||
val setCivRankingsButton = rankingLabel.toTextButton().onClick { setCivRankingsTable() }
|
||||
tabsTable.add(setCivRankingsButton)
|
||||
topTable.add(tabsTable)
|
||||
topTable.addSeparator()
|
||||
topTable.add(contentsTable)
|
||||
|
||||
if (playerCivInfo.isSpectator())
|
||||
setGlobalVictoryTable()
|
||||
@ -72,6 +74,16 @@ class VictoryScreen(val worldScreen: WorldScreen) : PickerScreen() {
|
||||
} else if (!someoneHasWon) {
|
||||
setDefaultCloseAction()
|
||||
}
|
||||
|
||||
if (playerCivInfo.isSpectator() || someoneHasWon || playerCivInfo.isDefeated()) {
|
||||
val replayLabel = "Replay"
|
||||
val replayButton = replayLabel.toTextButton().onClick { setReplayTable() }
|
||||
tabsTable.add(replayButton)
|
||||
}
|
||||
|
||||
topTable.add(tabsTable)
|
||||
topTable.addSeparator()
|
||||
topTable.add(contentsTable)
|
||||
}
|
||||
|
||||
|
||||
@ -121,7 +133,7 @@ class VictoryScreen(val worldScreen: WorldScreen) : PickerScreen() {
|
||||
ourVictoryStatusTable.add(victory.value.victoryScreenHeader.toLabel())
|
||||
}
|
||||
|
||||
contentsTable.clear()
|
||||
resetContent()
|
||||
contentsTable.add(ourVictoryStatusTable)
|
||||
}
|
||||
|
||||
@ -156,7 +168,7 @@ class VictoryScreen(val worldScreen: WorldScreen) : PickerScreen() {
|
||||
globalVictoryTable.add(getGlobalVictoryColumn(majorCivs, victory.key))
|
||||
}
|
||||
|
||||
contentsTable.clear()
|
||||
resetContent()
|
||||
contentsTable.add(globalVictoryTable)
|
||||
}
|
||||
|
||||
@ -181,12 +193,47 @@ class VictoryScreen(val worldScreen: WorldScreen) : PickerScreen() {
|
||||
|
||||
private fun setCivRankingsTable() {
|
||||
val majorCivs = gameInfo.civilizations.filter { it.isMajorCiv() }
|
||||
contentsTable.clear()
|
||||
resetContent()
|
||||
|
||||
if (UncivGame.Current.settings.useDemographics) contentsTable.add(buildDemographicsTable(majorCivs))
|
||||
else contentsTable.add(buildRankingsTable(majorCivs))
|
||||
}
|
||||
|
||||
private fun setReplayTable() {
|
||||
val replayTable = Table().apply { defaults().pad(10f) }
|
||||
val yearLabel = "".toLabel()
|
||||
replayTable.add(yearLabel).row()
|
||||
val replayMap = ReplayMap(gameInfo.tileMap)
|
||||
replayTable.add(replayMap).row()
|
||||
|
||||
var nextTurn = gameInfo.historyStartTurn
|
||||
val finalTurn = gameInfo.turns
|
||||
resetContent()
|
||||
replayTimer = Timer.schedule(
|
||||
object : Timer.Task() {
|
||||
override fun run() {
|
||||
updateReplayTable(yearLabel, replayMap, nextTurn++)
|
||||
}
|
||||
}, 0.0f,
|
||||
// A game of 600 rounds will take one minute.
|
||||
0.1f,
|
||||
// End at the last turn.
|
||||
finalTurn - nextTurn
|
||||
)
|
||||
contentsTable.add(replayTable)
|
||||
}
|
||||
|
||||
private fun updateReplayTable(yearLabel: Label, replayMap: ReplayMap, turn: Int) {
|
||||
val finalTurn = gameInfo.turns
|
||||
val year = gameInfo.getYear(turn - finalTurn)
|
||||
yearLabel.setText(
|
||||
YearTextUtil.toYearText(
|
||||
year, gameInfo.currentPlayerCiv.isLongCountDisplay()
|
||||
)
|
||||
)
|
||||
replayMap.update(turn)
|
||||
}
|
||||
|
||||
enum class RankLabels { Rank, Value, Best, Average, Worst}
|
||||
private fun buildDemographicsTable(majorCivs: List<Civilization>): Table {
|
||||
val demographicsTable = Table().apply { defaults().pad(5f) }
|
||||
@ -291,4 +338,14 @@ class VictoryScreen(val worldScreen: WorldScreen) : PickerScreen() {
|
||||
civGroup.pack()
|
||||
return civGroup
|
||||
}
|
||||
|
||||
private fun resetContent() {
|
||||
replayTimer?.cancel()
|
||||
contentsTable.clear()
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
super.dispose()
|
||||
replayTimer?.cancel()
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.components.Fonts
|
||||
import com.unciv.ui.components.MayaCalendar
|
||||
import com.unciv.ui.components.UncivTooltip.Companion.addTooltip
|
||||
import com.unciv.ui.components.YearTextUtil
|
||||
import com.unciv.ui.components.extensions.colorFromRGB
|
||||
import com.unciv.ui.components.extensions.darken
|
||||
import com.unciv.ui.components.extensions.onClick
|
||||
@ -34,7 +35,6 @@ import com.unciv.ui.screens.pickerscreens.PolicyPickerScreen
|
||||
import com.unciv.ui.screens.pickerscreens.TechPickerScreen
|
||||
import com.unciv.ui.screens.victoryscreen.VictoryScreen
|
||||
import com.unciv.ui.screens.worldscreen.mainmenu.WorldScreenMenuPopup
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.max
|
||||
import kotlin.math.roundToInt
|
||||
@ -340,11 +340,10 @@ class WorldScreenTopBar(val worldScreen: WorldScreen) : Table() {
|
||||
}
|
||||
|
||||
private fun updateResourcesTable(civInfo: Civilization) {
|
||||
val year = civInfo.gameInfo.getYear()
|
||||
val yearText = if (civInfo.isLongCountDisplay()) MayaCalendar.yearToMayaDate(year)
|
||||
else "[" + abs(year) + "] " + (if (year < 0) "BC" else "AD")
|
||||
turnsLabel.setText(Fonts.turn + "" + civInfo.gameInfo.turns + " | " + yearText.tr())
|
||||
|
||||
val yearText = YearTextUtil.toYearText(
|
||||
civInfo.gameInfo.getYear(), civInfo.isLongCountDisplay()
|
||||
)
|
||||
turnsLabel.setText(Fonts.turn + "" + civInfo.gameInfo.turns + " | " + yearText)
|
||||
resourcesWrapper.clearChildren()
|
||||
var firstPadLeft = 20f // We want a distance from the turns entry to the first resource, but only if any resource is displayed
|
||||
val civResources = civInfo.getCivResources()
|
||||
|
@ -32,11 +32,6 @@ class Minimap(val mapHolder: WorldMapHolder, minimapSize: Int, private val civIn
|
||||
// don't try to resize rotate etc - this table has a LOT of children so that's valuable render time!
|
||||
isTransform = false
|
||||
|
||||
var topX = -Float.MAX_VALUE
|
||||
var topY = -Float.MAX_VALUE
|
||||
var bottomX = Float.MAX_VALUE
|
||||
var bottomY = Float.MAX_VALUE
|
||||
|
||||
// Set fixed minimap size
|
||||
val stageMinimapSize = calcMinimapSize(minimapSize)
|
||||
setSize(stageMinimapSize.x, stageMinimapSize.y)
|
||||
@ -44,25 +39,19 @@ class Minimap(val mapHolder: WorldMapHolder, minimapSize: Int, private val civIn
|
||||
// Calculate max tileSize to fit in mimimap
|
||||
tileSize = calcTileSize(stageMinimapSize)
|
||||
minimapTiles = createMinimapTiles(tileSize)
|
||||
for (image in minimapTiles.map { it.image }) {
|
||||
tileLayer.addActor(image)
|
||||
|
||||
// keeps track of the current top/bottom/left/rightmost tiles to size and position the minimap correctly
|
||||
topX = max(topX, image.x + tileSize)
|
||||
topY = max(topY, image.y + tileSize)
|
||||
bottomX = min(bottomX, image.x)
|
||||
bottomY = min(bottomY, image.y)
|
||||
}
|
||||
|
||||
val tileExtension = MinimapTileUtil.spreadOutMinimapTiles(tileLayer, minimapTiles, tileSize)
|
||||
// there are tiles "below the zero",
|
||||
// so we zero out the starting position of the whole board so they will be displayed as well
|
||||
tileLayer.setSize(width, height)
|
||||
|
||||
// Center tiles in minimap holder
|
||||
tileMapWidth = topX - bottomX
|
||||
tileMapHeight = topY - bottomY
|
||||
val padX = (stageMinimapSize.x - tileMapWidth) * 0.5f - bottomX
|
||||
val padY = (stageMinimapSize.y - tileMapHeight) * 0.5f - bottomY
|
||||
tileMapWidth = tileExtension.width
|
||||
tileMapHeight = tileExtension.height
|
||||
val padX =
|
||||
(stageMinimapSize.x - tileMapWidth) * 0.5f - (tileExtension.x)
|
||||
val padY =
|
||||
(stageMinimapSize.y - tileMapHeight) * 0.5f - (tileExtension.y)
|
||||
for (group in tileLayer.children) {
|
||||
group.moveBy(padX, padY)
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import com.badlogic.gdx.utils.Align
|
||||
import com.unciv.logic.map.HexMath
|
||||
import com.unciv.logic.civilization.Civilization
|
||||
import com.unciv.logic.map.tile.Tile
|
||||
import com.unciv.logic.map.tile.TileHistory.TileHistoryState.CityCenterType
|
||||
import com.unciv.ui.images.IconCircleGroup
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.components.extensions.onClick
|
||||
@ -16,7 +17,7 @@ import com.unciv.utils.DebugUtils
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.atan
|
||||
|
||||
internal class MinimapTile(val tile: Tile, tileSize: Float, val onClick: () -> Unit) {
|
||||
class MinimapTile(val tile: Tile, tileSize: Float, val onClick: () -> Unit) {
|
||||
val image: Image = ImageGetter.getImage("OtherIcons/Hexagon")
|
||||
private var cityCircleImage: IconCircleGroup? = null
|
||||
var owningCiv: Civilization? = null
|
||||
@ -35,12 +36,15 @@ internal class MinimapTile(val tile: Tile, tileSize: Float, val onClick: () -> U
|
||||
image.onClick(onClick)
|
||||
}
|
||||
|
||||
fun updateColor(isTileUnrevealed: Boolean) {
|
||||
fun updateColor(isTileUnrevealed: Boolean, turn: Int? = null) {
|
||||
image.isVisible = DebugUtils.VISIBLE_MAP || !isTileUnrevealed
|
||||
if (!image.isVisible) return
|
||||
val isCityCenter =
|
||||
if (turn == null) tile.isCityCenter() else tile.history.getState(turn).cityCenterType != CityCenterType.None
|
||||
val owningCiv = if (turn == null) tile.getOwner() else getOwningCivFromHistory(tile, turn)
|
||||
image.color = when {
|
||||
tile.isCityCenter() && !tile.isWater -> tile.getOwner()!!.nation.getInnerColor()
|
||||
tile.getCity() != null && !tile.isWater -> tile.getOwner()!!.nation.getOuterColor()
|
||||
isCityCenter && !tile.isWater -> owningCiv!!.nation.getInnerColor()
|
||||
owningCiv != null && !tile.isWater -> owningCiv.nation.getOuterColor()
|
||||
else -> tile.getBaseTerrain().getColor().lerp(Color.GRAY, 0.5f)
|
||||
}
|
||||
}
|
||||
@ -52,16 +56,25 @@ internal class MinimapTile(val tile: Tile, tileSize: Float, val onClick: () -> U
|
||||
}
|
||||
}
|
||||
|
||||
fun updateBorders(): ActorChange {
|
||||
fun updateBorders(turn: Int? = null): ActorChange {
|
||||
val owningCiv = if (turn == null) tile.getOwner() else getOwningCivFromHistory(tile, turn)
|
||||
val imagesBefore = neighborToBorderImage.values.toSet()
|
||||
for (neighbor in tile.neighbors) {
|
||||
val shouldHaveBorderDisplayed = tile.getOwner() != null
|
||||
&& neighbor.getOwner() != tile.getOwner()
|
||||
val neighborOwningCiv =
|
||||
if (turn == null) neighbor.getOwner() else getOwningCivFromHistory(
|
||||
neighbor,
|
||||
turn
|
||||
)
|
||||
val shouldHaveBorderDisplayed = owningCiv != null
|
||||
&& neighborOwningCiv != owningCiv
|
||||
if (!shouldHaveBorderDisplayed) {
|
||||
neighborToBorderImage.remove(neighbor)
|
||||
continue
|
||||
}
|
||||
if (neighbor in neighborToBorderImage) continue
|
||||
if (neighbor in neighborToBorderImage) {
|
||||
neighborToBorderImage[neighbor]!!.color = owningCiv!!.nation.getInnerColor()
|
||||
continue
|
||||
}
|
||||
|
||||
val borderImage = ImageGetter.getWhiteDot()
|
||||
|
||||
@ -86,18 +99,34 @@ internal class MinimapTile(val tile: Tile, tileSize: Float, val onClick: () -> U
|
||||
-relativeWorldPosition.y * hexagonEdgeLength / 2
|
||||
)
|
||||
borderImage.rotateBy(angle)
|
||||
borderImage.color = tile.getOwner()!!.nation.getInnerColor()
|
||||
borderImage.color = owningCiv!!.nation.getInnerColor()
|
||||
neighborToBorderImage[neighbor] = borderImage
|
||||
}
|
||||
val imagesAfter = neighborToBorderImage.values.toSet()
|
||||
return ActorChange(imagesBefore - imagesAfter, imagesAfter - imagesBefore)
|
||||
}
|
||||
|
||||
fun updateCityCircle(): ActorChange {
|
||||
fun updateCityCircle(turn: Int? = null): ActorChange {
|
||||
val prevCircle = cityCircleImage
|
||||
val owningCiv = if (turn == null) tile.getOwner() else getOwningCivFromHistory(tile, turn)
|
||||
val isCityCenter =
|
||||
if (turn == null) tile.isCityCenter() else tile.history.getState(turn).cityCenterType != CityCenterType.None
|
||||
|
||||
val nation = tile.getOwner()!!.nation
|
||||
val nationIconSize = (if (tile.getCity()!!.isCapital() && tile.getOwner()!!.isMajorCiv()) 1.667f else 1.25f) * image.width
|
||||
if (owningCiv == null || !isCityCenter) {
|
||||
return ActorChange(
|
||||
if (prevCircle != null) setOf(prevCircle) else emptySet(),
|
||||
emptySet()
|
||||
)
|
||||
}
|
||||
|
||||
val nation = owningCiv.nation
|
||||
val isCapital =
|
||||
if (turn == null)
|
||||
tile.getCity()!!.isCapital()
|
||||
else
|
||||
tile.history.getState(turn).cityCenterType ==
|
||||
CityCenterType.Capital
|
||||
val nationIconSize = (if (isCapital && owningCiv.isMajorCiv()) 1.667f else 1.25f) * image.width
|
||||
val cityCircle = ImageGetter.getCircle().apply { color = nation.getInnerColor() }
|
||||
.surroundWithCircle(nationIconSize, color = nation.getOuterColor())
|
||||
val hexCenterXPosition = image.x + image.width / 2
|
||||
@ -109,4 +138,12 @@ internal class MinimapTile(val tile: Tile, tileSize: Float, val onClick: () -> U
|
||||
|
||||
return ActorChange(if (prevCircle != null) setOf(prevCircle) else emptySet(), setOf(cityCircle))
|
||||
}
|
||||
|
||||
fun getOwningCivFromHistory(tile: Tile, turn: Int) : Civilization? {
|
||||
val owningCivName = tile.history.getState(turn).owningCivName
|
||||
return if (owningCivName == null) null else tile.tileMap.gameInfo.getCivilization(
|
||||
owningCivName
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
package com.unciv.ui.screens.worldscreen.minimap
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.Group
|
||||
import java.awt.geom.Rectangle2D
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
object MinimapTileUtil {
|
||||
|
||||
fun spreadOutMinimapTiles(tileLayer: Group, tiles: List<MinimapTile>, tileSize: Float) : Rectangle2D.Float {
|
||||
var topX = -Float.MAX_VALUE
|
||||
var topY = -Float.MAX_VALUE
|
||||
var bottomX = Float.MAX_VALUE
|
||||
var bottomY = Float.MAX_VALUE
|
||||
|
||||
for (image in tiles.map { it.image }) {
|
||||
tileLayer.addActor(image)
|
||||
|
||||
// keeps track of the current top/bottom/left/rightmost tiles to size and position the minimap correctly
|
||||
topX = max(topX, image.x + tileSize)
|
||||
topY = max(topY, image.y + tileSize)
|
||||
bottomX = min(bottomX, image.x)
|
||||
bottomY = min(bottomY, image.y)
|
||||
}
|
||||
|
||||
return Rectangle2D.Float(bottomX, bottomY, topX-bottomX, topY-bottomY)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user