TileMapHolder refactor & Map Editor update (#1584)

* TileMapHolder renamed to MapHolder
* EditorMapHolder and WorldMapHolder inherits from ZoomableScrollPane
* MapEditor Brush Size (from 1 to 5)
* MapEditor painting mode (dragging the pointer on screen paints continuously)
This commit is contained in:
r3versi 2020-01-02 19:02:38 +01:00 committed by Yair Morgenstern
parent 908c6ad54a
commit a1b0c1dcd4
20 changed files with 349 additions and 170 deletions

View File

@ -945,6 +945,7 @@ Clear improvements = Elimina miglioramenti
Clear resource = Elimina risorsa Clear resource = Elimina risorsa
Requires = Richiede Requires = Richiede
Menu = Menu Menu = Menu
Brush Size = Dimensione pennello
# Civilopedia Tutorials names # Civilopedia Tutorials names

View File

@ -945,6 +945,7 @@ Clear improvements =
Clear resource = Clear resource =
Requires = Requires =
Menu = Menu =
Brush Size =
# Civilopedia Tutorials names # Civilopedia Tutorials names

View File

@ -1,10 +1,8 @@
package com.unciv.logic package com.unciv.logic
import com.badlogic.gdx.math.Vector2 import com.badlogic.gdx.math.Vector2
import java.util.* import com.badlogic.gdx.math.Vector3
import kotlin.math.abs import kotlin.math.*
import kotlin.math.max
import kotlin.math.sqrt
class HexMath { class HexMath {
@ -16,6 +14,18 @@ class HexMath {
return getVectorForAngle((2 * Math.PI * (hour / 12f)).toFloat()) return getVectorForAngle((2 * Math.PI * (hour / 12f)).toFloat())
} }
fun getAdjacentVectors(origin: Vector2): ArrayList<Vector2> {
val vectors = ArrayList<Vector2>()
vectors += Vector2(1f, 0f)
vectors += Vector2(1f, 1f)
vectors += Vector2(0f, 1f)
vectors += Vector2(-1f, 0f)
vectors += Vector2(-1f, -1f)
vectors += Vector2(0f, -1f)
for (vector in vectors) vector.add(origin)
return vectors
}
// HexCoordinates are a (x,y) vector, where x is the vector getting us to the top-left hex (e.g. 10 o'clock) // HexCoordinates are a (x,y) vector, where x is the vector getting us to the top-left hex (e.g. 10 o'clock)
// and y is the vector getting us to the top-right hex (e.g. 2 o'clock) // and y is the vector getting us to the top-right hex (e.g. 2 o'clock)
@ -30,16 +40,45 @@ class HexMath {
return xVector.scl(hexCoord.x).add(yVector.scl(hexCoord.y)) return xVector.scl(hexCoord.x).add(yVector.scl(hexCoord.y))
} }
fun getAdjacentVectors(origin: Vector2): ArrayList<Vector2> { fun world2HexCoords(worldCoord: Vector2): Vector2 {
val vectors = ArrayList<Vector2>() // D: diagonal, A: antidiagonal versors
vectors += Vector2(1f, 0f) val D = getVectorByClockHour(10).scl(sqrt(3.0).toFloat())
vectors += Vector2(1f, 1f) val A = getVectorByClockHour(2).scl(sqrt(3.0).toFloat())
vectors += Vector2(0f, 1f) val den = D.x * A.y - D.y * A.x
vectors += Vector2(-1f, 0f) val x = (worldCoord.x * A.y - worldCoord.y * A.x) / den
vectors += Vector2(-1f, -1f) val y = (worldCoord.y * D.x - worldCoord.x * D.y) / den
vectors += Vector2(0f, -1f) return Vector2(x, y)
for (vector in vectors) vector.add(origin) }
return vectors
fun hex2CubicCoords(hexCoord: Vector2): Vector3 {
return Vector3(hexCoord.y - hexCoord.x, hexCoord.x, -hexCoord.y)
}
fun cubic2HexCoords(cubicCoord: Vector3): Vector2 {
return Vector2(cubicCoord.y, -cubicCoord.z)
}
fun roundCubicCoords(cubicCoords: Vector3): Vector3 {
var rx = round(cubicCoords.x)
var ry = round(cubicCoords.y)
var rz = round(cubicCoords.z)
val deltaX = abs(rx - cubicCoords.x)
val deltaY = abs(ry - cubicCoords.y)
val deltaZ = abs(rz - cubicCoords.z)
if (deltaX > deltaY && deltaX > deltaZ)
rx = -ry-rz
else if (deltaY > deltaZ)
ry = -rx-rz
else
rz = -rx-ry
return Vector3(rx, ry, rz)
}
fun roundHexCoords(hexCoord: Vector2): Vector2 {
return cubic2HexCoords(roundCubicCoords(hex2CubicCoords(hexCoord)))
} }
fun getVectorsAtDistance(origin: Vector2, distance: Int): List<Vector2> { fun getVectorsAtDistance(origin: Vector2, distance: Int): List<Vector2> {

View File

@ -29,9 +29,9 @@ data class LocationAction(var locations: ArrayList<Vector2> = ArrayList()) : Not
override fun execute(worldScreen: WorldScreen) { override fun execute(worldScreen: WorldScreen) {
if (locations.isNotEmpty()) { if (locations.isNotEmpty()) {
var index = locations.indexOf(worldScreen.tileMapHolder.selectedTile?.position) var index = locations.indexOf(worldScreen.mapHolder.selectedTile?.position)
index = ++index % locations.size // cycle through tiles index = ++index % locations.size // cycle through tiles
worldScreen.tileMapHolder.setCenterPosition(locations[index], selectUnit = false) worldScreen.mapHolder.setCenterPosition(locations[index], selectUnit = false)
} }
} }
@ -49,7 +49,7 @@ class TechAction(val techName: String = "") : NotificationAction {
data class CityAction(val city: Vector2 = Vector2.Zero): NotificationAction { data class CityAction(val city: Vector2 = Vector2.Zero): NotificationAction {
override fun execute(worldScreen: WorldScreen) { override fun execute(worldScreen: WorldScreen) {
worldScreen.tileMapHolder.tileMap[city].getCity()?.let { worldScreen.mapHolder.tileMap[city].getCity()?.let {
worldScreen.game.setScreen(CityScreen(it)) worldScreen.game.setScreen(CityScreen(it))
} }
} }

View File

@ -307,7 +307,7 @@ class EmpireOverviewScreen(val viewingPlayer:CivilizationInfo) : CameraStageBase
val button = TextButton(unit.name.tr(), skin) val button = TextButton(unit.name.tr(), skin)
button.onClick { button.onClick {
UncivGame.Current.setWorldScreen() UncivGame.Current.setWorldScreen()
UncivGame.Current.worldScreen.tileMapHolder.setCenterPosition(unit.currentTile.position) UncivGame.Current.worldScreen.mapHolder.setCenterPosition(unit.currentTile.position)
} }
table.add(button).left() table.add(button).left()
val mapUnitAction = unit.mapUnitAction val mapUnitAction = unit.mapUnitAction

View File

@ -17,7 +17,7 @@ import com.unciv.models.stats.Stats
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.tilegroups.TileSetStrings import com.unciv.ui.tilegroups.TileSetStrings
import com.unciv.ui.utils.* import com.unciv.ui.utils.*
import com.unciv.ui.worldscreen.TileGroupMap import com.unciv.ui.map.TileGroupMap
import java.util.* import java.util.*
import kotlin.math.ceil import kotlin.math.ceil
import kotlin.math.floor import kotlin.math.floor

View File

@ -87,7 +87,7 @@ class CityScreenCityPickerTable(val cityScreen: CityScreen) : Table(){
exitCityButton.onClick { exitCityButton.onClick {
val game = cityScreen.game val game = cityScreen.game
game.setWorldScreen() game.setWorldScreen()
game.worldScreen.tileMapHolder.setCenterPosition(city.location) game.worldScreen.mapHolder.setCenterPosition(city.location)
game.worldScreen.bottomUnitTable.selectedUnit=null game.worldScreen.bottomUnitTable.selectedUnit=null
} }

View File

@ -1,19 +1,20 @@
package com.unciv.ui.worldscreen package com.unciv.ui.map
import com.badlogic.gdx.math.Vector2
import com.badlogic.gdx.scenes.scene2d.Group import com.badlogic.gdx.scenes.scene2d.Group
import com.unciv.logic.HexMath import com.unciv.logic.HexMath
import com.unciv.ui.tilegroups.TileGroup import com.unciv.ui.tilegroups.TileGroup
class TileGroupMap<T: TileGroup>(val tileGroups:Collection<T>, padding:Float): Group(){ class TileGroupMap<T: TileGroup>(val tileGroups: Collection<T>, val padding: Float): Group(){
var topX = -Float.MAX_VALUE
var topY = -Float.MAX_VALUE
var bottomX = Float.MAX_VALUE
var bottomY = Float.MAX_VALUE
val groupSize = 50
init{ init{
var topX = -Float.MAX_VALUE
var topY = -Float.MAX_VALUE
var bottomX = Float.MAX_VALUE
var bottomY = Float.MAX_VALUE
for(tileGroup in tileGroups){ for(tileGroup in tileGroups){
val positionalVector = HexMath().hex2WorldCoords(tileGroup.tileInfo.position) val positionalVector = HexMath().hex2WorldCoords(tileGroup.tileInfo.position)
val groupSize = 50
tileGroup.setPosition(positionalVector.x * 0.8f * groupSize.toFloat(), tileGroup.setPosition(positionalVector.x * 0.8f * groupSize.toFloat(),
positionalVector.y * 0.8f * groupSize.toFloat()) positionalVector.y * 0.8f * groupSize.toFloat())
@ -56,6 +57,16 @@ class TileGroupMap<T: TileGroup>(val tileGroups:Collection<T>, padding:Float): G
// 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
setSize(topX - bottomX + padding*2, topY - bottomY + padding*2) setSize(topX - bottomX + padding*2, topY - bottomY + padding*2)
}
/**
* Returns the positional coordinates of the TileGroupMap center.
*/
fun getPositionalVector(stageCoords: Vector2): Vector2 {
val trueGroupSize = 0.8f * groupSize.toFloat()
return Vector2(bottomX - padding, bottomY - padding)
.add(stageCoords)
.sub(groupSize.toFloat() / 2f, groupSize.toFloat() / 2f)
.scl(1f / trueGroupSize)
} }
} }

View File

@ -0,0 +1,73 @@
package com.unciv.ui.mapeditor
import com.badlogic.gdx.math.Vector2
import com.unciv.logic.map.TileMap
import com.unciv.logic.map.TileInfo
import com.unciv.ui.tilegroups.TileGroup
import com.unciv.ui.tilegroups.TileSetStrings
import com.unciv.ui.utils.onClick
import com.unciv.ui.map.TileGroupMap
import com.unciv.ui.utils.*
import com.unciv.logic.HexMath
class EditorMapHolder(internal val mapEditorScreen: MapEditorScreen, internal val tileMap: TileMap): ZoomableScrollPane() {
val tileGroups = HashMap<TileInfo, TileGroup>()
lateinit var tileGroupMap: TileGroupMap<TileGroup>
internal fun addTiles() {
val tileSetStrings = TileSetStrings()
for (tileGroup in tileMap.values.map { TileGroup(it, tileSetStrings) })
tileGroups[tileGroup.tileInfo] = tileGroup
tileGroupMap = TileGroupMap(tileGroups.values, mapEditorScreen.stage.width)
actor = tileGroupMap
for (tileGroup in tileGroups.values) {
tileGroup.showEntireMap = true
tileGroup.update()
tileGroup.onClick {
val distance = mapEditorScreen.tileEditorOptions.brushSize - 1
for (tileInfo in mapEditorScreen.tileMap.getTilesInDistance(tileGroup.tileInfo.position, distance)) {
mapEditorScreen.tileEditorOptions.updateTileWhenClicked(tileInfo)
tileInfo.setTransients()
tileGroups[tileInfo]!!.update()
}
}
}
setSize(mapEditorScreen.stage.width * 2, mapEditorScreen.stage.height * 2)
setOrigin(width / 2,height / 2)
center(mapEditorScreen.stage)
layout()
scrollPercentX = .5f
scrollPercentY = .5f
updateVisualScroll()
}
fun updateTileGroups() {
for (tileGroup in tileGroups.values)
tileGroup.update()
}
fun setTransients() {
for (tileInfo in tileGroups.keys)
tileInfo.setTransients()
}
fun getClosestTileTo(stageCoords: Vector2): TileInfo? {
val positionalCoords = tileGroupMap.getPositionalVector(stageCoords)
val hexPosition = HexMath().world2HexCoords(positionalCoords)
val rounded = HexMath().roundHexCoords(hexPosition)
if (tileMap.contains(rounded))
return tileMap.get(rounded)
else
return null
}
}

View File

@ -24,7 +24,7 @@ class MapEditorMenuPopup(mapEditorScreen: MapEditorScreen): PopupTable(mapEditor
val clearCurrentMapButton = TextButton("Clear current map".tr(),skin) val clearCurrentMapButton = TextButton("Clear current map".tr(),skin)
clearCurrentMapButton.onClick { clearCurrentMapButton.onClick {
for(tileGroup in mapEditorScreen.mapHolder.tileGroups) for(tileGroup in mapEditorScreen.mapHolder.tileGroups.values)
{ {
val tile = tileGroup.tileInfo val tile = tileGroup.tileInfo
tile.baseTerrain=Constants.ocean tile.baseTerrain=Constants.ocean

View File

@ -1,51 +1,59 @@
package com.unciv.ui.mapeditor package com.unciv.ui.mapeditor
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.math.Vector2
import com.badlogic.gdx.scenes.scene2d.InputEvent
import com.badlogic.gdx.scenes.scene2d.InputListener
import com.badlogic.gdx.scenes.scene2d.actions.Actions import com.badlogic.gdx.scenes.scene2d.actions.Actions
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane
import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.scenes.scene2d.ui.TextButton
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.MapSaver import com.unciv.logic.MapSaver
import com.unciv.logic.map.TileMap import com.unciv.logic.map.TileMap
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.tilegroups.TileGroup
import com.unciv.ui.tilegroups.TileSetStrings
import com.unciv.ui.utils.CameraStageBaseScreen import com.unciv.ui.utils.CameraStageBaseScreen
import com.unciv.ui.utils.onClick import com.unciv.ui.utils.onClick
import com.unciv.ui.utils.setFontSize import com.unciv.ui.utils.setFontSize
import com.unciv.ui.worldscreen.TileGroupMap
class MapEditorScreen(): CameraStageBaseScreen(){ class MapEditorScreen(): CameraStageBaseScreen() {
var tileMap = TileMap() val ruleset = UncivGame.Current.ruleset
var mapName = "My first map" var mapName = "My first map"
lateinit var mapHolder: TileGroupMap<TileGroup>
private val showHideEditorOptionsButton = TextButton(">",skin)
val ruleSet = UncivGame.Current.ruleset
private val tileEditorOptions = TileEditorOptionsTable(this)
constructor(mapNameToLoad:String?):this(){ var tileMap = TileMap()
lateinit var mapHolder: EditorMapHolder
val tileEditorOptions = TileEditorOptionsTable(this)
private val showHideEditorOptionsButton = TextButton(">", skin)
constructor(mapNameToLoad: String?): this() {
var mapToLoad = mapNameToLoad var mapToLoad = mapNameToLoad
if (mapToLoad == null) { if (mapToLoad == null) {
val existingSaves = MapSaver().getMaps() val existingSaves = MapSaver().getMaps()
if(existingSaves.isNotEmpty()) if(existingSaves.isNotEmpty())
mapToLoad = existingSaves.first() mapToLoad = existingSaves.first()
} }
if(mapToLoad!=null){
mapName=mapToLoad if(mapToLoad != null){
tileMap= MapSaver().loadMap(mapName) mapName = mapToLoad
tileMap = MapSaver().loadMap(mapName)
} }
initialize() initialize()
} }
constructor(map: TileMap):this(){ constructor(map: TileMap): this() {
tileMap = map tileMap = map
initialize() initialize()
} }
fun initialize() { fun initialize() {
tileMap.setTransients(ruleSet) tileMap.setTransients(ruleset)
val mapHolder = getMapHolder(tileMap)
mapHolder = EditorMapHolder(this, tileMap)
mapHolder.addTiles()
stage.addActor(mapHolder) stage.addActor(mapHolder)
stage.addActor(tileEditorOptions) stage.addActor(tileEditorOptions)
tileEditorOptions.setPosition(stage.width - tileEditorOptions.width, 0f) tileEditorOptions.setPosition(stage.width - tileEditorOptions.width, 0f)
@ -76,35 +84,60 @@ class MapEditorScreen(): CameraStageBaseScreen(){
optionsMenuButton.pack() optionsMenuButton.pack()
optionsMenuButton.x = 30f optionsMenuButton.x = 30f
optionsMenuButton.y = 30f optionsMenuButton.y = 30f
stage.addActor(optionsMenuButton) stage.addActor(optionsMenuButton)
}
private fun getMapHolder(tileMap: TileMap): ScrollPane { mapHolder.addCaptureListener(object: InputListener() {
val tileSetStrings = TileSetStrings() var isDragging = false
val tileGroups = tileMap.values.map { TileGroup(it, tileSetStrings) } var isPainting = false
for (tileGroup in tileGroups) { var touchDownTime = System.currentTimeMillis()
tileGroup.showEntireMap = true
tileGroup.update()
tileGroup.onClick {
val tileInfo = tileGroup.tileInfo
tileEditorOptions.updateTileWhenClicked(tileInfo) override fun touchDown(event: InputEvent?, x: Float, y: Float, pointer: Int, button: Int): Boolean {
tileGroup.tileInfo.setTransients() touchDownTime = System.currentTimeMillis()
tileGroup.update() return true
} }
}
mapHolder = TileGroupMap(tileGroups, 300f) override fun touchDragged(event: InputEvent?, x: Float, y: Float, pointer: Int) {
val scrollPane = ScrollPane(mapHolder) if (!isDragging) {
scrollPane.setSize(stage.width, stage.height) isDragging = true
scrollPane.layout() val deltaTime = System.currentTimeMillis() - touchDownTime
scrollPane.scrollPercentX=0.5f if (deltaTime > 400) {
scrollPane.scrollPercentY=0.5f isPainting = true
scrollPane.updateVisualScroll() stage.cancelTouchFocusExcept(this, mapHolder)
return scrollPane }
}
if (isPainting) {
mapHolder.updateTileGroups()
mapHolder.setTransients()
val stageCoords = mapHolder.actor.stageToLocalCoordinates(Vector2(event!!.stageX, event.stageY))
val centerTileInfo = mapHolder.getClosestTileTo(stageCoords)
if (centerTileInfo != null) {
val distance = tileEditorOptions.brushSize - 1
for (tileInfo in tileMap.getTilesInDistance(centerTileInfo.position, distance)) {
tileEditorOptions.updateTileWhenClicked(tileInfo)
tileInfo.setTransients()
mapHolder.tileGroups[tileInfo]!!.update()
mapHolder.tileGroups[tileInfo]!!.showCircle(Color.WHITE)
}
}
}
}
override fun touchUp(event: InputEvent?, x: Float, y: Float, pointer: Int, button: Int) {
// Reset tile overlays
if (isPainting) {
mapHolder.updateTileGroups()
mapHolder.setTransients()
}
isDragging = false
isPainting = false
}
})
} }
} }

View File

@ -4,8 +4,10 @@ import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Actor import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.Group import com.badlogic.gdx.scenes.scene2d.Group
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane
import com.badlogic.gdx.scenes.scene2d.ui.Slider
import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.scenes.scene2d.ui.TextButton
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener
import com.unciv.Constants import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.map.RoadStatus import com.unciv.logic.map.RoadStatus
@ -19,6 +21,7 @@ import com.unciv.ui.tilegroups.TileGroup
import com.unciv.ui.tilegroups.TileSetStrings import com.unciv.ui.tilegroups.TileSetStrings
import com.unciv.ui.utils.* import com.unciv.ui.utils.*
class TileEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(CameraStageBaseScreen.skin){ class TileEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(CameraStageBaseScreen.skin){
val tileSetLocation = "TileSets/"+ UncivGame.Current.settings.tileSet +"/" val tileSetLocation = "TileSets/"+ UncivGame.Current.settings.tileSet +"/"
@ -37,13 +40,14 @@ class TileEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(Camera
val editorPickTable = Table() val editorPickTable = Table()
var brushSize = 1
private var currentHex: Actor = Group() private var currentHex: Actor = Group()
val ruleSet = mapEditorScreen.ruleSet val ruleset = mapEditorScreen.ruleset
init{ init{
height=mapEditorScreen.stage.height height = mapEditorScreen.stage.height
width=mapEditorScreen.stage.width/3 width = mapEditorScreen.stage.width/3
setTerrainsAndResources() setTerrainsAndResources()
@ -52,10 +56,28 @@ class TileEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(Camera
.onClick { setTerrainsAndResources() } .onClick { setTerrainsAndResources() }
tabPickerTable.add(terrainsAndResourcesTabButton) tabPickerTable.add(terrainsAndResourcesTabButton)
val civLocationsButton = TextButton("Improvements".tr(),skin) val civLocationsButton = TextButton("Improvements".tr(), skin)
.onClick { setImprovements() } .onClick { setImprovements() }
tabPickerTable.add(civLocationsButton) tabPickerTable.add(civLocationsButton)
tabPickerTable.pack() tabPickerTable.pack()
val sliderTab = Table()
val slider = Slider(1f,5f,1f, false, skin)
val sliderLabel = "{Brush Size} $brushSize".toLabel()
slider.addListener(object : ChangeListener() {
override fun changed(event: ChangeEvent?, actor: Actor?) {
brushSize = slider.getValue().toInt()
sliderLabel.setText("{Brush Size} $brushSize".tr())
}
})
sliderTab.defaults().pad(5f)
sliderTab.add(sliderLabel)
sliderTab.add(slider)
add(sliderTab).row()
add(ScrollPane(tabPickerTable).apply { this.width= mapEditorScreen.stage.width/3}).row() add(ScrollPane(tabPickerTable).apply { this.width= mapEditorScreen.stage.width/3}).row()
add(editorPickTable).row() add(editorPickTable).row()
@ -74,7 +96,7 @@ class TileEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(Camera
} }
}).row() }).row()
for(improvement in ruleSet.tileImprovements.values){ for(improvement in ruleset.tileImprovements.values){
if(improvement.name.startsWith("Remove")) continue if(improvement.name.startsWith("Remove")) continue
val improvementImage = getHex(Color.WHITE,ImageGetter.getImprovementIcon(improvement.name,40f)) val improvementImage = getHex(Color.WHITE,ImageGetter.getImprovementIcon(improvement.name,40f))
improvementImage.onClick { improvementImage.onClick {
@ -88,7 +110,7 @@ class TileEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(Camera
editorPickTable.add(ScrollPane(improvementsTable)).height(mapEditorScreen.stage.height*0.7f) editorPickTable.add(ScrollPane(improvementsTable)).height(mapEditorScreen.stage.height*0.7f)
val nationsTable = Table() val nationsTable = Table()
for(nation in ruleSet.nations.values){ for(nation in ruleset.nations.values){
val nationImage = getHex(Color.WHITE,ImageGetter.getNationIndicator(nation,40f)) val nationImage = getHex(Color.WHITE,ImageGetter.getNationIndicator(nation,40f))
nationImage.onClick { nationImage.onClick {
clearSelection() clearSelection()
@ -151,16 +173,16 @@ class TileEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(Camera
} }
}) })
for (resource in ruleSet.tileResources.values) { for (resource in ruleset.tileResources.values) {
val resourceHex = getHex(Color.WHITE, ImageGetter.getResourceImage(resource.name, 40f)) val resourceHex = getHex(Color.WHITE, ImageGetter.getResourceImage(resource.name, 40f))
resourceHex.onClick { resourceHex.onClick {
clearSelection() clearSelection()
selectedResource = resource selectedResource = resource
val tileInfo = TileInfo() val tileInfo = TileInfo()
tileInfo.ruleset = mapEditorScreen.ruleSet tileInfo.ruleset = mapEditorScreen.ruleset
val terrain = resource.terrainsCanBeFoundOn.first() val terrain = resource.terrainsCanBeFoundOn.first()
val terrainObject = ruleSet.terrains[terrain]!! val terrainObject = ruleset.terrains[terrain]!!
if (terrainObject.type == TerrainType.TerrainFeature) { if (terrainObject.type == TerrainType.TerrainFeature) {
tileInfo.baseTerrain = when { tileInfo.baseTerrain = when {
terrainObject.occursOn == null -> terrainObject.occursOn!!.first() terrainObject.occursOn == null -> terrainObject.occursOn!!.first()
@ -179,9 +201,9 @@ class TileEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(Camera
} }
private fun addTerrainOptions(terrainFeaturesTable: Table, baseTerrainTable: Table) { private fun addTerrainOptions(terrainFeaturesTable: Table, baseTerrainTable: Table) {
for (terrain in ruleSet.terrains.values) { for (terrain in ruleset.terrains.values) {
val tileInfo = TileInfo() val tileInfo = TileInfo()
tileInfo.ruleset = mapEditorScreen.ruleSet tileInfo.ruleset = mapEditorScreen.ruleset
if (terrain.type == TerrainType.TerrainFeature) { if (terrain.type == TerrainType.TerrainFeature) {
tileInfo.baseTerrain = when { tileInfo.baseTerrain = when {
terrain.occursOn != null -> terrain.occursOn.first() terrain.occursOn != null -> terrain.occursOn.first()
@ -318,7 +340,7 @@ class TileEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(Camera
else tileInfo.improvement = improvement.name else tileInfo.improvement = improvement.name
if(improvement.name.startsWith("StartingLocation")) if(improvement.name.startsWith("StartingLocation"))
for(tileGroup in mapEditorScreen.mapHolder.tileGroups){ for(tileGroup in mapEditorScreen.mapHolder.tileGroups.values){
val tile = tileGroup.tileInfo val tile = tileGroup.tileInfo
if(tile.improvement==improvement.name && tile!=tileInfo) if(tile.improvement==improvement.name && tile!=tileInfo)
tile.improvement=null tile.improvement=null
@ -332,7 +354,6 @@ class TileEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(Camera
} }
normalizeTile(tileInfo) normalizeTile(tileInfo)
tileInfo.setTransients()
} }
fun normalizeTile(tileInfo: TileInfo){ fun normalizeTile(tileInfo: TileInfo){

View File

@ -0,0 +1,48 @@
package com.unciv.ui.utils
import com.badlogic.gdx.scenes.scene2d.InputEvent
import com.badlogic.gdx.scenes.scene2d.InputListener
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane
import com.badlogic.gdx.scenes.scene2d.utils.ActorGestureListener
import kotlin.math.sqrt
open class ZoomableScrollPane: ScrollPane(null) {
init{
// Remove the existing inputListener
// which defines that mouse scroll = vertical movement
val zoomListener = listeners.last { it is InputListener && it !in captureListeners }
removeListener(zoomListener)
addZoomListeners()
}
fun zoom(zoomScale: Float) {
if (zoomScale < 0.5f) return
setScale(zoomScale)
}
private fun addZoomListeners() {
addListener(object: InputListener() {
override fun scrolled(event: InputEvent?, x: Float, y: Float, amount: Int): Boolean {
if(amount > 0) zoom(scaleX * 0.8f)
else zoom(scaleX / 0.8f)
return false
}
})
addListener(object : ActorGestureListener() {
var lastScale = 1f
var lastInitialDistance = 0f
override fun zoom(event: InputEvent?, initialDistance: Float, distance: Float) {
if (lastInitialDistance != initialDistance) {
lastInitialDistance = initialDistance
lastScale = scaleX
}
val scale: Float = sqrt((distance / initialDistance).toDouble()).toFloat() * lastScale
zoom(scale)
}
})
}
}

View File

@ -16,14 +16,14 @@ import com.unciv.ui.utils.ImageGetter
import com.unciv.ui.utils.onClick import com.unciv.ui.utils.onClick
import com.unciv.ui.utils.surroundWithCircle import com.unciv.ui.utils.surroundWithCircle
class Minimap(val tileMapHolder: TileMapHolder) : ScrollPane(null){ class Minimap(val mapHolder: WorldMapHolder) : ScrollPane(null){
val allTiles = Group() val allTiles = Group()
val tileImages = HashMap<TileInfo, Image>() val tileImages = HashMap<TileInfo, Image>()
fun setScrollToTileMapHolder(){ fun setScrollTomapHolder(){
scrollPercentX = tileMapHolder.scrollPercentX scrollPercentX = mapHolder.scrollPercentX
scrollPercentY = tileMapHolder.scrollPercentY scrollPercentY = mapHolder.scrollPercentY
} }
init{ init{
@ -32,7 +32,7 @@ class Minimap(val tileMapHolder: TileMapHolder) : ScrollPane(null){
var bottomX = 0f var bottomX = 0f
var bottomY = 0f var bottomY = 0f
for (tileInfo in tileMapHolder.tileMap.values) { for (tileInfo in mapHolder.tileMap.values) {
val hex = ImageGetter.getImage("OtherIcons/Hexagon") val hex = ImageGetter.getImage("OtherIcons/Hexagon")
val positionalVector = HexMath().hex2WorldCoords(tileInfo.position) val positionalVector = HexMath().hex2WorldCoords(tileInfo.position)
@ -41,8 +41,8 @@ class Minimap(val tileMapHolder: TileMapHolder) : ScrollPane(null){
hex.setPosition(positionalVector.x * 0.5f * groupSize, hex.setPosition(positionalVector.x * 0.5f * groupSize,
positionalVector.y * 0.5f * groupSize) positionalVector.y * 0.5f * groupSize)
hex.onClick { hex.onClick {
tileMapHolder.setCenterPosition(tileInfo.position) mapHolder.setCenterPosition(tileInfo.position)
setScrollToTileMapHolder() setScrollTomapHolder()
} }
allTiles.addActor(hex) allTiles.addActor(hex)
tileImages[tileInfo] = hex tileImages[tileInfo] = hex
@ -61,19 +61,19 @@ class Minimap(val tileMapHolder: TileMapHolder) : ScrollPane(null){
// 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
allTiles.setSize(10 + topX - bottomX, 10 + topY - bottomY) allTiles.setSize(10 + topX - bottomX, 10 + topY - bottomY)
widget = allTiles actor = allTiles
layout() layout()
updateVisualScroll() updateVisualScroll()
tileMapHolder.addListener(object : InputListener(){ mapHolder.addListener(object : InputListener(){
override fun handle(e: Event?): Boolean { override fun handle(e: Event?): Boolean {
setScrollToTileMapHolder() setScrollTomapHolder()
return true return true
} }
}) })
} }
fun update(cloneCivilization: CivilizationInfo) { fun update(cloneCivilization: CivilizationInfo) {
for(tileInfo in tileMapHolder.tileMap.values) { for(tileInfo in mapHolder.tileMap.values) {
val hex = tileImages[tileInfo]!! val hex = tileImages[tileInfo]!!
if (!(UncivGame.Current.viewEntireMapForDebug || cloneCivilization.exploredTiles.contains(tileInfo.position))) if (!(UncivGame.Current.viewEntireMapForDebug || cloneCivilization.exploredTiles.contains(tileInfo.position)))
hex.color = Color.DARK_GRAY hex.color = Color.DARK_GRAY
@ -86,9 +86,9 @@ class Minimap(val tileMapHolder: TileMapHolder) : ScrollPane(null){
} }
} }
class MinimapHolder(tileMapHolder: TileMapHolder): Table(){ class MinimapHolder(mapHolder: WorldMapHolder): Table(){
val minimap = Minimap(tileMapHolder) val minimap = Minimap(mapHolder)
val worldScreen = tileMapHolder.worldScreen val worldScreen = mapHolder.worldScreen
init{ init{
add(getToggleIcons()).align(Align.bottom) add(getToggleIcons()).align(Align.bottom)

View File

@ -6,9 +6,7 @@ import com.badlogic.gdx.math.Interpolation
import com.badlogic.gdx.math.Vector2 import com.badlogic.gdx.math.Vector2
import com.badlogic.gdx.scenes.scene2d.* import com.badlogic.gdx.scenes.scene2d.*
import com.badlogic.gdx.scenes.scene2d.actions.FloatAction import com.badlogic.gdx.scenes.scene2d.actions.FloatAction
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane
import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.utils.ActorGestureListener
import com.unciv.Constants import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.automation.UnitAutomation import com.unciv.logic.automation.UnitAutomation
@ -21,26 +19,18 @@ import com.unciv.models.UncivSound
import com.unciv.models.ruleset.unit.UnitType import com.unciv.models.ruleset.unit.UnitType
import com.unciv.ui.tilegroups.TileSetStrings import com.unciv.ui.tilegroups.TileSetStrings
import com.unciv.ui.tilegroups.WorldTileGroup import com.unciv.ui.tilegroups.WorldTileGroup
import com.unciv.ui.map.TileGroupMap
import com.unciv.ui.utils.* import com.unciv.ui.utils.*
import com.unciv.ui.worldscreen.unit.UnitContextMenu import com.unciv.ui.worldscreen.unit.UnitContextMenu
import kotlin.concurrent.thread import kotlin.concurrent.thread
import kotlin.math.sqrt
class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap: TileMap): ZoomableScrollPane() {
class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap: TileMap) : ScrollPane(null) {
internal var selectedTile: TileInfo? = null internal var selectedTile: TileInfo? = null
val tileGroups = HashMap<TileInfo, WorldTileGroup>() val tileGroups = HashMap<TileInfo, WorldTileGroup>()
var unitActionOverlay :Actor?=null var unitActionOverlay :Actor?=null
init{
// Remove the existing inputListener
// which defines that mouse scroll = vertical movement
val zoomListener = listeners.last { it is InputListener && it !in captureListeners }
removeListener (zoomListener)
}
// Used to transfer data on the "move here" button that should be created, from the side thread to the main thread // Used to transfer data on the "move here" button that should be created, from the side thread to the main thread
class MoveHereButtonDto(val unit: MapUnit, val tileInfo: TileInfo, val turnsToGetThere: Int) class MoveHereButtonDto(val unit: MapUnit, val tileInfo: TileInfo, val turnsToGetThere: Int)
@ -65,46 +55,9 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap:
setOrigin(width/2,height/2) setOrigin(width/2,height/2)
center(worldScreen.stage) center(worldScreen.stage)
addGestureListener()
addListener(object :InputListener(){
override fun scrolled(event: InputEvent?, x: Float, y: Float, amount: Int): Boolean {
if(amount>0) zoom(scaleX*0.8f)
else zoom(scaleX/0.8f)
return false
}
})
layout() // Fit the scroll pane to the contents - otherwise, setScroll won't work! layout() // Fit the scroll pane to the contents - otherwise, setScroll won't work!
} }
fun zoom(zoomScale:Float){
if (zoomScale < 0.5f) return
setScale(zoomScale)
for (tilegroup in tileGroups.values.filter { it.cityButton != null })
tilegroup.cityButton!!.setScale(1 / zoomScale)
}
private fun addGestureListener() {
addListener(object : ActorGestureListener() {
var lastScale = 1f
var lastInitialDistance = 0f
override fun zoom(event: InputEvent?, initialDistance: Float, distance: Float) {
// deselect any unit, as zooming occasionally forwards clicks on to the map
worldScreen.bottomUnitTable.selectedUnit = null
worldScreen.shouldUpdate = true
if (lastInitialDistance != initialDistance) {
lastInitialDistance = initialDistance
lastScale = scaleX
}
val scale: Float = sqrt((distance / initialDistance).toDouble()).toFloat() * lastScale
zoom(scale)
}
})
}
private fun onTileClicked(tileInfo: TileInfo) { private fun onTileClicked(tileInfo: TileInfo) {
unitActionOverlay?.remove() unitActionOverlay?.remove()
selectedTile = tileInfo selectedTile = tileInfo
@ -357,5 +310,4 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap:
worldScreen.shouldUpdate=true worldScreen.shouldUpdate=true
} }
} }

View File

@ -48,8 +48,8 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
var isPlayersTurn = viewingCiv == gameInfo.currentPlayerCiv // todo this should be updated when passing turns var isPlayersTurn = viewingCiv == gameInfo.currentPlayerCiv // todo this should be updated when passing turns
var waitingForAutosave = false var waitingForAutosave = false
val tileMapHolder: TileMapHolder = TileMapHolder(this, gameInfo.tileMap) val mapHolder = WorldMapHolder(this, gameInfo.tileMap)
val minimapWrapper = MinimapHolder(tileMapHolder) val minimapWrapper = MinimapHolder(mapHolder)
private val topBar = WorldScreenTopBar(this) private val topBar = WorldScreenTopBar(this)
val bottomUnitTable = UnitTable(this) val bottomUnitTable = UnitTable(this)
@ -76,7 +76,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
minimapWrapper.x = stage.width - minimapWrapper.width minimapWrapper.x = stage.width - minimapWrapper.width
tileMapHolder.addTiles() mapHolder.addTiles()
techButtonHolder.touchable=Touchable.enabled techButtonHolder.touchable=Touchable.enabled
techButtonHolder.onClick(UncivSound.Paper) { techButtonHolder.onClick(UncivSound.Paper) {
@ -92,7 +92,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
techPolicyAndVictoryHolder.add(policyScreenButton).pad(10f) techPolicyAndVictoryHolder.add(policyScreenButton).pad(10f)
} }
stage.addActor(tileMapHolder) stage.addActor(mapHolder)
stage.addActor(minimapWrapper) stage.addActor(minimapWrapper)
stage.addActor(topBar) stage.addActor(topBar)
stage.addActor(nextTurnButton) stage.addActor(nextTurnButton)
@ -119,7 +119,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
viewingCiv.getCivUnits().isNotEmpty() -> viewingCiv.getCivUnits().first().getTile().position viewingCiv.getCivUnits().isNotEmpty() -> viewingCiv.getCivUnits().first().getTile().position
else -> Vector2.Zero else -> Vector2.Zero
} }
tileMapHolder.setCenterPosition(tileToCenterOn,true) mapHolder.setCenterPosition(tileToCenterOn,true)
if(gameInfo.gameParameters.isOnlineMultiplayer && !gameInfo.isUpToDate) if(gameInfo.gameParameters.isOnlineMultiplayer && !gameInfo.isUpToDate)
@ -173,7 +173,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
displayTutorialsOnUpdate() displayTutorialsOnUpdate()
bottomUnitTable.update() bottomUnitTable.update()
bottomTileInfoTable.updateTileTable(tileMapHolder.selectedTile!!) bottomTileInfoTable.updateTileTable(mapHolder.selectedTile!!)
bottomTileInfoTable.x = stage.width - bottomTileInfoTable.width bottomTileInfoTable.x = stage.width - bottomTileInfoTable.width
bottomTileInfoTable.y = if (UncivGame.Current.settings.showMinimap) minimapWrapper.height else 0f bottomTileInfoTable.y = if (UncivGame.Current.settings.showMinimap) minimapWrapper.height else 0f
battleTable.update() battleTable.update()
@ -198,7 +198,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
// if we use the clone, then when we update viewable tiles // if we use the clone, then when we update viewable tiles
// it doesn't update the explored tiles of the civ... need to think about that harder // it doesn't update the explored tiles of the civ... need to think about that harder
// it causes a bug when we move a unit to an unexplored tile (for instance a cavalry unit which can move far) // it causes a bug when we move a unit to an unexplored tile (for instance a cavalry unit which can move far)
tileMapHolder.updateTiles(viewingCiv) mapHolder.updateTiles(viewingCiv)
topBar.update(viewingCiv) topBar.update(viewingCiv)
@ -344,7 +344,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
if (viewingCiv.shouldGoToDueUnit()) { if (viewingCiv.shouldGoToDueUnit()) {
val nextDueUnit = viewingCiv.getNextDueUnit() val nextDueUnit = viewingCiv.getNextDueUnit()
if(nextDueUnit!=null) { if(nextDueUnit!=null) {
tileMapHolder.setCenterPosition(nextDueUnit.currentTile.position, false, false) mapHolder.setCenterPosition(nextDueUnit.currentTile.position, false, false)
bottomUnitTable.selectedUnit = nextDueUnit bottomUnitTable.selectedUnit = nextDueUnit
shouldUpdate=true shouldUpdate=true
} }
@ -413,11 +413,11 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
fun createNewWorldScreen(){ fun createNewWorldScreen(){
val newWorldScreen = WorldScreen(gameInfoClone.getPlayerToViewAs()) val newWorldScreen = WorldScreen(gameInfoClone.getPlayerToViewAs())
newWorldScreen.tileMapHolder.scrollX = tileMapHolder.scrollX newWorldScreen.mapHolder.scrollX = mapHolder.scrollX
newWorldScreen.tileMapHolder.scrollY = tileMapHolder.scrollY newWorldScreen.mapHolder.scrollY = mapHolder.scrollY
newWorldScreen.tileMapHolder.scaleX = tileMapHolder.scaleX newWorldScreen.mapHolder.scaleX = mapHolder.scaleX
newWorldScreen.tileMapHolder.scaleY = tileMapHolder.scaleY newWorldScreen.mapHolder.scaleY = mapHolder.scaleY
newWorldScreen.tileMapHolder.updateVisualScroll() newWorldScreen.mapHolder.updateVisualScroll()
game.worldScreen = newWorldScreen game.worldScreen = newWorldScreen
game.setWorldScreen() game.setWorldScreen()
} }

View File

@ -61,8 +61,8 @@ class BattleTable(val worldScreen: WorldScreen): Table() {
private fun tryGetDefender(): ICombatant? { private fun tryGetDefender(): ICombatant? {
val attackerCiv = worldScreen.viewingCiv val attackerCiv = worldScreen.viewingCiv
if (worldScreen.tileMapHolder.selectedTile == null) return null // no selected tile if (worldScreen.mapHolder.selectedTile == null) return null // no selected tile
val selectedTile = worldScreen.tileMapHolder.selectedTile!! val selectedTile = worldScreen.mapHolder.selectedTile!!
val defender: ICombatant? = Battle(worldScreen.gameInfo).getMapCombatantOfTile(selectedTile) val defender: ICombatant? = Battle(worldScreen.gameInfo).getMapCombatantOfTile(selectedTile)
@ -197,7 +197,7 @@ class BattleTable(val worldScreen: WorldScreen): Table() {
attackButton.onClick { attackButton.onClick {
try { try {
battle.moveAndAttack(attacker, attackableEnemy) battle.moveAndAttack(attacker, attackableEnemy)
worldScreen.tileMapHolder.unitActionOverlay?.remove() // the overlay was one of attacking worldScreen.mapHolder.unitActionOverlay?.remove() // the overlay was one of attacking
worldScreen.shouldUpdate = true worldScreen.shouldUpdate = true
} }
catch (ex:Exception){ catch (ex:Exception){

View File

@ -7,11 +7,11 @@ import com.badlogic.gdx.utils.Align
import com.unciv.logic.map.MapUnit import com.unciv.logic.map.MapUnit
import com.unciv.ui.utils.ImageGetter import com.unciv.ui.utils.ImageGetter
import com.unciv.ui.utils.onClick import com.unciv.ui.utils.onClick
import com.unciv.ui.worldscreen.TileMapHolder import com.unciv.ui.worldscreen.WorldMapHolder
class IdleUnitButton ( class IdleUnitButton (
internal val unitTable: UnitTable, internal val unitTable: UnitTable,
val tileMapHolder: TileMapHolder, val tileMapHolder: WorldMapHolder,
val previous:Boolean val previous:Boolean
) : Table() { ) : Table() {

View File

@ -13,10 +13,10 @@ import com.unciv.ui.utils.CameraStageBaseScreen
import com.unciv.ui.utils.ImageGetter import com.unciv.ui.utils.ImageGetter
import com.unciv.ui.utils.Sounds import com.unciv.ui.utils.Sounds
import com.unciv.ui.utils.onClick import com.unciv.ui.utils.onClick
import com.unciv.ui.worldscreen.TileMapHolder import com.unciv.ui.worldscreen.WorldMapHolder
import kotlin.concurrent.thread import kotlin.concurrent.thread
class UnitContextMenu(val tileMapHolder: TileMapHolder, val selectedUnit: MapUnit, val targetTile: TileInfo) : VerticalGroup() { class UnitContextMenu(val tileMapHolder: WorldMapHolder, val selectedUnit: MapUnit, val targetTile: TileInfo) : VerticalGroup() {
init { init {

View File

@ -16,8 +16,8 @@ import com.unciv.ui.utils.*
import com.unciv.ui.worldscreen.WorldScreen import com.unciv.ui.worldscreen.WorldScreen
class UnitTable(val worldScreen: WorldScreen) : Table(){ class UnitTable(val worldScreen: WorldScreen) : Table(){
private val prevIdleUnitButton = IdleUnitButton(this,worldScreen.tileMapHolder,true) private val prevIdleUnitButton = IdleUnitButton(this,worldScreen.mapHolder,true)
private val nextIdleUnitButton = IdleUnitButton(this,worldScreen.tileMapHolder,false) private val nextIdleUnitButton = IdleUnitButton(this,worldScreen.mapHolder,false)
private val unitIconHolder=Table() private val unitIconHolder=Table()
private val unitNameLabel = "".toLabel() private val unitNameLabel = "".toLabel()
private val promotionsTable = Table() private val promotionsTable = Table()
@ -64,7 +64,7 @@ class UnitTable(val worldScreen: WorldScreen) : Table(){
touchable = Touchable.enabled touchable = Touchable.enabled
onClick { onClick {
selectedUnit?.currentTile?.position?.let { selectedUnit?.currentTile?.position?.let {
worldScreen.tileMapHolder.setCenterPosition(it, false, false) worldScreen.mapHolder.setCenterPosition(it, false, false)
} }
} }
}).expand() }).expand()