diff --git a/core/src/com/unciv/logic/map/tile/Tile.kt b/core/src/com/unciv/logic/map/tile/Tile.kt index 3584b592b5..9196fdb4d3 100644 --- a/core/src/com/unciv/logic/map/tile/Tile.kt +++ b/core/src/com/unciv/logic/map/tile/Tile.kt @@ -690,6 +690,8 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable { return if (probability == 0.0) 0.04 // This is the default of 1 per 25 tiles else probability } + + fun isTilemapInitialized() = ::tileMap.isInitialized //endregion //region state-changing functions @@ -712,7 +714,7 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable { isOcean = baseTerrain == Constants.ocean // Resource amounts missing - Old save or bad mapgen? - if (::tileMap.isInitialized && resource != null && tileResource.resourceType == ResourceType.Strategic && resourceAmount == 0) { + if (isTilemapInitialized() && resource != null && tileResource.resourceType == ResourceType.Strategic && resourceAmount == 0) { // Let's assume it's a small deposit setTileResource(tileResource, majorDeposit = false) } @@ -812,7 +814,7 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable { } private fun updateUniqueMap() { - if (!::tileMap.isInitialized) return // This tile is a fake tile, for visual display only (e.g. map editor, civilopedia) + if (!isTilemapInitialized()) return // This tile is a fake tile, for visual display only (e.g. map editor, civilopedia) val terrainNameList = allTerrains.map { it.name }.toList() // List hash is function of all its items, so the same items in the same order will always give the same hash diff --git a/core/src/com/unciv/ui/components/tilegroups/TileSetStrings.kt b/core/src/com/unciv/ui/components/tilegroups/TileSetStrings.kt index d40a6deb9c..f6a2b776ba 100644 --- a/core/src/com/unciv/ui/components/tilegroups/TileSetStrings.kt +++ b/core/src/com/unciv/ui/components/tilegroups/TileSetStrings.kt @@ -8,6 +8,8 @@ import com.unciv.models.metadata.GameSettings import com.unciv.models.ruleset.Ruleset import com.unciv.models.tilesets.TileSetCache import com.unciv.models.tilesets.TileSetConfig +import com.unciv.ui.components.tilegroups.layers.EdgeTileImage +import com.unciv.ui.components.tilegroups.layers.NeighborDirection import com.unciv.ui.images.ImageAttempter import com.unciv.ui.images.ImageGetter @@ -66,6 +68,26 @@ class TileSetStrings( val bottomRightRiver by lazy { orFallback { tilesLocation + "River-BottomRight"} } val bottomRiver by lazy { orFallback { tilesLocation + "River-Bottom"} } val bottomLeftRiver by lazy { orFallback { tilesLocation + "River-BottomLeft"} } + + val edgeImagesByPosition = ImageGetter.getAllImageNames() + .filter { it.startsWith(tileSetLocation +"Edges/") } + .mapNotNull { + val split = it.split('/').last() // without folder + .split("-") + // Comprised of 3 parts: origin tilefilter, destination tilefilter, + // and edge type: Bottom, BottomLeft or BottomRight + if (split.size != 4) return@mapNotNull null + + // split[0] is name and is unused + val originTileFilter = split[1] + val destinationTileFilter = split[2] + val neighborDirection = split[3] + val neighborDirectionEnumValue = NeighborDirection.entries + .firstOrNull { it.name == neighborDirection } ?: return@mapNotNull null + + EdgeTileImage(it, originTileFilter, destinationTileFilter, neighborDirectionEnumValue) + } + .groupBy { it.edgeType } val unitsLocation = unitSetLocation + "Units/" diff --git a/core/src/com/unciv/ui/components/tilegroups/layers/TileLayerTerrain.kt b/core/src/com/unciv/ui/components/tilegroups/layers/TileLayerTerrain.kt index 272ce74fa8..b6feab8c45 100644 --- a/core/src/com/unciv/ui/components/tilegroups/layers/TileLayerTerrain.kt +++ b/core/src/com/unciv/ui/components/tilegroups/layers/TileLayerTerrain.kt @@ -1,6 +1,7 @@ package com.unciv.ui.components.tilegroups.layers import com.badlogic.gdx.graphics.Color +import com.badlogic.gdx.math.Vector2 import com.badlogic.gdx.scenes.scene2d.Actor import com.badlogic.gdx.scenes.scene2d.ui.Image import com.unciv.UncivGame @@ -85,10 +86,32 @@ class TileLayerTerrain(tileGroup: TileGroup, size: Float) : TileLayer(tileGroup, else -> baseHexagon + getTerrainImageLocations(terrainImages) + getImprovementAndResourceImages(resourceAndImprovementSequence) } } + + private fun getEdgeTileLocations(): Sequence { + val tile = tile() + if (!tile.isTilemapInitialized()) // fake tile + return emptySequence() + return tile.neighbors + .filter { it.position.x < tile.position.x || it.position.y < tile.position.y } + .flatMap { getMatchingEdges(tile, it) } + } + + private fun getMatchingEdges(originTile: Tile, neighborTile: Tile): List{ + val vectorToNeighbor = neighborTile.position.cpy().sub(originTile.position) + val direction = NeighborDirection.fromVector(vectorToNeighbor) ?: return emptyList() + val possibleEdgeFiles = strings().edgeImagesByPosition[direction] ?: return emptyList() + + return possibleEdgeFiles.filter { + if (!originTile.matchesFilter(it.originTileFilter)) return@filter false + if (!neighborTile.matchesFilter(it.destinationTileFilter)) return@filter false + return@filter true + }.map { it.fileName } + } private fun updateTileImage(viewingCiv: Civilization?) { - val tileBaseImageLocations = getTileBaseImageLocations(viewingCiv) - + val tileBaseImageLocations = getTileBaseImageLocations(viewingCiv) + + getEdgeTileLocations() + if (tileBaseImageLocations.size == tileImageIdentifiers.size) { if (tileBaseImageLocations.withIndex().all { (i, imageLocation) -> tileImageIdentifiers[i] == imageLocation }) return // All image identifiers are the same as the current ones, no need to change anything @@ -211,3 +234,21 @@ class TileLayerTerrain(tileGroup: TileGroup, size: Float) : TileLayer(tileGroup, else baseHexagon + strings().orFallback{ getTile(tileGroup.tile.naturalWonder!!) } } + +enum class NeighborDirection { + Top, TopRight, TopLeft, Bottom, BottomLeft, BottomRight; + + companion object { + fun fromVector(vector2: Vector2): NeighborDirection? = when { + vector2.x == 1f && vector2.y == 1f -> Top + vector2.x == 0f && vector2.y == 1f -> TopRight + vector2.x == 1f && vector2.y == 0f -> TopLeft + vector2.x == -1f && vector2.y == -1f -> Bottom + vector2.x == 0f && vector2.y == -1f -> BottomLeft + vector2.x == -1f && vector2.y == 0f -> BottomRight + else -> null + } + } +} + +data class EdgeTileImage(val fileName:String, val originTileFilter: String, val destinationTileFilter: String, val edgeType: NeighborDirection) \ No newline at end of file diff --git a/core/src/com/unciv/ui/images/ImageGetter.kt b/core/src/com/unciv/ui/images/ImageGetter.kt index dbd9945b4c..13c0fcbade 100644 --- a/core/src/com/unciv/ui/images/ImageGetter.kt +++ b/core/src/com/unciv/ui/images/ImageGetter.kt @@ -467,6 +467,8 @@ object ImageGetter { specialist.color = color return specialist } + + fun getAllImageNames() = textureRegionDrawables.keys fun getAvailableSkins() = ninePatchDrawables.keys.asSequence().map { it.split("/")[1] }.distinct() diff --git a/docs/Modders/Creating-a-custom-tileset.md b/docs/Modders/Creating-a-custom-tileset.md index e4f91d5867..41c1f046e9 100644 --- a/docs/Modders/Creating-a-custom-tileset.md +++ b/docs/Modders/Creating-a-custom-tileset.md @@ -146,3 +146,22 @@ They can be for unit types (Archery, Seige, Cavalry) or for specific unit names The files should be in the format of `-attack-`. For example, a 3 frame animation for Sword units would have the files `Sword-attack-1.png`, `Sword-attack-3.png`, `Sword-attack-3.png` + +## Edge images + +You can add additional images that will be drawn only when a tile is adjacent to another tile in a specific direction. + +The images should be placed in the `Images/Tilesets//Edges` folder, rather than in `/Tiles`. + +The name of the tile should be `---.png`, where direction of one of: + +- Bottom +- BottomLeft +- BottomRight +- Top +- TopLeft +- TopRight + +For example: `Cliff-Hills-Coast-Top.png` + +The initial name has no bearing on the image used, it is just a way to group images together.