Dynamic minimap (#8794)

* Dynamic minimap

* Fixed minimap size

* Fix for rectangular maps

* Fix minimap for spectator

* Proper fix for spectator

* Resizing the game window no longer breaks the minimap

* Implemented the camera rectangle + Explored region more accurate positioning

* ExploredRectangle is calculated only after expanding the region
This commit is contained in:
Gualdimar 2023-03-08 11:25:13 +02:00 committed by GitHub
parent 0d12fc7dfc
commit 6d72c7b85f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 242 additions and 81 deletions

View File

@ -1,5 +1,6 @@
package com.unciv.logic.civilization
import com.badlogic.gdx.math.Rectangle
import com.badlogic.gdx.math.Vector2
import com.unciv.logic.IsPartOfGameInfoSerialization
import com.unciv.logic.map.HexMath.getLatitude
@ -9,21 +10,35 @@ import com.unciv.logic.map.MapParameters
import com.unciv.logic.map.MapShape
import com.unciv.ui.components.tilegroups.TileGroupMap
import kotlin.math.abs
import kotlin.math.sqrt
class ExploredRegion () : IsPartOfGameInfoSerialization {
@Transient
private var isWorldWrap = false
private var worldWrap = false
@Transient
private var evenMapWidth = false
@Transient
private var rectangularMap = false
@Transient
private var mapRadius = 0f
@Transient
private val tileRadius = (TileGroupMap.groupSize + 4) * 0.75f
private val tileRadius = TileGroupMap.groupSize * 0.8f
@Transient
private var shouldRecalculateCoords = true
@Transient
private var shouldUpdateMinimap = true
// Rectangle for positioning the camera viewport on the minimap
@Transient
private val exploredRectangle = Rectangle()
@Transient
private var shouldRestrictX = false
@ -43,6 +58,8 @@ class ExploredRegion () : IsPartOfGameInfoSerialization {
// Getters
fun shouldRecalculateCoords(): Boolean = shouldRecalculateCoords
fun shouldUpdateMinimap(): Boolean = shouldUpdateMinimap
fun getRectangle(): Rectangle = exploredRectangle
fun shouldRestrictX(): Boolean = shouldRestrictX
fun getLeftX(): Float = topLeftStage.x
fun getRightX():Float = bottomRightStage.x
@ -57,16 +74,21 @@ class ExploredRegion () : IsPartOfGameInfoSerialization {
}
fun setMapParameters(mapParameters: MapParameters) {
isWorldWrap = mapParameters.worldWrap
this.worldWrap = mapParameters.worldWrap
evenMapWidth = worldWrap
if (mapParameters.shape == MapShape.rectangular)
if (mapParameters.shape == MapShape.rectangular) {
mapRadius = (mapParameters.mapSize.width / 2).toFloat()
evenMapWidth = mapParameters.mapSize.width % 2 == 0 || evenMapWidth
rectangularMap = true
}
else
mapRadius = mapParameters.mapSize.radius.toFloat()
}
// Check if tilePosition is beyond explored region
fun checkTilePosition(tilePosition: Vector2, explorerPosition: Vector2?) {
var mapExplored = false
var longitude = getLongitude(tilePosition)
val latitude = getLatitude(tilePosition)
@ -81,14 +103,14 @@ class ExploredRegion () : IsPartOfGameInfoSerialization {
if (topLeft.x >= bottomRight.x) {
if (longitude > topLeft.x) {
// For world wrap maps when the maximumX is reached, we move to a minimumX - 1f
if (isWorldWrap && longitude == mapRadius) longitude = mapRadius * -1f
if (worldWrap && longitude == mapRadius) longitude = mapRadius * -1f
topLeft.x = longitude
shouldRecalculateCoords = true
mapExplored = true
} else if (longitude < bottomRight.x) {
// For world wrap maps when the minimumX is reached, we move to a maximumX + 1f
if (isWorldWrap && longitude == (mapRadius * -1f + 1f)) longitude = mapRadius + 1f
if (worldWrap && longitude == (mapRadius * -1f + 1f)) longitude = mapRadius + 1f
bottomRight.x = longitude
shouldRecalculateCoords = true
mapExplored = true
}
} else {
// When we cross the map edge with world wrap, the vectors are swapped along the x-axis
@ -125,17 +147,22 @@ class ExploredRegion () : IsPartOfGameInfoSerialization {
else
bottomRight.x = longitude
shouldRecalculateCoords = true
mapExplored = true
}
}
// Check Y coord
if (latitude > topLeft.y) {
topLeft.y = latitude
shouldRecalculateCoords = true
mapExplored = true
} else if (latitude < bottomRight.y) {
bottomRight.y = latitude
mapExplored = true
}
if(mapExplored){
shouldRecalculateCoords = true
shouldUpdateMinimap = true
}
}
@ -150,7 +177,7 @@ class ExploredRegion () : IsPartOfGameInfoSerialization {
val bottomRightWorld = worldFromLatLong(bottomRight, tileRadius)
// Convert X to the stage coords
val mapCenterX = if (isWorldWrap) mapMaxX * 0.5f + tileRadius else mapMaxX * 0.5f
val mapCenterX = if (evenMapWidth) (mapMaxX + TileGroupMap.groupSize + 4f) * 0.5f else mapMaxX * 0.5f
var left = mapCenterX + topLeftWorld.x
var right = mapCenterX + bottomRightWorld.x
@ -159,11 +186,41 @@ class ExploredRegion () : IsPartOfGameInfoSerialization {
if (right < 0f) right = mapMaxX - 10f
// Convert Y to the stage coords
val mapCenterY = mapMaxY * 0.5f
val mapCenterY = if (rectangularMap) mapMaxY * 0.5f + TileGroupMap.groupSize * 0.25f else mapMaxY * 0.5f
val top = mapCenterY-topLeftWorld.y
val bottom = mapCenterY-bottomRightWorld.y
topLeftStage = Vector2(left, top)
bottomRightStage = Vector2(right, bottom)
// Calculate rectangle for positioning the camera viewport on the minimap
val yOffset = tileRadius * sqrt(3f) * 0.5f
exploredRectangle.x = left - tileRadius
exploredRectangle.y = mapMaxY - bottom - yOffset * 0.5f
exploredRectangle.width = getWidth() * tileRadius * 1.5f
exploredRectangle.height = getHeight() * yOffset
}
fun isPositionInRegion(postition: Vector2): Boolean {
val long = getLongitude(postition)
val lat = getLatitude(postition)
return if (topLeft.x > bottomRight.x)
(long <= topLeft.x && long >= bottomRight.x && lat <= topLeft.y && lat >= bottomRight.y)
else
(((long >= topLeft.x && long >= bottomRight.x) || (long <= topLeft.x && long <= bottomRight.x)) && lat <= topLeft.y && lat >= bottomRight.y)
}
fun getWidth(): Int {
val result: Float
if (topLeft.x > bottomRight.x) result = topLeft.x - bottomRight.x
else result = mapRadius * 2f - (bottomRight.x - topLeft.x)
return result.toInt() + 1
}
fun getHeight(): Int = (topLeft.y - bottomRight.y).toInt() + 1
fun getMinimapLeft(tileSize: Float): Float {
shouldUpdateMinimap = false
return (topLeft.x + 1f) * tileSize * -0.75f
}
}

View File

@ -36,8 +36,10 @@ class TileGroupMap<T: TileGroup>(
/** Vertical size of a hex in world coordinates, or the distance between the centers of any two opposing edges
* (the hex is oriented so it has corners to the left and right of the center and its upper and lower bounds are horizontal edges) */
const val groupSize = 50f
/** Length of the diagonal of a hex, or distance between two opposing corners */
const val groupSizeDiagonal = groupSize * 1.1547005f // groupSize * sqrt(4/3)
/** Horizontal displacement per hex, meaning the increase in overall map size (in world coordinates) when adding a column.
* On the hex, this can be visualized as the horizontal distance between the leftmost corner and the
* line connecting the two corners at 2 and 4 o'clock. */
@ -74,21 +76,19 @@ class TileGroupMap<T: TileGroup>(
HexMath.hex2WorldCoords(tileGroup.tile.position)
}
tileGroup.setPosition(positionalVector.x * 0.8f * groupSize,
positionalVector.y * 0.8f * groupSize
tileGroup.setPosition(
positionalVector.x * 0.8f * groupSize,
positionalVector.y * 0.8f * groupSize
)
topX =
if (worldWrap)
if (worldWrap)
// Well it's not pretty but it works
// This is so topX is the same no matter what worldWrap is
// wrapped worlds are missing one tile width on the right side
// which would result in a smaller topX
// The resulting topX was always missing 1.2 * groupSize in every possible
// combination of map size and shape
max(topX, tileGroup.x + groupSize * 2.2f)
else
max(topX, tileGroup.x + groupSize)
max(topX, tileGroup.x + groupSize * 1.2f)
else
max(topX, tileGroup.x + groupSize + 4f)
topY = max(topY, tileGroup.y + groupSize)
bottomX = min(bottomX, tileGroup.x)
@ -114,14 +114,14 @@ class TileGroupMap<T: TileGroup>(
// Apparently the sortedByDescending is kinda memory-intensive because it needs to sort ALL the tiles
for (group in tileGroups.sortedByDescending { it.tile.position.x + it.tile.position.y }) {
// now, we steal the subgroups from all the tilegroups, that's how we form layers!
baseLayers.add(group.layerTerrain.apply { setPosition(group.x,group.y) })
featureLayers.add(group.layerFeatures.apply { setPosition(group.x,group.y) })
borderLayers.add(group.layerBorders.apply { setPosition(group.x,group.y) })
miscLayers.add(group.layerMisc.apply { setPosition(group.x,group.y) })
pixelUnitLayers.add(group.layerUnitArt.apply { setPosition(group.x,group.y) })
circleFogCrosshairLayers.add(group.layerOverlay.apply { setPosition(group.x,group.y) })
unitLayers.add(group.layerUnitFlag.apply { setPosition(group.x,group.y) })
cityButtonLayers.add(group.layerCityButton.apply { setPosition(group.x,group.y) })
baseLayers.add(group.layerTerrain.apply { setPosition(group.x, group.y) })
featureLayers.add(group.layerFeatures.apply { setPosition(group.x, group.y) })
borderLayers.add(group.layerBorders.apply { setPosition(group.x, group.y) })
miscLayers.add(group.layerMisc.apply { setPosition(group.x, group.y) })
pixelUnitLayers.add(group.layerUnitArt.apply { setPosition(group.x, group.y) })
circleFogCrosshairLayers.add(group.layerOverlay.apply { setPosition(group.x, group.y) })
unitLayers.add(group.layerUnitFlag.apply { setPosition(group.x, group.y) })
cityButtonLayers.add(group.layerCityButton.apply { setPosition(group.x, group.y) })
}
for (group in baseLayers) addActor(group)
@ -136,11 +136,7 @@ class TileGroupMap<T: TileGroup>(
// there are tiles "below the zero",
// so we zero out the starting position of the whole board so they will be displayed as well
// Map's width is reduced by groupSize if it is wrapped, because wrapped map will miss a tile on the right.
// This ensures that wrapped maps have a smooth transition.
// If map is not wrapped, Map's width doesn't need to be reduce by groupSize
if (worldWrap) setSize(topX - bottomX - groupSize, topY - bottomY)
else setSize(topX - bottomX, topY - bottomY)
setSize(topX - bottomX, topY - bottomY)
cullingArea = Rectangle(0f, 0f, width, height)
@ -153,9 +149,9 @@ class TileGroupMap<T: TileGroup>(
fun getPositionalVector(stageCoords: Vector2): Vector2 {
val trueGroupSize = 0.8f * groupSize
return Vector2(bottomX, bottomY)
.add(stageCoords)
.sub(groupSize / 2f, groupSize / 2f)
.scl(1f / trueGroupSize)
.add(stageCoords)
.sub(groupSize / 2f, groupSize / 2f)
.scl(1f / trueGroupSize)
}
override fun act(delta: Float) {
@ -173,8 +169,9 @@ class TileGroupMap<T: TileGroup>(
if (worldWrap) {
// Prevent flickering when zoomed out so you can see entire map
val visibleMapWidth = if (mapHolder.width > maxVisibleMapWidth) maxVisibleMapWidth
else mapHolder.width
val visibleMapWidth =
if (mapHolder.width > maxVisibleMapWidth) maxVisibleMapWidth
else mapHolder.width
// Where is viewport's boundaries
val rightSide = mapHolder.scrollX + visibleMapWidth / 2f
@ -202,11 +199,11 @@ class TileGroupMap<T: TileGroup>(
it.x += width
} else if (beyondLeft) {
// Move from right to left
if (it.x + groupSize >= drawTopX + diffLeft)
if (it.x + groupSize + 4f >= drawTopX + diffLeft)
it.x -= width
}
newBottomX = min(newBottomX, it.x)
newTopX = max(newTopX, it.x + groupSize)
newTopX = max(newTopX, it.x + groupSize + 4f)
}
drawBottomX = newBottomX
@ -215,5 +212,4 @@ class TileGroupMap<T: TileGroup>(
}
super.draw(batch, parentAlpha)
}
}

View File

@ -381,6 +381,9 @@ class WorldScreen(
if(uiEnabled){
displayTutorialsOnUpdate()
if (fogOfWar) minimapWrapper.update(selectedCiv)
else minimapWrapper.update(viewingCiv)
bottomUnitTable.update()
bottomTileInfoTable.updateTileTable(mapHolder.selectedTile)
bottomTileInfoTable.x = stage.width - bottomTileInfoTable.width
@ -391,9 +394,6 @@ class WorldScreen(
displayTutorialTaskOnUpdate()
if (fogOfWar) minimapWrapper.update(selectedCiv)
else minimapWrapper.update(viewingCiv)
unitActionsTable.update(bottomUnitTable.selectedUnit)
unitActionsTable.y = bottomUnitTable.height
}

View File

@ -16,23 +16,33 @@ import com.unciv.ui.components.extensions.*
import com.unciv.ui.screens.worldscreen.WorldMapHolder
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sqrt
class Minimap(val mapHolder: WorldMapHolder, minimapSize: Int) : Group() {
class Minimap(val mapHolder: WorldMapHolder, minimapSize: Int, private val civInfo: Civilization?) : Group() {
private val tileLayer = Group()
private val minimapTiles: List<MinimapTile>
private val scrollPositionIndicators: List<ClippingImage>
private var lastViewingCiv: Civilization? = null
private var tileSize = 0f
private var tileMapWidth = 0f
private var tileMapHeight = 0f
init {
// don't try to resize rotate etc - this table has a LOT of children so that's valuable render time!
isTransform = false
var topX = 0f
var topY = 0f
var bottomX = 0f
var bottomY = 0f
var topX = -Float.MAX_VALUE
var topY = -Float.MAX_VALUE
var bottomX = Float.MAX_VALUE
var bottomY = Float.MAX_VALUE
val tileSize = calcTileSize(minimapSize)
// Set fixed minimap size
val stageMinimapSize = calcMinimapSize(minimapSize)
setSize(stageMinimapSize.x, stageMinimapSize.y)
// Calculate max tileSize to fit in mimimap
tileSize = calcTileSize(stageMinimapSize)
minimapTiles = createMinimapTiles(tileSize)
for (image in minimapTiles.map { it.image }) {
tileLayer.addActor(image)
@ -44,37 +54,98 @@ class Minimap(val mapHolder: WorldMapHolder, minimapSize: Int) : Group() {
bottomY = min(bottomY, image.y)
}
for (group in tileLayer.children) {
group.moveBy(-bottomX, -bottomY)
}
// 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(topX - bottomX, topY - bottomY)
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
for (group in tileLayer.children) {
group.moveBy(padX, padY)
}
scrollPositionIndicators = createScrollPositionIndicators()
scrollPositionIndicators.forEach(tileLayer::addActor)
setSize(tileLayer.width, tileLayer.height)
addActor(tileLayer)
mapHolder.onViewportChangedListener = ::updateScrollPosition
}
private fun calcTileSize(minimapSize: Int): Float {
// Support rectangular maps with extreme aspect ratios by scaling to the larger coordinate with a slight weighting to make the bounding box 4:3
val effectiveRadius = with(mapHolder.tileMap.mapParameters) {
if (shape != MapShape.rectangular) mapSize.radius
else max(
mapSize.height,
mapSize.width * 3 / 4
) * MapSize.Huge.radius / MapSize.Huge.height
private fun calcTileSize(minimapSize: Vector2): Float {
val height: Float
val width: Float
val mapParameters = mapHolder.tileMap.mapParameters
if (civInfo != null) {
height = civInfo.exploredRegion.getHeight().toFloat()
width = civInfo.exploredRegion.getWidth().toFloat()
} else {
if (mapParameters.shape != MapShape.rectangular) {
val diameter = mapParameters.mapSize.radius * 2f + 1f
height = diameter.toFloat()
width = diameter.toFloat()
} else {
height = mapParameters.mapSize.height.toFloat()
width = mapParameters.mapSize.width.toFloat()
}
}
val result =
min(
minimapSize.y / (height + 1.5f) / sqrt(3f) * 4f, // 1.5 - padding, hex height = sqrt(3) / 2 * d / 2 -> d = height / sqrt(3) * 2 * 2
minimapSize.x / (width + 0.5f) / 0.75f // 0.5 - padding, hex width = 0.75 * d -> d = width / 0.75
)
return result
}
private fun calcMinTileSize(minimapSize: Int): Float {
// Support rectangular maps with extreme aspect ratios by scaling to the larger coordinate with a slight weighting to make the bounding box 4:3
val effectiveRadius =
with(mapHolder.tileMap.mapParameters) {
if (shape != MapShape.rectangular) mapSize.radius
else max(
mapSize.height,
mapSize.width * 3 / 4
) * MapSize.Huge.radius / MapSize.Huge.height
}
val mapSizePercent = if (minimapSize < 22) minimapSize + 9 else minimapSize * 5 - 75
val smallerWorldDimension = mapHolder.worldScreen.stage.let { min(it.width, it.height) }
return smallerWorldDimension * mapSizePercent / 100 / effectiveRadius
}
private fun calcMinimapSize(minimapSize: Int): Vector2 {
val minimapTileSize = calcMinTileSize(minimapSize)
var height: Float
var width: Float
val mapParameters = mapHolder.tileMap.mapParameters
if (mapParameters.shape != MapShape.rectangular) {
val diameter = mapParameters.mapSize.radius * 2f + 1f
height = diameter
width = diameter
} else {
height = mapParameters.mapSize.height.toFloat()
width = mapParameters.mapSize.width.toFloat()
}
// hex height = sqrt(3) / 2 * d / 2, number of rows = mapDiameter * 2
height *= minimapTileSize * sqrt(3f) * 0.5f
// hex width = 0.75 * d
width =
if (mapParameters.worldWrap)
(width - 1f) * minimapTileSize * 0.75f
else
width * minimapTileSize * 0.75f
return Vector2(width, height)
}
private fun createScrollPositionIndicators(): List<ClippingImage> {
// If we are continuous scrolling (world wrap), add another 2 scrollPositionIndicators which
// get drawn at proper offsets to simulate looping
@ -89,10 +160,19 @@ class Minimap(val mapHolder: WorldMapHolder, minimapSize: Int) : Group() {
private fun createMinimapTiles(tileSize: Float): List<MinimapTile> {
val tiles = ArrayList<MinimapTile>()
val pad = if (mapHolder.tileMap.mapParameters.shape != MapShape.rectangular)
mapHolder.tileMap.mapParameters.mapSize.radius * tileSize * 1.5f
else
(mapHolder.tileMap.mapParameters.mapSize.width - 1f) * tileSize * 0.75f
val leftSide =
if (civInfo != null) civInfo.exploredRegion.getMinimapLeft(tileSize) else -Float.MAX_VALUE
for (tileInfo in mapHolder.tileMap.values) {
if (civInfo?.exploredRegion?.isPositionInRegion(tileInfo.position) == false) continue
val minimapTile = MinimapTile(tileInfo, tileSize, onClick = {
mapHolder.setCenterPosition(tileInfo.position)
})
if (minimapTile.image.x < leftSide)
minimapTile.image.x += pad
tiles.add(minimapTile)
}
return tiles
@ -102,8 +182,14 @@ class Minimap(val mapHolder: WorldMapHolder, minimapSize: Int) : Group() {
*
* Requires [scrollPositionIndicator] to be a [ClippingImage] to keep the displayed portion of the indicator within the bounds of the minimap.
*/
private fun updateScrollPosition(worldWidth: Float, worldHeight: Float, worldViewport: Rectangle) {
operator fun Rectangle.times(other: Vector2) = Rectangle(x * other.x, y * other.y, width * other.x, height * other.y)
private fun updateScrollPosition(
worldWidth: Float,
worldHeight: Float,
worldViewport: Rectangle
) {
operator fun Rectangle.times(other: Vector2) =
Rectangle(x * other.x, y * other.y, width * other.x, height * other.y)
fun Actor.setViewport(rect: Rectangle) {
x = rect.x;
y = rect.y;
@ -111,16 +197,38 @@ class Minimap(val mapHolder: WorldMapHolder, minimapSize: Int) : Group() {
height = rect.height
}
val worldToMiniFactor = Vector2(tileLayer.width / worldWidth, tileLayer.height / worldHeight)
val miniViewport = worldViewport * worldToMiniFactor
val worldToMiniFactor: Vector2
var miniViewport = worldViewport
if (civInfo != null) {
if (civInfo.exploredRegion.shouldRecalculateCoords()) civInfo.exploredRegion.calculateStageCoords(
worldWidth,
worldHeight
)
val exploredRectangle = civInfo.exploredRegion.getRectangle()
worldToMiniFactor = Vector2(
tileMapWidth / exploredRectangle.width,
tileMapHeight / exploredRectangle.height
)
miniViewport.x -= exploredRectangle.x
miniViewport.y -= exploredRectangle.y
} else
worldToMiniFactor =
Vector2(tileLayer.width / worldWidth, tileLayer.height / worldHeight)
miniViewport *= worldToMiniFactor
miniViewport.x += (tileLayer.width - tileMapWidth) * 0.5f
miniViewport.y += (tileLayer.height - tileMapHeight) * 0.5f
// This _could_ place parts of the 'camera' icon outside the minimap if it were a standard Image, thus the ClippingImage helper class
scrollPositionIndicators[0].setViewport(miniViewport)
// If world wrap enabled, draw another 2 viewports at proper offset to simulate wrapping
if (scrollPositionIndicators.size != 1) {
miniViewport.x -= tileLayer.width
val offset = worldWidth * worldToMiniFactor.x
miniViewport.x -= offset
scrollPositionIndicators[1].setViewport(miniViewport)
miniViewport.x += tileLayer.width * 2
miniViewport.x += offset * 2f
scrollPositionIndicators[2].setViewport(miniViewport)
}
}

View File

@ -4,6 +4,7 @@ import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.g2d.Batch
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align
import com.unciv.GUI
import com.unciv.UncivGame
import com.unciv.logic.civilization.Civilization
import com.unciv.ui.images.ImageGetter
@ -43,20 +44,19 @@ class MinimapHolder(val mapHolder: WorldMapHolder) : Table() {
backgroundColor = Color.GREEN
)
init {
rebuildIfSizeChanged()
}
private fun rebuildIfSizeChanged() {
private fun rebuildIfSizeChanged(civInfo: Civilization) {
// For Spectator should not restrict minimap
var civInfo: Civilization? = civInfo
if(GUI.getViewingPlayer().isSpectator()) civInfo = null
val newMinimapSize = worldScreen.game.settings.minimapSize
if (newMinimapSize == minimapSize) return
if (newMinimapSize == minimapSize && civInfo?.exploredRegion?.shouldUpdateMinimap() != true) return
minimapSize = newMinimapSize
rebuild()
rebuild(civInfo)
}
private fun rebuild(){
private fun rebuild(civInfo: Civilization?){
this.clear()
minimap = Minimap(mapHolder, minimapSize)
minimap = Minimap(mapHolder, minimapSize, civInfo)
add(getToggleIcons()).align(Align.bottom)
add(getWrappedMinimap())
pack()
@ -97,7 +97,7 @@ class MinimapHolder(val mapHolder: WorldMapHolder) : Table() {
}
fun update(civInfo: Civilization) {
rebuildIfSizeChanged()
rebuildIfSizeChanged(civInfo)
isVisible = UncivGame.Current.settings.showMinimap
if (isVisible) {
minimap.update(civInfo)