diff --git a/android/assets/jsons/translationsByLanguage/Italian.properties b/android/assets/jsons/translationsByLanguage/Italian.properties index e2837fc5e7..b34cfe04dd 100644 --- a/android/assets/jsons/translationsByLanguage/Italian.properties +++ b/android/assets/jsons/translationsByLanguage/Italian.properties @@ -945,6 +945,7 @@ Clear improvements = Elimina miglioramenti Clear resource = Elimina risorsa Requires = Richiede Menu = Menu +Brush Size = Dimensione pennello # Civilopedia Tutorials names diff --git a/android/assets/jsons/translationsByLanguage/template.properties b/android/assets/jsons/translationsByLanguage/template.properties index 1f014ab8e6..a79d4722bc 100644 --- a/android/assets/jsons/translationsByLanguage/template.properties +++ b/android/assets/jsons/translationsByLanguage/template.properties @@ -945,6 +945,7 @@ Clear improvements = Clear resource = Requires = Menu = +Brush Size = # Civilopedia Tutorials names diff --git a/core/src/com/unciv/logic/HexMath.kt b/core/src/com/unciv/logic/HexMath.kt index d4127e1731..6b30b7029d 100644 --- a/core/src/com/unciv/logic/HexMath.kt +++ b/core/src/com/unciv/logic/HexMath.kt @@ -1,10 +1,8 @@ package com.unciv.logic import com.badlogic.gdx.math.Vector2 -import java.util.* -import kotlin.math.abs -import kotlin.math.max -import kotlin.math.sqrt +import com.badlogic.gdx.math.Vector3 +import kotlin.math.* class HexMath { @@ -16,6 +14,18 @@ class HexMath { return getVectorForAngle((2 * Math.PI * (hour / 12f)).toFloat()) } + fun getAdjacentVectors(origin: Vector2): ArrayList { + val vectors = ArrayList() + 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) // 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)) } - fun getAdjacentVectors(origin: Vector2): ArrayList { - val vectors = ArrayList() - 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 + fun world2HexCoords(worldCoord: Vector2): Vector2 { + // D: diagonal, A: antidiagonal versors + val D = getVectorByClockHour(10).scl(sqrt(3.0).toFloat()) + val A = getVectorByClockHour(2).scl(sqrt(3.0).toFloat()) + val den = D.x * A.y - D.y * A.x + val x = (worldCoord.x * A.y - worldCoord.y * A.x) / den + val y = (worldCoord.y * D.x - worldCoord.x * D.y) / den + return Vector2(x, y) + } + + 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 { diff --git a/core/src/com/unciv/logic/civilization/Notification.kt b/core/src/com/unciv/logic/civilization/Notification.kt index a332f4ba3b..ee4caef9a9 100644 --- a/core/src/com/unciv/logic/civilization/Notification.kt +++ b/core/src/com/unciv/logic/civilization/Notification.kt @@ -29,9 +29,9 @@ data class LocationAction(var locations: ArrayList = ArrayList()) : Not override fun execute(worldScreen: WorldScreen) { 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 - 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 { override fun execute(worldScreen: WorldScreen) { - worldScreen.tileMapHolder.tileMap[city].getCity()?.let { + worldScreen.mapHolder.tileMap[city].getCity()?.let { worldScreen.game.setScreen(CityScreen(it)) } } diff --git a/core/src/com/unciv/ui/EmpireOverviewScreen.kt b/core/src/com/unciv/ui/EmpireOverviewScreen.kt index 5cdfbe0f42..bd149ffa88 100644 --- a/core/src/com/unciv/ui/EmpireOverviewScreen.kt +++ b/core/src/com/unciv/ui/EmpireOverviewScreen.kt @@ -307,7 +307,7 @@ class EmpireOverviewScreen(val viewingPlayer:CivilizationInfo) : CameraStageBase val button = TextButton(unit.name.tr(), skin) button.onClick { UncivGame.Current.setWorldScreen() - UncivGame.Current.worldScreen.tileMapHolder.setCenterPosition(unit.currentTile.position) + UncivGame.Current.worldScreen.mapHolder.setCenterPosition(unit.currentTile.position) } table.add(button).left() val mapUnitAction = unit.mapUnitAction diff --git a/core/src/com/unciv/ui/cityscreen/CityScreen.kt b/core/src/com/unciv/ui/cityscreen/CityScreen.kt index f9289656dd..d9dc101447 100644 --- a/core/src/com/unciv/ui/cityscreen/CityScreen.kt +++ b/core/src/com/unciv/ui/cityscreen/CityScreen.kt @@ -17,7 +17,7 @@ import com.unciv.models.stats.Stats import com.unciv.models.translations.tr import com.unciv.ui.tilegroups.TileSetStrings import com.unciv.ui.utils.* -import com.unciv.ui.worldscreen.TileGroupMap +import com.unciv.ui.map.TileGroupMap import java.util.* import kotlin.math.ceil import kotlin.math.floor diff --git a/core/src/com/unciv/ui/cityscreen/CityScreenCityPickerTable.kt b/core/src/com/unciv/ui/cityscreen/CityScreenCityPickerTable.kt index 054c71b16c..e637997e82 100644 --- a/core/src/com/unciv/ui/cityscreen/CityScreenCityPickerTable.kt +++ b/core/src/com/unciv/ui/cityscreen/CityScreenCityPickerTable.kt @@ -87,7 +87,7 @@ class CityScreenCityPickerTable(val cityScreen: CityScreen) : Table(){ exitCityButton.onClick { val game = cityScreen.game game.setWorldScreen() - game.worldScreen.tileMapHolder.setCenterPosition(city.location) + game.worldScreen.mapHolder.setCenterPosition(city.location) game.worldScreen.bottomUnitTable.selectedUnit=null } diff --git a/core/src/com/unciv/ui/worldscreen/TileGroupMap.kt b/core/src/com/unciv/ui/map/TileGroupMap.kt similarity index 78% rename from core/src/com/unciv/ui/worldscreen/TileGroupMap.kt rename to core/src/com/unciv/ui/map/TileGroupMap.kt index 5bb462845d..6d27d31f02 100644 --- a/core/src/com/unciv/ui/worldscreen/TileGroupMap.kt +++ b/core/src/com/unciv/ui/map/TileGroupMap.kt @@ -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.unciv.logic.HexMath import com.unciv.ui.tilegroups.TileGroup -class TileGroupMap(val tileGroups:Collection, padding:Float): Group(){ +class TileGroupMap(val tileGroups: Collection, 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{ - var topX = -Float.MAX_VALUE - var topY = -Float.MAX_VALUE - var bottomX = Float.MAX_VALUE - var bottomY = Float.MAX_VALUE - for(tileGroup in tileGroups){ val positionalVector = HexMath().hex2WorldCoords(tileGroup.tileInfo.position) - val groupSize = 50 + tileGroup.setPosition(positionalVector.x * 0.8f * groupSize.toFloat(), positionalVector.y * 0.8f * groupSize.toFloat()) @@ -56,6 +57,16 @@ class TileGroupMap(val tileGroups:Collection, padding:Float): G // there are tiles "below the zero", // 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) + } + /** + * 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) } } \ No newline at end of file diff --git a/core/src/com/unciv/ui/mapeditor/EditorMapHolder.kt b/core/src/com/unciv/ui/mapeditor/EditorMapHolder.kt new file mode 100644 index 0000000000..dc0f875bfc --- /dev/null +++ b/core/src/com/unciv/ui/mapeditor/EditorMapHolder.kt @@ -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() + lateinit var tileGroupMap: TileGroupMap + + 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 + } +} \ No newline at end of file diff --git a/core/src/com/unciv/ui/mapeditor/MapEditorMenuPopup.kt b/core/src/com/unciv/ui/mapeditor/MapEditorMenuPopup.kt index 124d0784e4..0851143be0 100644 --- a/core/src/com/unciv/ui/mapeditor/MapEditorMenuPopup.kt +++ b/core/src/com/unciv/ui/mapeditor/MapEditorMenuPopup.kt @@ -24,7 +24,7 @@ class MapEditorMenuPopup(mapEditorScreen: MapEditorScreen): PopupTable(mapEditor val clearCurrentMapButton = TextButton("Clear current map".tr(),skin) clearCurrentMapButton.onClick { - for(tileGroup in mapEditorScreen.mapHolder.tileGroups) + for(tileGroup in mapEditorScreen.mapHolder.tileGroups.values) { val tile = tileGroup.tileInfo tile.baseTerrain=Constants.ocean diff --git a/core/src/com/unciv/ui/mapeditor/MapEditorScreen.kt b/core/src/com/unciv/ui/mapeditor/MapEditorScreen.kt index af7ef9fbaf..da24f25525 100644 --- a/core/src/com/unciv/ui/mapeditor/MapEditorScreen.kt +++ b/core/src/com/unciv/ui/mapeditor/MapEditorScreen.kt @@ -1,51 +1,59 @@ 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.ui.ScrollPane import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.unciv.UncivGame import com.unciv.logic.MapSaver import com.unciv.logic.map.TileMap 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.onClick import com.unciv.ui.utils.setFontSize -import com.unciv.ui.worldscreen.TileGroupMap -class MapEditorScreen(): CameraStageBaseScreen(){ - var tileMap = TileMap() +class MapEditorScreen(): CameraStageBaseScreen() { + val ruleset = UncivGame.Current.ruleset var mapName = "My first map" - lateinit var mapHolder: TileGroupMap - 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 if (mapToLoad == null) { val existingSaves = MapSaver().getMaps() if(existingSaves.isNotEmpty()) mapToLoad = existingSaves.first() } - if(mapToLoad!=null){ - mapName=mapToLoad - tileMap= MapSaver().loadMap(mapName) + + if(mapToLoad != null){ + mapName = mapToLoad + tileMap = MapSaver().loadMap(mapName) } + initialize() } - constructor(map: TileMap):this(){ + constructor(map: TileMap): this() { tileMap = map initialize() } fun initialize() { - tileMap.setTransients(ruleSet) - val mapHolder = getMapHolder(tileMap) + tileMap.setTransients(ruleset) + mapHolder = EditorMapHolder(this, tileMap) + mapHolder.addTiles() stage.addActor(mapHolder) + stage.addActor(tileEditorOptions) tileEditorOptions.setPosition(stage.width - tileEditorOptions.width, 0f) @@ -76,35 +84,60 @@ class MapEditorScreen(): CameraStageBaseScreen(){ optionsMenuButton.pack() optionsMenuButton.x = 30f optionsMenuButton.y = 30f - stage.addActor(optionsMenuButton) - } - private fun getMapHolder(tileMap: TileMap): ScrollPane { - val tileSetStrings = TileSetStrings() - val tileGroups = tileMap.values.map { TileGroup(it, tileSetStrings) } - for (tileGroup in tileGroups) { - tileGroup.showEntireMap = true - tileGroup.update() - tileGroup.onClick { - val tileInfo = tileGroup.tileInfo + mapHolder.addCaptureListener(object: InputListener() { + var isDragging = false + var isPainting = false + var touchDownTime = System.currentTimeMillis() - tileEditorOptions.updateTileWhenClicked(tileInfo) - tileGroup.tileInfo.setTransients() - tileGroup.update() + override fun touchDown(event: InputEvent?, x: Float, y: Float, pointer: Int, button: Int): Boolean { + touchDownTime = System.currentTimeMillis() + return true } - } - mapHolder = TileGroupMap(tileGroups, 300f) - val scrollPane = ScrollPane(mapHolder) - scrollPane.setSize(stage.width, stage.height) - scrollPane.layout() - scrollPane.scrollPercentX=0.5f - scrollPane.scrollPercentY=0.5f - scrollPane.updateVisualScroll() - return scrollPane + override fun touchDragged(event: InputEvent?, x: Float, y: Float, pointer: Int) { + if (!isDragging) { + isDragging = true + val deltaTime = System.currentTimeMillis() - touchDownTime + if (deltaTime > 400) { + isPainting = true + stage.cancelTouchFocusExcept(this, mapHolder) + } + } + + 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 + } + }) } - - } diff --git a/core/src/com/unciv/ui/mapeditor/TileEditorOptionsTable.kt b/core/src/com/unciv/ui/mapeditor/TileEditorOptionsTable.kt index 57ebcae1b4..ba9be5d69d 100644 --- a/core/src/com/unciv/ui/mapeditor/TileEditorOptionsTable.kt +++ b/core/src/com/unciv/ui/mapeditor/TileEditorOptionsTable.kt @@ -4,8 +4,10 @@ import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.Actor import com.badlogic.gdx.scenes.scene2d.Group 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.TextButton +import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener import com.unciv.Constants import com.unciv.UncivGame 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.utils.* + class TileEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(CameraStageBaseScreen.skin){ val tileSetLocation = "TileSets/"+ UncivGame.Current.settings.tileSet +"/" @@ -37,13 +40,14 @@ class TileEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(Camera val editorPickTable = Table() + var brushSize = 1 private var currentHex: Actor = Group() - val ruleSet = mapEditorScreen.ruleSet + val ruleset = mapEditorScreen.ruleset init{ - height=mapEditorScreen.stage.height - width=mapEditorScreen.stage.width/3 + height = mapEditorScreen.stage.height + width = mapEditorScreen.stage.width/3 setTerrainsAndResources() @@ -52,10 +56,28 @@ class TileEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(Camera .onClick { setTerrainsAndResources() } tabPickerTable.add(terrainsAndResourcesTabButton) - val civLocationsButton = TextButton("Improvements".tr(),skin) + val civLocationsButton = TextButton("Improvements".tr(), skin) .onClick { setImprovements() } tabPickerTable.add(civLocationsButton) 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(editorPickTable).row() @@ -74,7 +96,7 @@ class TileEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(Camera } }).row() - for(improvement in ruleSet.tileImprovements.values){ + for(improvement in ruleset.tileImprovements.values){ if(improvement.name.startsWith("Remove")) continue val improvementImage = getHex(Color.WHITE,ImageGetter.getImprovementIcon(improvement.name,40f)) improvementImage.onClick { @@ -88,7 +110,7 @@ class TileEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(Camera editorPickTable.add(ScrollPane(improvementsTable)).height(mapEditorScreen.stage.height*0.7f) val nationsTable = Table() - for(nation in ruleSet.nations.values){ + for(nation in ruleset.nations.values){ val nationImage = getHex(Color.WHITE,ImageGetter.getNationIndicator(nation,40f)) nationImage.onClick { 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)) resourceHex.onClick { clearSelection() selectedResource = resource val tileInfo = TileInfo() - tileInfo.ruleset = mapEditorScreen.ruleSet + tileInfo.ruleset = mapEditorScreen.ruleset val terrain = resource.terrainsCanBeFoundOn.first() - val terrainObject = ruleSet.terrains[terrain]!! + val terrainObject = ruleset.terrains[terrain]!! if (terrainObject.type == TerrainType.TerrainFeature) { tileInfo.baseTerrain = when { terrainObject.occursOn == null -> terrainObject.occursOn!!.first() @@ -179,9 +201,9 @@ class TileEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(Camera } private fun addTerrainOptions(terrainFeaturesTable: Table, baseTerrainTable: Table) { - for (terrain in ruleSet.terrains.values) { + for (terrain in ruleset.terrains.values) { val tileInfo = TileInfo() - tileInfo.ruleset = mapEditorScreen.ruleSet + tileInfo.ruleset = mapEditorScreen.ruleset if (terrain.type == TerrainType.TerrainFeature) { tileInfo.baseTerrain = when { terrain.occursOn != null -> terrain.occursOn.first() @@ -318,7 +340,7 @@ class TileEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(Camera else tileInfo.improvement = improvement.name if(improvement.name.startsWith("StartingLocation")) - for(tileGroup in mapEditorScreen.mapHolder.tileGroups){ + for(tileGroup in mapEditorScreen.mapHolder.tileGroups.values){ val tile = tileGroup.tileInfo if(tile.improvement==improvement.name && tile!=tileInfo) tile.improvement=null @@ -332,7 +354,6 @@ class TileEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(Camera } normalizeTile(tileInfo) - tileInfo.setTransients() } fun normalizeTile(tileInfo: TileInfo){ diff --git a/core/src/com/unciv/ui/utils/ZoomableScrollPane.kt b/core/src/com/unciv/ui/utils/ZoomableScrollPane.kt new file mode 100644 index 0000000000..972a8a3d48 --- /dev/null +++ b/core/src/com/unciv/ui/utils/ZoomableScrollPane.kt @@ -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) + } + }) + } +} \ No newline at end of file diff --git a/core/src/com/unciv/ui/worldscreen/Minimap.kt b/core/src/com/unciv/ui/worldscreen/Minimap.kt index 7d43d5d13b..2770adcb0b 100644 --- a/core/src/com/unciv/ui/worldscreen/Minimap.kt +++ b/core/src/com/unciv/ui/worldscreen/Minimap.kt @@ -16,14 +16,14 @@ import com.unciv.ui.utils.ImageGetter import com.unciv.ui.utils.onClick import com.unciv.ui.utils.surroundWithCircle -class Minimap(val tileMapHolder: TileMapHolder) : ScrollPane(null){ +class Minimap(val mapHolder: WorldMapHolder) : ScrollPane(null){ val allTiles = Group() val tileImages = HashMap() - fun setScrollToTileMapHolder(){ - scrollPercentX = tileMapHolder.scrollPercentX - scrollPercentY = tileMapHolder.scrollPercentY + fun setScrollTomapHolder(){ + scrollPercentX = mapHolder.scrollPercentX + scrollPercentY = mapHolder.scrollPercentY } init{ @@ -32,7 +32,7 @@ class Minimap(val tileMapHolder: TileMapHolder) : ScrollPane(null){ var bottomX = 0f var bottomY = 0f - for (tileInfo in tileMapHolder.tileMap.values) { + for (tileInfo in mapHolder.tileMap.values) { val hex = ImageGetter.getImage("OtherIcons/Hexagon") val positionalVector = HexMath().hex2WorldCoords(tileInfo.position) @@ -41,8 +41,8 @@ class Minimap(val tileMapHolder: TileMapHolder) : ScrollPane(null){ hex.setPosition(positionalVector.x * 0.5f * groupSize, positionalVector.y * 0.5f * groupSize) hex.onClick { - tileMapHolder.setCenterPosition(tileInfo.position) - setScrollToTileMapHolder() + mapHolder.setCenterPosition(tileInfo.position) + setScrollTomapHolder() } allTiles.addActor(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 allTiles.setSize(10 + topX - bottomX, 10 + topY - bottomY) - widget = allTiles + actor = allTiles layout() updateVisualScroll() - tileMapHolder.addListener(object : InputListener(){ + mapHolder.addListener(object : InputListener(){ override fun handle(e: Event?): Boolean { - setScrollToTileMapHolder() + setScrollTomapHolder() return true } }) } fun update(cloneCivilization: CivilizationInfo) { - for(tileInfo in tileMapHolder.tileMap.values) { + for(tileInfo in mapHolder.tileMap.values) { val hex = tileImages[tileInfo]!! if (!(UncivGame.Current.viewEntireMapForDebug || cloneCivilization.exploredTiles.contains(tileInfo.position))) hex.color = Color.DARK_GRAY @@ -86,9 +86,9 @@ class Minimap(val tileMapHolder: TileMapHolder) : ScrollPane(null){ } } -class MinimapHolder(tileMapHolder: TileMapHolder): Table(){ - val minimap = Minimap(tileMapHolder) - val worldScreen = tileMapHolder.worldScreen +class MinimapHolder(mapHolder: WorldMapHolder): Table(){ + val minimap = Minimap(mapHolder) + val worldScreen = mapHolder.worldScreen init{ add(getToggleIcons()).align(Align.bottom) diff --git a/core/src/com/unciv/ui/worldscreen/TileMapHolder.kt b/core/src/com/unciv/ui/worldscreen/WorldMapHolder.kt similarity index 87% rename from core/src/com/unciv/ui/worldscreen/TileMapHolder.kt rename to core/src/com/unciv/ui/worldscreen/WorldMapHolder.kt index 08909cf690..bc57219d87 100644 --- a/core/src/com/unciv/ui/worldscreen/TileMapHolder.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldMapHolder.kt @@ -6,9 +6,7 @@ import com.badlogic.gdx.math.Interpolation import com.badlogic.gdx.math.Vector2 import com.badlogic.gdx.scenes.scene2d.* 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.utils.ActorGestureListener import com.unciv.Constants import com.unciv.UncivGame 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.ui.tilegroups.TileSetStrings import com.unciv.ui.tilegroups.WorldTileGroup +import com.unciv.ui.map.TileGroupMap import com.unciv.ui.utils.* import com.unciv.ui.worldscreen.unit.UnitContextMenu import kotlin.concurrent.thread -import kotlin.math.sqrt - -class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap: TileMap) : ScrollPane(null) { +class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap: TileMap): ZoomableScrollPane() { internal var selectedTile: TileInfo? = null val tileGroups = HashMap() 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 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) 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! } - 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) { unitActionOverlay?.remove() selectedTile = tileInfo @@ -357,5 +310,4 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap: worldScreen.shouldUpdate=true } - } \ No newline at end of file diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index cbff4030be..9dc7484b2a 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -48,8 +48,8 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() { var isPlayersTurn = viewingCiv == gameInfo.currentPlayerCiv // todo this should be updated when passing turns var waitingForAutosave = false - val tileMapHolder: TileMapHolder = TileMapHolder(this, gameInfo.tileMap) - val minimapWrapper = MinimapHolder(tileMapHolder) + val mapHolder = WorldMapHolder(this, gameInfo.tileMap) + val minimapWrapper = MinimapHolder(mapHolder) private val topBar = WorldScreenTopBar(this) val bottomUnitTable = UnitTable(this) @@ -76,7 +76,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() { minimapWrapper.x = stage.width - minimapWrapper.width - tileMapHolder.addTiles() + mapHolder.addTiles() techButtonHolder.touchable=Touchable.enabled techButtonHolder.onClick(UncivSound.Paper) { @@ -92,7 +92,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() { techPolicyAndVictoryHolder.add(policyScreenButton).pad(10f) } - stage.addActor(tileMapHolder) + stage.addActor(mapHolder) stage.addActor(minimapWrapper) stage.addActor(topBar) stage.addActor(nextTurnButton) @@ -119,7 +119,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() { viewingCiv.getCivUnits().isNotEmpty() -> viewingCiv.getCivUnits().first().getTile().position else -> Vector2.Zero } - tileMapHolder.setCenterPosition(tileToCenterOn,true) + mapHolder.setCenterPosition(tileToCenterOn,true) if(gameInfo.gameParameters.isOnlineMultiplayer && !gameInfo.isUpToDate) @@ -173,7 +173,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() { displayTutorialsOnUpdate() bottomUnitTable.update() - bottomTileInfoTable.updateTileTable(tileMapHolder.selectedTile!!) + bottomTileInfoTable.updateTileTable(mapHolder.selectedTile!!) bottomTileInfoTable.x = stage.width - bottomTileInfoTable.width bottomTileInfoTable.y = if (UncivGame.Current.settings.showMinimap) minimapWrapper.height else 0f battleTable.update() @@ -198,7 +198,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() { // 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 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) @@ -344,7 +344,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() { if (viewingCiv.shouldGoToDueUnit()) { val nextDueUnit = viewingCiv.getNextDueUnit() if(nextDueUnit!=null) { - tileMapHolder.setCenterPosition(nextDueUnit.currentTile.position, false, false) + mapHolder.setCenterPosition(nextDueUnit.currentTile.position, false, false) bottomUnitTable.selectedUnit = nextDueUnit shouldUpdate=true } @@ -413,11 +413,11 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() { fun createNewWorldScreen(){ val newWorldScreen = WorldScreen(gameInfoClone.getPlayerToViewAs()) - newWorldScreen.tileMapHolder.scrollX = tileMapHolder.scrollX - newWorldScreen.tileMapHolder.scrollY = tileMapHolder.scrollY - newWorldScreen.tileMapHolder.scaleX = tileMapHolder.scaleX - newWorldScreen.tileMapHolder.scaleY = tileMapHolder.scaleY - newWorldScreen.tileMapHolder.updateVisualScroll() + newWorldScreen.mapHolder.scrollX = mapHolder.scrollX + newWorldScreen.mapHolder.scrollY = mapHolder.scrollY + newWorldScreen.mapHolder.scaleX = mapHolder.scaleX + newWorldScreen.mapHolder.scaleY = mapHolder.scaleY + newWorldScreen.mapHolder.updateVisualScroll() game.worldScreen = newWorldScreen game.setWorldScreen() } diff --git a/core/src/com/unciv/ui/worldscreen/bottombar/BattleTable.kt b/core/src/com/unciv/ui/worldscreen/bottombar/BattleTable.kt index 90a9e9d038..27df8b61d8 100644 --- a/core/src/com/unciv/ui/worldscreen/bottombar/BattleTable.kt +++ b/core/src/com/unciv/ui/worldscreen/bottombar/BattleTable.kt @@ -61,8 +61,8 @@ class BattleTable(val worldScreen: WorldScreen): Table() { private fun tryGetDefender(): ICombatant? { val attackerCiv = worldScreen.viewingCiv - if (worldScreen.tileMapHolder.selectedTile == null) return null // no selected tile - val selectedTile = worldScreen.tileMapHolder.selectedTile!! + if (worldScreen.mapHolder.selectedTile == null) return null // no selected tile + val selectedTile = worldScreen.mapHolder.selectedTile!! val defender: ICombatant? = Battle(worldScreen.gameInfo).getMapCombatantOfTile(selectedTile) @@ -197,7 +197,7 @@ class BattleTable(val worldScreen: WorldScreen): Table() { attackButton.onClick { try { 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 } catch (ex:Exception){ diff --git a/core/src/com/unciv/ui/worldscreen/unit/IdleUnitButton.kt b/core/src/com/unciv/ui/worldscreen/unit/IdleUnitButton.kt index 7191a5df49..92145480ac 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/IdleUnitButton.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/IdleUnitButton.kt @@ -7,11 +7,11 @@ import com.badlogic.gdx.utils.Align import com.unciv.logic.map.MapUnit import com.unciv.ui.utils.ImageGetter import com.unciv.ui.utils.onClick -import com.unciv.ui.worldscreen.TileMapHolder +import com.unciv.ui.worldscreen.WorldMapHolder class IdleUnitButton ( internal val unitTable: UnitTable, - val tileMapHolder: TileMapHolder, + val tileMapHolder: WorldMapHolder, val previous:Boolean ) : Table() { diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitContextMenu.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitContextMenu.kt index 42c0aa8f02..38430bda02 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitContextMenu.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitContextMenu.kt @@ -13,10 +13,10 @@ import com.unciv.ui.utils.CameraStageBaseScreen import com.unciv.ui.utils.ImageGetter import com.unciv.ui.utils.Sounds import com.unciv.ui.utils.onClick -import com.unciv.ui.worldscreen.TileMapHolder +import com.unciv.ui.worldscreen.WorldMapHolder 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 { diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitTable.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitTable.kt index accbd9eb23..961e4f50dc 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitTable.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitTable.kt @@ -16,8 +16,8 @@ import com.unciv.ui.utils.* import com.unciv.ui.worldscreen.WorldScreen class UnitTable(val worldScreen: WorldScreen) : Table(){ - private val prevIdleUnitButton = IdleUnitButton(this,worldScreen.tileMapHolder,true) - private val nextIdleUnitButton = IdleUnitButton(this,worldScreen.tileMapHolder,false) + private val prevIdleUnitButton = IdleUnitButton(this,worldScreen.mapHolder,true) + private val nextIdleUnitButton = IdleUnitButton(this,worldScreen.mapHolder,false) private val unitIconHolder=Table() private val unitNameLabel = "".toLabel() private val promotionsTable = Table() @@ -64,7 +64,7 @@ class UnitTable(val worldScreen: WorldScreen) : Table(){ touchable = Touchable.enabled onClick { selectedUnit?.currentTile?.position?.let { - worldScreen.tileMapHolder.setCenterPosition(it, false, false) + worldScreen.mapHolder.setCenterPosition(it, false, false) } } }).expand()