mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-22 10:54:19 -04:00
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:
parent
0d12fc7dfc
commit
6d72c7b85f
@ -1,5 +1,6 @@
|
|||||||
package com.unciv.logic.civilization
|
package com.unciv.logic.civilization
|
||||||
|
|
||||||
|
import com.badlogic.gdx.math.Rectangle
|
||||||
import com.badlogic.gdx.math.Vector2
|
import com.badlogic.gdx.math.Vector2
|
||||||
import com.unciv.logic.IsPartOfGameInfoSerialization
|
import com.unciv.logic.IsPartOfGameInfoSerialization
|
||||||
import com.unciv.logic.map.HexMath.getLatitude
|
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.logic.map.MapShape
|
||||||
import com.unciv.ui.components.tilegroups.TileGroupMap
|
import com.unciv.ui.components.tilegroups.TileGroupMap
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
class ExploredRegion () : IsPartOfGameInfoSerialization {
|
class ExploredRegion () : IsPartOfGameInfoSerialization {
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
private var isWorldWrap = false
|
private var worldWrap = false
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private var evenMapWidth = false
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private var rectangularMap = false
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
private var mapRadius = 0f
|
private var mapRadius = 0f
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
private val tileRadius = (TileGroupMap.groupSize + 4) * 0.75f
|
private val tileRadius = TileGroupMap.groupSize * 0.8f
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
private var shouldRecalculateCoords = true
|
private var shouldRecalculateCoords = true
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private var shouldUpdateMinimap = true
|
||||||
|
|
||||||
|
// Rectangle for positioning the camera viewport on the minimap
|
||||||
|
@Transient
|
||||||
|
private val exploredRectangle = Rectangle()
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
private var shouldRestrictX = false
|
private var shouldRestrictX = false
|
||||||
|
|
||||||
@ -43,6 +58,8 @@ class ExploredRegion () : IsPartOfGameInfoSerialization {
|
|||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
fun shouldRecalculateCoords(): Boolean = shouldRecalculateCoords
|
fun shouldRecalculateCoords(): Boolean = shouldRecalculateCoords
|
||||||
|
fun shouldUpdateMinimap(): Boolean = shouldUpdateMinimap
|
||||||
|
fun getRectangle(): Rectangle = exploredRectangle
|
||||||
fun shouldRestrictX(): Boolean = shouldRestrictX
|
fun shouldRestrictX(): Boolean = shouldRestrictX
|
||||||
fun getLeftX(): Float = topLeftStage.x
|
fun getLeftX(): Float = topLeftStage.x
|
||||||
fun getRightX():Float = bottomRightStage.x
|
fun getRightX():Float = bottomRightStage.x
|
||||||
@ -57,16 +74,21 @@ class ExploredRegion () : IsPartOfGameInfoSerialization {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setMapParameters(mapParameters: MapParameters) {
|
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()
|
mapRadius = (mapParameters.mapSize.width / 2).toFloat()
|
||||||
|
evenMapWidth = mapParameters.mapSize.width % 2 == 0 || evenMapWidth
|
||||||
|
rectangularMap = true
|
||||||
|
}
|
||||||
else
|
else
|
||||||
mapRadius = mapParameters.mapSize.radius.toFloat()
|
mapRadius = mapParameters.mapSize.radius.toFloat()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if tilePosition is beyond explored region
|
// Check if tilePosition is beyond explored region
|
||||||
fun checkTilePosition(tilePosition: Vector2, explorerPosition: Vector2?) {
|
fun checkTilePosition(tilePosition: Vector2, explorerPosition: Vector2?) {
|
||||||
|
var mapExplored = false
|
||||||
var longitude = getLongitude(tilePosition)
|
var longitude = getLongitude(tilePosition)
|
||||||
val latitude = getLatitude(tilePosition)
|
val latitude = getLatitude(tilePosition)
|
||||||
|
|
||||||
@ -81,14 +103,14 @@ class ExploredRegion () : IsPartOfGameInfoSerialization {
|
|||||||
if (topLeft.x >= bottomRight.x) {
|
if (topLeft.x >= bottomRight.x) {
|
||||||
if (longitude > topLeft.x) {
|
if (longitude > topLeft.x) {
|
||||||
// For world wrap maps when the maximumX is reached, we move to a minimumX - 1f
|
// 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
|
topLeft.x = longitude
|
||||||
shouldRecalculateCoords = true
|
mapExplored = true
|
||||||
} else if (longitude < bottomRight.x) {
|
} else if (longitude < bottomRight.x) {
|
||||||
// For world wrap maps when the minimumX is reached, we move to a maximumX + 1f
|
// 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
|
bottomRight.x = longitude
|
||||||
shouldRecalculateCoords = true
|
mapExplored = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// When we cross the map edge with world wrap, the vectors are swapped along the x-axis
|
// 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
|
else
|
||||||
bottomRight.x = longitude
|
bottomRight.x = longitude
|
||||||
|
|
||||||
shouldRecalculateCoords = true
|
mapExplored = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check Y coord
|
// Check Y coord
|
||||||
if (latitude > topLeft.y) {
|
if (latitude > topLeft.y) {
|
||||||
topLeft.y = latitude
|
topLeft.y = latitude
|
||||||
shouldRecalculateCoords = true
|
mapExplored = true
|
||||||
} else if (latitude < bottomRight.y) {
|
} else if (latitude < bottomRight.y) {
|
||||||
bottomRight.y = latitude
|
bottomRight.y = latitude
|
||||||
|
mapExplored = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mapExplored){
|
||||||
shouldRecalculateCoords = true
|
shouldRecalculateCoords = true
|
||||||
|
shouldUpdateMinimap = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,7 +177,7 @@ class ExploredRegion () : IsPartOfGameInfoSerialization {
|
|||||||
val bottomRightWorld = worldFromLatLong(bottomRight, tileRadius)
|
val bottomRightWorld = worldFromLatLong(bottomRight, tileRadius)
|
||||||
|
|
||||||
// Convert X to the stage coords
|
// 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 left = mapCenterX + topLeftWorld.x
|
||||||
var right = mapCenterX + bottomRightWorld.x
|
var right = mapCenterX + bottomRightWorld.x
|
||||||
|
|
||||||
@ -159,11 +186,41 @@ class ExploredRegion () : IsPartOfGameInfoSerialization {
|
|||||||
if (right < 0f) right = mapMaxX - 10f
|
if (right < 0f) right = mapMaxX - 10f
|
||||||
|
|
||||||
// Convert Y to the stage coords
|
// 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 top = mapCenterY-topLeftWorld.y
|
||||||
val bottom = mapCenterY-bottomRightWorld.y
|
val bottom = mapCenterY-bottomRightWorld.y
|
||||||
|
|
||||||
topLeftStage = Vector2(left, top)
|
topLeftStage = Vector2(left, top)
|
||||||
bottomRightStage = Vector2(right, bottom)
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
/** 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) */
|
* (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
|
const val groupSize = 50f
|
||||||
|
|
||||||
/** Length of the diagonal of a hex, or distance between two opposing corners */
|
/** Length of the diagonal of a hex, or distance between two opposing corners */
|
||||||
const val groupSizeDiagonal = groupSize * 1.1547005f // groupSize * sqrt(4/3)
|
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.
|
/** 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
|
* 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. */
|
* line connecting the two corners at 2 and 4 o'clock. */
|
||||||
@ -74,21 +76,19 @@ class TileGroupMap<T: TileGroup>(
|
|||||||
HexMath.hex2WorldCoords(tileGroup.tile.position)
|
HexMath.hex2WorldCoords(tileGroup.tile.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
tileGroup.setPosition(positionalVector.x * 0.8f * groupSize,
|
tileGroup.setPosition(
|
||||||
positionalVector.y * 0.8f * groupSize
|
positionalVector.x * 0.8f * groupSize,
|
||||||
|
positionalVector.y * 0.8f * groupSize
|
||||||
)
|
)
|
||||||
|
|
||||||
topX =
|
topX =
|
||||||
if (worldWrap)
|
if (worldWrap)
|
||||||
// Well it's not pretty but it works
|
// 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
|
// The resulting topX was always missing 1.2 * groupSize in every possible
|
||||||
// combination of map size and shape
|
// combination of map size and shape
|
||||||
max(topX, tileGroup.x + groupSize * 2.2f)
|
max(topX, tileGroup.x + groupSize * 1.2f)
|
||||||
else
|
else
|
||||||
max(topX, tileGroup.x + groupSize)
|
max(topX, tileGroup.x + groupSize + 4f)
|
||||||
|
|
||||||
topY = max(topY, tileGroup.y + groupSize)
|
topY = max(topY, tileGroup.y + groupSize)
|
||||||
bottomX = min(bottomX, tileGroup.x)
|
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
|
// 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 }) {
|
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!
|
// 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) })
|
baseLayers.add(group.layerTerrain.apply { setPosition(group.x, group.y) })
|
||||||
featureLayers.add(group.layerFeatures.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) })
|
borderLayers.add(group.layerBorders.apply { setPosition(group.x, group.y) })
|
||||||
miscLayers.add(group.layerMisc.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) })
|
pixelUnitLayers.add(group.layerUnitArt.apply { setPosition(group.x, group.y) })
|
||||||
circleFogCrosshairLayers.add(group.layerOverlay.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) })
|
unitLayers.add(group.layerUnitFlag.apply { setPosition(group.x, group.y) })
|
||||||
cityButtonLayers.add(group.layerCityButton.apply { setPosition(group.x,group.y) })
|
cityButtonLayers.add(group.layerCityButton.apply { setPosition(group.x, group.y) })
|
||||||
}
|
}
|
||||||
|
|
||||||
for (group in baseLayers) addActor(group)
|
for (group in baseLayers) addActor(group)
|
||||||
@ -136,11 +136,7 @@ class TileGroupMap<T: TileGroup>(
|
|||||||
|
|
||||||
// there are tiles "below the zero",
|
// there are tiles "below the zero",
|
||||||
// so we zero out the starting position of the whole board so they will be displayed as well
|
// 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.
|
setSize(topX - bottomX, topY - bottomY)
|
||||||
// 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)
|
|
||||||
|
|
||||||
cullingArea = Rectangle(0f, 0f, width, height)
|
cullingArea = Rectangle(0f, 0f, width, height)
|
||||||
|
|
||||||
@ -153,9 +149,9 @@ class TileGroupMap<T: TileGroup>(
|
|||||||
fun getPositionalVector(stageCoords: Vector2): Vector2 {
|
fun getPositionalVector(stageCoords: Vector2): Vector2 {
|
||||||
val trueGroupSize = 0.8f * groupSize
|
val trueGroupSize = 0.8f * groupSize
|
||||||
return Vector2(bottomX, bottomY)
|
return Vector2(bottomX, bottomY)
|
||||||
.add(stageCoords)
|
.add(stageCoords)
|
||||||
.sub(groupSize / 2f, groupSize / 2f)
|
.sub(groupSize / 2f, groupSize / 2f)
|
||||||
.scl(1f / trueGroupSize)
|
.scl(1f / trueGroupSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun act(delta: Float) {
|
override fun act(delta: Float) {
|
||||||
@ -173,8 +169,9 @@ class TileGroupMap<T: TileGroup>(
|
|||||||
|
|
||||||
if (worldWrap) {
|
if (worldWrap) {
|
||||||
// Prevent flickering when zoomed out so you can see entire map
|
// Prevent flickering when zoomed out so you can see entire map
|
||||||
val visibleMapWidth = if (mapHolder.width > maxVisibleMapWidth) maxVisibleMapWidth
|
val visibleMapWidth =
|
||||||
else mapHolder.width
|
if (mapHolder.width > maxVisibleMapWidth) maxVisibleMapWidth
|
||||||
|
else mapHolder.width
|
||||||
|
|
||||||
// Where is viewport's boundaries
|
// Where is viewport's boundaries
|
||||||
val rightSide = mapHolder.scrollX + visibleMapWidth / 2f
|
val rightSide = mapHolder.scrollX + visibleMapWidth / 2f
|
||||||
@ -202,11 +199,11 @@ class TileGroupMap<T: TileGroup>(
|
|||||||
it.x += width
|
it.x += width
|
||||||
} else if (beyondLeft) {
|
} else if (beyondLeft) {
|
||||||
// Move from right to left
|
// Move from right to left
|
||||||
if (it.x + groupSize >= drawTopX + diffLeft)
|
if (it.x + groupSize + 4f >= drawTopX + diffLeft)
|
||||||
it.x -= width
|
it.x -= width
|
||||||
}
|
}
|
||||||
newBottomX = min(newBottomX, it.x)
|
newBottomX = min(newBottomX, it.x)
|
||||||
newTopX = max(newTopX, it.x + groupSize)
|
newTopX = max(newTopX, it.x + groupSize + 4f)
|
||||||
}
|
}
|
||||||
|
|
||||||
drawBottomX = newBottomX
|
drawBottomX = newBottomX
|
||||||
@ -215,5 +212,4 @@ class TileGroupMap<T: TileGroup>(
|
|||||||
}
|
}
|
||||||
super.draw(batch, parentAlpha)
|
super.draw(batch, parentAlpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -381,6 +381,9 @@ class WorldScreen(
|
|||||||
if(uiEnabled){
|
if(uiEnabled){
|
||||||
displayTutorialsOnUpdate()
|
displayTutorialsOnUpdate()
|
||||||
|
|
||||||
|
if (fogOfWar) minimapWrapper.update(selectedCiv)
|
||||||
|
else minimapWrapper.update(viewingCiv)
|
||||||
|
|
||||||
bottomUnitTable.update()
|
bottomUnitTable.update()
|
||||||
bottomTileInfoTable.updateTileTable(mapHolder.selectedTile)
|
bottomTileInfoTable.updateTileTable(mapHolder.selectedTile)
|
||||||
bottomTileInfoTable.x = stage.width - bottomTileInfoTable.width
|
bottomTileInfoTable.x = stage.width - bottomTileInfoTable.width
|
||||||
@ -391,9 +394,6 @@ class WorldScreen(
|
|||||||
|
|
||||||
displayTutorialTaskOnUpdate()
|
displayTutorialTaskOnUpdate()
|
||||||
|
|
||||||
if (fogOfWar) minimapWrapper.update(selectedCiv)
|
|
||||||
else minimapWrapper.update(viewingCiv)
|
|
||||||
|
|
||||||
unitActionsTable.update(bottomUnitTable.selectedUnit)
|
unitActionsTable.update(bottomUnitTable.selectedUnit)
|
||||||
unitActionsTable.y = bottomUnitTable.height
|
unitActionsTable.y = bottomUnitTable.height
|
||||||
}
|
}
|
||||||
|
@ -16,23 +16,33 @@ import com.unciv.ui.components.extensions.*
|
|||||||
import com.unciv.ui.screens.worldscreen.WorldMapHolder
|
import com.unciv.ui.screens.worldscreen.WorldMapHolder
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
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 tileLayer = Group()
|
||||||
private val minimapTiles: List<MinimapTile>
|
private val minimapTiles: List<MinimapTile>
|
||||||
private val scrollPositionIndicators: List<ClippingImage>
|
private val scrollPositionIndicators: List<ClippingImage>
|
||||||
private var lastViewingCiv: Civilization? = null
|
private var lastViewingCiv: Civilization? = null
|
||||||
|
|
||||||
|
private var tileSize = 0f
|
||||||
|
private var tileMapWidth = 0f
|
||||||
|
private var tileMapHeight = 0f
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// don't try to resize rotate etc - this table has a LOT of children so that's valuable render time!
|
// don't try to resize rotate etc - this table has a LOT of children so that's valuable render time!
|
||||||
isTransform = false
|
isTransform = false
|
||||||
|
|
||||||
var topX = 0f
|
var topX = -Float.MAX_VALUE
|
||||||
var topY = 0f
|
var topY = -Float.MAX_VALUE
|
||||||
var bottomX = 0f
|
var bottomX = Float.MAX_VALUE
|
||||||
var bottomY = 0f
|
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)
|
minimapTiles = createMinimapTiles(tileSize)
|
||||||
for (image in minimapTiles.map { it.image }) {
|
for (image in minimapTiles.map { it.image }) {
|
||||||
tileLayer.addActor(image)
|
tileLayer.addActor(image)
|
||||||
@ -44,37 +54,98 @@ class Minimap(val mapHolder: WorldMapHolder, minimapSize: Int) : Group() {
|
|||||||
bottomY = min(bottomY, image.y)
|
bottomY = min(bottomY, image.y)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (group in tileLayer.children) {
|
|
||||||
group.moveBy(-bottomX, -bottomY)
|
|
||||||
}
|
|
||||||
|
|
||||||
// there are tiles "below the zero",
|
// there are tiles "below the zero",
|
||||||
// so we zero out the starting position of the whole board so they will be displayed as well
|
// 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 = createScrollPositionIndicators()
|
||||||
scrollPositionIndicators.forEach(tileLayer::addActor)
|
scrollPositionIndicators.forEach(tileLayer::addActor)
|
||||||
|
|
||||||
setSize(tileLayer.width, tileLayer.height)
|
|
||||||
addActor(tileLayer)
|
addActor(tileLayer)
|
||||||
|
|
||||||
mapHolder.onViewportChangedListener = ::updateScrollPosition
|
mapHolder.onViewportChangedListener = ::updateScrollPosition
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun calcTileSize(minimapSize: Int): Float {
|
private fun calcTileSize(minimapSize: Vector2): 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 height: Float
|
||||||
val effectiveRadius = with(mapHolder.tileMap.mapParameters) {
|
val width: Float
|
||||||
if (shape != MapShape.rectangular) mapSize.radius
|
val mapParameters = mapHolder.tileMap.mapParameters
|
||||||
else max(
|
|
||||||
mapSize.height,
|
if (civInfo != null) {
|
||||||
mapSize.width * 3 / 4
|
height = civInfo.exploredRegion.getHeight().toFloat()
|
||||||
) * MapSize.Huge.radius / MapSize.Huge.height
|
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 mapSizePercent = if (minimapSize < 22) minimapSize + 9 else minimapSize * 5 - 75
|
||||||
val smallerWorldDimension = mapHolder.worldScreen.stage.let { min(it.width, it.height) }
|
val smallerWorldDimension = mapHolder.worldScreen.stage.let { min(it.width, it.height) }
|
||||||
return smallerWorldDimension * mapSizePercent / 100 / effectiveRadius
|
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> {
|
private fun createScrollPositionIndicators(): List<ClippingImage> {
|
||||||
// If we are continuous scrolling (world wrap), add another 2 scrollPositionIndicators which
|
// If we are continuous scrolling (world wrap), add another 2 scrollPositionIndicators which
|
||||||
// get drawn at proper offsets to simulate looping
|
// 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> {
|
private fun createMinimapTiles(tileSize: Float): List<MinimapTile> {
|
||||||
val tiles = ArrayList<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) {
|
for (tileInfo in mapHolder.tileMap.values) {
|
||||||
|
if (civInfo?.exploredRegion?.isPositionInRegion(tileInfo.position) == false) continue
|
||||||
val minimapTile = MinimapTile(tileInfo, tileSize, onClick = {
|
val minimapTile = MinimapTile(tileInfo, tileSize, onClick = {
|
||||||
mapHolder.setCenterPosition(tileInfo.position)
|
mapHolder.setCenterPosition(tileInfo.position)
|
||||||
})
|
})
|
||||||
|
if (minimapTile.image.x < leftSide)
|
||||||
|
minimapTile.image.x += pad
|
||||||
tiles.add(minimapTile)
|
tiles.add(minimapTile)
|
||||||
}
|
}
|
||||||
return tiles
|
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.
|
* 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) {
|
private fun updateScrollPosition(
|
||||||
operator fun Rectangle.times(other: Vector2) = Rectangle(x * other.x, y * other.y, width * other.x, height * other.y)
|
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) {
|
fun Actor.setViewport(rect: Rectangle) {
|
||||||
x = rect.x;
|
x = rect.x;
|
||||||
y = rect.y;
|
y = rect.y;
|
||||||
@ -111,16 +197,38 @@ class Minimap(val mapHolder: WorldMapHolder, minimapSize: Int) : Group() {
|
|||||||
height = rect.height
|
height = rect.height
|
||||||
}
|
}
|
||||||
|
|
||||||
val worldToMiniFactor = Vector2(tileLayer.width / worldWidth, tileLayer.height / worldHeight)
|
val worldToMiniFactor: Vector2
|
||||||
val miniViewport = worldViewport * worldToMiniFactor
|
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
|
// 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)
|
scrollPositionIndicators[0].setViewport(miniViewport)
|
||||||
|
|
||||||
// If world wrap enabled, draw another 2 viewports at proper offset to simulate wrapping
|
// If world wrap enabled, draw another 2 viewports at proper offset to simulate wrapping
|
||||||
if (scrollPositionIndicators.size != 1) {
|
if (scrollPositionIndicators.size != 1) {
|
||||||
miniViewport.x -= tileLayer.width
|
val offset = worldWidth * worldToMiniFactor.x
|
||||||
|
miniViewport.x -= offset
|
||||||
scrollPositionIndicators[1].setViewport(miniViewport)
|
scrollPositionIndicators[1].setViewport(miniViewport)
|
||||||
miniViewport.x += tileLayer.width * 2
|
miniViewport.x += offset * 2f
|
||||||
scrollPositionIndicators[2].setViewport(miniViewport)
|
scrollPositionIndicators[2].setViewport(miniViewport)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import com.badlogic.gdx.graphics.Color
|
|||||||
import com.badlogic.gdx.graphics.g2d.Batch
|
import com.badlogic.gdx.graphics.g2d.Batch
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||||
import com.badlogic.gdx.utils.Align
|
import com.badlogic.gdx.utils.Align
|
||||||
|
import com.unciv.GUI
|
||||||
import com.unciv.UncivGame
|
import com.unciv.UncivGame
|
||||||
import com.unciv.logic.civilization.Civilization
|
import com.unciv.logic.civilization.Civilization
|
||||||
import com.unciv.ui.images.ImageGetter
|
import com.unciv.ui.images.ImageGetter
|
||||||
@ -43,20 +44,19 @@ class MinimapHolder(val mapHolder: WorldMapHolder) : Table() {
|
|||||||
backgroundColor = Color.GREEN
|
backgroundColor = Color.GREEN
|
||||||
)
|
)
|
||||||
|
|
||||||
init {
|
private fun rebuildIfSizeChanged(civInfo: Civilization) {
|
||||||
rebuildIfSizeChanged()
|
// For Spectator should not restrict minimap
|
||||||
}
|
var civInfo: Civilization? = civInfo
|
||||||
|
if(GUI.getViewingPlayer().isSpectator()) civInfo = null
|
||||||
private fun rebuildIfSizeChanged() {
|
|
||||||
val newMinimapSize = worldScreen.game.settings.minimapSize
|
val newMinimapSize = worldScreen.game.settings.minimapSize
|
||||||
if (newMinimapSize == minimapSize) return
|
if (newMinimapSize == minimapSize && civInfo?.exploredRegion?.shouldUpdateMinimap() != true) return
|
||||||
minimapSize = newMinimapSize
|
minimapSize = newMinimapSize
|
||||||
rebuild()
|
rebuild(civInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun rebuild(){
|
private fun rebuild(civInfo: Civilization?){
|
||||||
this.clear()
|
this.clear()
|
||||||
minimap = Minimap(mapHolder, minimapSize)
|
minimap = Minimap(mapHolder, minimapSize, civInfo)
|
||||||
add(getToggleIcons()).align(Align.bottom)
|
add(getToggleIcons()).align(Align.bottom)
|
||||||
add(getWrappedMinimap())
|
add(getWrappedMinimap())
|
||||||
pack()
|
pack()
|
||||||
@ -97,7 +97,7 @@ class MinimapHolder(val mapHolder: WorldMapHolder) : Table() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun update(civInfo: Civilization) {
|
fun update(civInfo: Civilization) {
|
||||||
rebuildIfSizeChanged()
|
rebuildIfSizeChanged(civInfo)
|
||||||
isVisible = UncivGame.Current.settings.showMinimap
|
isVisible = UncivGame.Current.settings.showMinimap
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
minimap.update(civInfo)
|
minimap.update(civInfo)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user