mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-24 20:31:51 -04:00
Some improvements to Map Editor - step 1 (#6622)
This commit is contained in:
parent
4ad9d69424
commit
996c58e1fb
@ -271,7 +271,6 @@ Military near City-State =
|
||||
Sum: =
|
||||
|
||||
|
||||
|
||||
# Trades
|
||||
|
||||
Trade =
|
||||
@ -488,6 +487,7 @@ Continent: [param] ([amount] tiles) =
|
||||
Change map to fit selected ruleset? =
|
||||
Area: [amount] tiles, [amount2] continents/islands =
|
||||
Do you want to leave without saving the recent changes? =
|
||||
Do you want to load another map without saving the recent changes? =
|
||||
Invalid map: Area ([area]) does not match saved dimensions ([dimensions]). =
|
||||
The dimensions have now been fixed for you. =
|
||||
River generation failed! =
|
||||
@ -503,6 +503,7 @@ Sprout vegetation =
|
||||
Spawn rare features =
|
||||
Distribute ice =
|
||||
Assign continent IDs =
|
||||
Place Natural Wonders =
|
||||
Let the rivers flow =
|
||||
Spread Resources =
|
||||
Create ancient ruins =
|
||||
|
@ -75,14 +75,10 @@ class MainMenuScreen: BaseScreen() {
|
||||
.generateMap(MapParameters().apply { mapSize = MapSizeNew(MapSize.Small); type = MapType.default })
|
||||
postCrashHandlingRunnable { // for GL context
|
||||
ImageGetter.setNewRuleset(RulesetCache.getVanillaRuleset())
|
||||
val mapHolder = EditorMapHolder(MapEditorScreen(), newMap) {}
|
||||
val mapHolder = EditorMapHolder(this, newMap) {}
|
||||
backgroundTable.addAction(Actions.sequence(
|
||||
Actions.fadeOut(0f),
|
||||
Actions.run {
|
||||
mapHolder.apply {
|
||||
addTiles(this@MainMenuScreen.stage)
|
||||
touchable = Touchable.disabled
|
||||
}
|
||||
backgroundTable.addActor(mapHolder)
|
||||
mapHolder.center(backgroundTable)
|
||||
},
|
||||
|
@ -9,7 +9,7 @@ object MapSaver {
|
||||
|
||||
fun json() = GameSaver.json()
|
||||
|
||||
private const val mapsFolder = "maps"
|
||||
const val mapsFolder = "maps"
|
||||
var saveZipped = true
|
||||
|
||||
private fun getMap(mapName:String) = Gdx.files.local("$mapsFolder/$mapName")
|
||||
|
@ -10,9 +10,13 @@ import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
|
||||
class MapLandmassGenerator(val ruleset: Ruleset, val randomness: MapGenerationRandomness) {
|
||||
|
||||
//region _Fields
|
||||
private val firstLandTerrain = ruleset.terrains.values.first { it.type==TerrainType.Land }
|
||||
private val landTerrainName = firstLandTerrain.name
|
||||
private val firstWaterTerrain = ruleset.terrains.values.firstOrNull { it.type==TerrainType.Water }
|
||||
private val waterTerrainName = firstWaterTerrain?.name ?: ""
|
||||
private var waterThreshold = 0.0
|
||||
//endregion
|
||||
|
||||
fun generateLand(tileMap: TileMap) {
|
||||
// This is to accommodate land-only mods
|
||||
@ -22,6 +26,8 @@ class MapLandmassGenerator(val ruleset: Ruleset, val randomness: MapGenerationRa
|
||||
return
|
||||
}
|
||||
|
||||
waterThreshold = tileMap.mapParameters.waterThreshold.toDouble()
|
||||
|
||||
when (tileMap.mapParameters.type) {
|
||||
MapType.pangaea -> createPangaea(tileMap)
|
||||
MapType.innerSea -> createInnerSea(tileMap)
|
||||
@ -33,11 +39,8 @@ class MapLandmassGenerator(val ruleset: Ruleset, val randomness: MapGenerationRa
|
||||
}
|
||||
}
|
||||
|
||||
private fun spawnLandOrWater(tile: TileInfo, elevation: Double, threshold: Double) {
|
||||
when {
|
||||
elevation < threshold -> tile.baseTerrain = firstWaterTerrain!!.name
|
||||
else -> tile.baseTerrain = firstLandTerrain.name
|
||||
}
|
||||
private fun spawnLandOrWater(tile: TileInfo, elevation: Double) {
|
||||
tile.baseTerrain = if (elevation < waterThreshold) waterTerrainName else landTerrainName
|
||||
}
|
||||
|
||||
/**
|
||||
@ -62,15 +65,16 @@ class MapLandmassGenerator(val ruleset: Ruleset, val randomness: MapGenerationRa
|
||||
val elevationSeed = randomness.RNG.nextInt().toDouble()
|
||||
for (tile in tileMap.values) {
|
||||
val elevation = randomness.getPerlinNoise(tile, elevationSeed)
|
||||
spawnLandOrWater(tile, elevation, tileMap.mapParameters.waterThreshold.toDouble())
|
||||
spawnLandOrWater(tile, elevation)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createArchipelago(tileMap: TileMap) {
|
||||
val elevationSeed = randomness.RNG.nextInt().toDouble()
|
||||
waterThreshold += 0.25
|
||||
for (tile in tileMap.values) {
|
||||
val elevation = getRidgedPerlinNoise(tile, elevationSeed)
|
||||
spawnLandOrWater(tile, elevation, 0.25 + tileMap.mapParameters.waterThreshold.toDouble())
|
||||
spawnLandOrWater(tile, elevation)
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,7 +83,7 @@ class MapLandmassGenerator(val ruleset: Ruleset, val randomness: MapGenerationRa
|
||||
for (tile in tileMap.values) {
|
||||
var elevation = randomness.getPerlinNoise(tile, elevationSeed)
|
||||
elevation = (elevation + getEllipticContinent(tile, tileMap)) / 2.0
|
||||
spawnLandOrWater(tile, elevation, tileMap.mapParameters.waterThreshold.toDouble())
|
||||
spawnLandOrWater(tile, elevation)
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,7 +92,7 @@ class MapLandmassGenerator(val ruleset: Ruleset, val randomness: MapGenerationRa
|
||||
for (tile in tileMap.values) {
|
||||
var elevation = randomness.getPerlinNoise(tile, elevationSeed)
|
||||
elevation -= getEllipticContinent(tile, tileMap, 0.6) * 0.3
|
||||
spawnLandOrWater(tile, elevation, tileMap.mapParameters.waterThreshold.toDouble())
|
||||
spawnLandOrWater(tile, elevation)
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,7 +101,7 @@ class MapLandmassGenerator(val ruleset: Ruleset, val randomness: MapGenerationRa
|
||||
for (tile in tileMap.values) {
|
||||
var elevation = randomness.getPerlinNoise(tile, elevationSeed)
|
||||
elevation = (elevation + getTwoContinentsTransform(tile, tileMap)) / 2.0
|
||||
spawnLandOrWater(tile, elevation, tileMap.mapParameters.waterThreshold.toDouble())
|
||||
spawnLandOrWater(tile, elevation)
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,7 +110,7 @@ class MapLandmassGenerator(val ruleset: Ruleset, val randomness: MapGenerationRa
|
||||
for (tile in tileMap.values) {
|
||||
var elevation = randomness.getPerlinNoise(tile, elevationSeed)
|
||||
elevation = (elevation + getFourCornersTransform(tile, tileMap)) / 2.0
|
||||
spawnLandOrWater(tile, elevation, tileMap.mapParameters.waterThreshold.toDouble())
|
||||
spawnLandOrWater(tile, elevation)
|
||||
}
|
||||
}
|
||||
|
||||
@ -184,7 +188,6 @@ class MapLandmassGenerator(val ruleset: Ruleset, val randomness: MapGenerationRa
|
||||
return Perlin.ridgedNoise3d(worldCoords.x.toDouble(), worldCoords.y.toDouble(), seed, nOctaves, persistence, lacunarity, scale)
|
||||
}
|
||||
|
||||
// region Cellular automata
|
||||
private fun generateLandCellularAutomata(tileMap: TileMap) {
|
||||
|
||||
for (tile in tileMap.values) {
|
||||
@ -196,5 +199,4 @@ class MapLandmassGenerator(val ruleset: Ruleset, val randomness: MapGenerationRa
|
||||
|
||||
smoothen(tileMap)
|
||||
}
|
||||
// endregion
|
||||
}
|
@ -316,16 +316,7 @@ class FormattedLine (
|
||||
val image = category.getImage?.invoke(parts[1], iconSize) ?: return 0
|
||||
|
||||
if (iconCrossed) {
|
||||
val cross = ImageGetter.getRedCross(iconSize * 0.7f, 0.7f)
|
||||
val group = Group().apply {
|
||||
isTransform = false
|
||||
setSize(iconSize, iconSize)
|
||||
image.center(this)
|
||||
addActor(image)
|
||||
cross.center(this)
|
||||
addActor(cross)
|
||||
}
|
||||
table.add(group).size(iconSize).padRight(iconPad)
|
||||
table.add(ImageGetter.getCrossedImage(image, iconSize)).size(iconSize).padRight(iconPad)
|
||||
} else {
|
||||
table.add(image).size(iconSize).padRight(iconPad)
|
||||
}
|
||||
|
@ -344,6 +344,16 @@ object ImageGetter {
|
||||
return redCross
|
||||
}
|
||||
|
||||
fun getCrossedImage(image: Actor, iconSize: Float) = Group().apply {
|
||||
isTransform = false
|
||||
setSize(iconSize, iconSize)
|
||||
image.center(this)
|
||||
addActor(image)
|
||||
val cross = getRedCross(iconSize * 0.7f, 0.7f)
|
||||
cross.center(this)
|
||||
addActor(cross)
|
||||
}
|
||||
|
||||
fun getArrowImage(align:Int = Align.right): Image {
|
||||
val image = getImage("OtherIcons/ArrowRight")
|
||||
image.setOrigin(Align.center)
|
||||
|
@ -1,7 +1,9 @@
|
||||
package com.unciv.ui.mapeditor
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.badlogic.gdx.scenes.scene2d.Stage
|
||||
import com.badlogic.gdx.scenes.scene2d.*
|
||||
import com.badlogic.gdx.scenes.scene2d.actions.Actions
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.HexMath
|
||||
import com.unciv.logic.map.TileInfo
|
||||
@ -11,11 +13,18 @@ import com.unciv.ui.tilegroups.TileGroup
|
||||
import com.unciv.ui.tilegroups.TileSetStrings
|
||||
import com.unciv.ui.utils.*
|
||||
|
||||
|
||||
/**
|
||||
* This MapHolder is used both for the Map Editor and the Main Menu background!
|
||||
* @param parentScreen a MapEditorScreen or a MainMenuScreen
|
||||
*/
|
||||
class EditorMapHolder(
|
||||
parentScreen: BaseScreen,
|
||||
internal val tileMap: TileMap,
|
||||
private val onTileClick: (TileInfo) -> Unit
|
||||
): ZoomableScrollPane() {
|
||||
val editorScreen = parentScreen as? MapEditorScreen
|
||||
|
||||
val tileGroups = HashMap<TileInfo, List<TileGroup>>()
|
||||
private lateinit var tileGroupMap: TileGroupMap<TileGroup>
|
||||
private val allTileGroups = ArrayList<TileGroup>()
|
||||
@ -23,9 +32,16 @@ class EditorMapHolder(
|
||||
private val maxWorldZoomOut = UncivGame.Current.settings.maxWorldZoomOut
|
||||
private val minZoomScale = 1f / maxWorldZoomOut
|
||||
|
||||
private var blinkAction: Action? = null
|
||||
|
||||
private var savedCaptureListeners = emptyList<EventListener>()
|
||||
private var savedListeners = emptyList<EventListener>()
|
||||
|
||||
init {
|
||||
if (editorScreen == null) touchable = Touchable.disabled
|
||||
continuousScrollingX = tileMap.mapParameters.worldWrap
|
||||
addTiles(parentScreen.stage)
|
||||
if (editorScreen != null) addCaptureListener(getDragPaintListener())
|
||||
}
|
||||
|
||||
internal fun addTiles(stage: Stage) {
|
||||
@ -72,7 +88,8 @@ class EditorMapHolder(
|
||||
*/
|
||||
tileGroup.showEntireMap = true
|
||||
tileGroup.update()
|
||||
tileGroup.onClick { onTileClick(tileGroup.tileInfo) }
|
||||
if (touchable != Touchable.disabled)
|
||||
tileGroup.onClick { onTileClick(tileGroup.tileInfo) }
|
||||
}
|
||||
|
||||
setSize(stage.width * maxWorldZoomOut, stage.height * maxWorldZoomOut)
|
||||
@ -105,22 +122,100 @@ class EditorMapHolder(
|
||||
return null
|
||||
}
|
||||
|
||||
// Currently unused, drag painting will need it
|
||||
fun getClosestTileTo(stageCoords: Vector2): TileInfo? {
|
||||
val positionalCoords = tileGroupMap.getPositionalVector(stageCoords)
|
||||
val hexPosition = HexMath.world2HexCoords(positionalCoords)
|
||||
val rounded = HexMath.roundHexCoords(hexPosition)
|
||||
return tileMap.getOrNull(rounded)
|
||||
}
|
||||
|
||||
fun setCenterPosition(vector: Vector2) {
|
||||
fun setCenterPosition(vector: Vector2, blink: Boolean = false) {
|
||||
val tileGroup = allTileGroups.firstOrNull { it.tileInfo.position == vector } ?: return
|
||||
scrollX = tileGroup.x + tileGroup.width / 2 - width / 2
|
||||
scrollY = maxY - (tileGroup.y + tileGroup.width / 2 - height / 2)
|
||||
if (!blink) return
|
||||
|
||||
removeAction(blinkAction) // so we don't have multiple blinks at once
|
||||
blinkAction = Actions.repeat(3, Actions.sequence(
|
||||
Actions.run { tileGroup.highlightImage.isVisible = false },
|
||||
Actions.delay(.3f),
|
||||
Actions.run { tileGroup.highlightImage.isVisible = true },
|
||||
Actions.delay(.3f)
|
||||
))
|
||||
addAction(blinkAction) // Don't set it on the group because it's an actionless group
|
||||
}
|
||||
|
||||
override fun zoom(zoomScale: Float) {
|
||||
if (zoomScale < minZoomScale || zoomScale > 2f) return
|
||||
setScale(zoomScale)
|
||||
}
|
||||
|
||||
/*
|
||||
The ScrollPane interferes with the dragging listener of MapEditorToolsDrawer.
|
||||
Once the ZoomableScrollPane super is initialized, there are 3 listeners + 1 capture listener:
|
||||
listeners[0] = ZoomableScrollPane.getFlickScrollListener()
|
||||
listeners[1] = ZoomableScrollPane.addZoomListeners: override fun scrolled (MouseWheel)
|
||||
listeners[2] = ZoomableScrollPane.addZoomListeners: override fun zoom (Android pinch)
|
||||
captureListeners[0] = ScrollPane.addCaptureListener: touchDown, touchUp, touchDragged, mouseMoved
|
||||
Clearing and putting back the captureListener _should_ suffice, but in practice it doesn't.
|
||||
Therefore, save all listeners when they're hurting us, and put them back when needed.
|
||||
*/
|
||||
internal fun killListeners() {
|
||||
savedCaptureListeners = captureListeners.toList()
|
||||
savedListeners = listeners.toList()
|
||||
clearListeners()
|
||||
}
|
||||
internal fun resurrectListeners() {
|
||||
val captureListenersToAdd = savedCaptureListeners
|
||||
savedCaptureListeners = emptyList()
|
||||
val listenersToAdd = savedListeners
|
||||
savedListeners = emptyList()
|
||||
for (listener in listenersToAdd) addListener(listener)
|
||||
for (listener in captureListenersToAdd) addCaptureListener(listener)
|
||||
}
|
||||
|
||||
/** Factory to create the listener that does "paint by dragging"
|
||||
* Should only be called if this MapHolder is used from MapEditorScreen
|
||||
*/
|
||||
private fun getDragPaintListener(): InputListener {
|
||||
return object : InputListener() {
|
||||
var isDragging = false
|
||||
var isPainting = false
|
||||
var touchDownTime = System.currentTimeMillis()
|
||||
|
||||
override fun touchDown(event: InputEvent?, x: Float, y: Float, pointer: Int, button: Int): Boolean {
|
||||
touchDownTime = System.currentTimeMillis()
|
||||
return true
|
||||
}
|
||||
|
||||
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, this@EditorMapHolder)
|
||||
}
|
||||
}
|
||||
if (!isPainting) return
|
||||
|
||||
editorScreen!!.hideSelection()
|
||||
val stageCoords = actor.stageToLocalCoordinates(Vector2(event!!.stageX, event.stageY))
|
||||
val centerTileInfo = getClosestTileTo(stageCoords)
|
||||
?: return
|
||||
editorScreen.tabs.edit.paintTilesWithBrush(centerTileInfo)
|
||||
}
|
||||
|
||||
override fun touchUp(event: InputEvent?, x: Float, y: Float, pointer: Int, button: Int) {
|
||||
// Reset the whole map
|
||||
if (isPainting) {
|
||||
updateTileGroups()
|
||||
setTransients()
|
||||
}
|
||||
|
||||
isDragging = false
|
||||
isPainting = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getClosestTileTo(stageCoords: Vector2): TileInfo? {
|
||||
val positionalCoords = tileGroupMap.getPositionalVector(stageCoords)
|
||||
val hexPosition = HexMath.world2HexCoords(positionalCoords)
|
||||
val rounded = HexMath.roundHexCoords(hexPosition)
|
||||
return tileMap.getOrNull(rounded)
|
||||
}
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ class MapEditorEditFeaturesTab(
|
||||
val eraserIcon = "Terrain/${firstFeature.name}"
|
||||
val eraser = FormattedLine("Remove features", icon = eraserIcon, size = 32, iconCrossed = true)
|
||||
add(eraser.render(0f).apply { onClick {
|
||||
editTab.setBrush("Remove feature", eraserIcon) { tile ->
|
||||
editTab.setBrush("Remove features", eraserIcon, true) { tile ->
|
||||
tile.removeTerrainFeatures()
|
||||
}
|
||||
} }).padBottom(0f).row()
|
||||
@ -139,7 +139,7 @@ class MapEditorEditResourcesTab(
|
||||
val eraserIcon = "Resource/${firstResource.name}"
|
||||
val eraser = FormattedLine("Remove resource", icon = eraserIcon, size = 32, iconCrossed = true)
|
||||
add(eraser.render(0f).apply { onClick {
|
||||
editTab.setBrush("Remove resource", eraserIcon) { tile ->
|
||||
editTab.setBrush("Remove resource", eraserIcon, true) { tile ->
|
||||
tile.resource = null
|
||||
}
|
||||
} }).padBottom(0f).row()
|
||||
@ -186,7 +186,7 @@ class MapEditorEditImprovementsTab(
|
||||
val eraserIcon = "Improvement/${firstImprovement.name}"
|
||||
val eraser = FormattedLine("Remove improvement", icon = eraserIcon, size = 32, iconCrossed = true)
|
||||
add(eraser.render(0f).apply { onClick {
|
||||
editTab.setBrush("Remove improvement", eraserIcon) { tile ->
|
||||
editTab.setBrush("Remove improvement", eraserIcon, true) { tile ->
|
||||
tile.improvement = null
|
||||
tile.roadStatus = RoadStatus.None
|
||||
}
|
||||
@ -259,7 +259,7 @@ class MapEditorEditStartsTab(
|
||||
val eraserIcon = "Nation/${firstNation.name}"
|
||||
val eraser = FormattedLine("Remove starting locations", icon = eraserIcon, size = 24, iconCrossed = true)
|
||||
add(eraser.render(0f).apply { onClick {
|
||||
editTab.setBrush(BrushHandlerType.Direct, "Remove starting locations", eraserIcon) { tile ->
|
||||
editTab.setBrush(BrushHandlerType.Direct, "Remove starting locations", eraserIcon, true) { tile ->
|
||||
tile.tileMap.removeStartingLocations(tile.position)
|
||||
}
|
||||
} }).padBottom(0f).row()
|
||||
@ -406,16 +406,8 @@ class MapEditorEditRiversTab(
|
||||
}
|
||||
}
|
||||
}.makeTileGroup()
|
||||
private fun getRemoveRiverIcon() = Group().apply {
|
||||
isTransform = false
|
||||
setSize(iconSize, iconSize)
|
||||
val tileGroup = getTileGroupWithRivers(RiverEdge.All)
|
||||
tileGroup.center(this)
|
||||
addActor(tileGroup)
|
||||
val cross = ImageGetter.getRedCross(iconSize * 0.7f, 1f)
|
||||
cross.center(this)
|
||||
addActor(cross)
|
||||
}
|
||||
private fun getRemoveRiverIcon() =
|
||||
ImageGetter.getCrossedImage(getTileGroupWithRivers(RiverEdge.All), iconSize)
|
||||
private fun getRiverIcon(edge: RiverEdge) = Group().apply {
|
||||
// wrap same as getRemoveRiverIcon so the icons align the same (using getTileGroupWithRivers directly works but looks ugly - reason unknown to me)
|
||||
isTransform = false
|
||||
|
@ -107,21 +107,12 @@ class MapEditorEditTab(
|
||||
|
||||
private fun selectPage(index: Int) = subTabs.selectPage(index)
|
||||
|
||||
fun setBrush(
|
||||
name: String,
|
||||
icon: String,
|
||||
isRemove: Boolean = false,
|
||||
applyAction: (TileInfo)->Unit
|
||||
) {
|
||||
fun setBrush(name: String, icon: String, isRemove: Boolean = false, applyAction: (TileInfo)->Unit) {
|
||||
brushHandlerType = BrushHandlerType.Tile
|
||||
brushCell.setActor(FormattedLine(name, icon = icon, iconCrossed = isRemove).render(0f))
|
||||
brushAction = applyAction
|
||||
}
|
||||
private fun setBrush(
|
||||
name: String,
|
||||
icon: Actor,
|
||||
applyAction: (TileInfo)->Unit
|
||||
) {
|
||||
private fun setBrush(name: String, icon: Actor, applyAction: (TileInfo)->Unit) {
|
||||
brushHandlerType = BrushHandlerType.Tile
|
||||
val line = Table().apply {
|
||||
add(icon).padRight(10f)
|
||||
@ -130,22 +121,12 @@ class MapEditorEditTab(
|
||||
brushCell.setActor(line)
|
||||
brushAction = applyAction
|
||||
}
|
||||
fun setBrush(
|
||||
handlerType: BrushHandlerType,
|
||||
name: String,
|
||||
icon: String,
|
||||
isRemove: Boolean = false,
|
||||
applyAction: (TileInfo)->Unit
|
||||
) {
|
||||
fun setBrush(handlerType: BrushHandlerType, name: String, icon: String,
|
||||
isRemove: Boolean = false, applyAction: (TileInfo)->Unit) {
|
||||
setBrush(name, icon, isRemove, applyAction)
|
||||
brushHandlerType = handlerType
|
||||
}
|
||||
fun setBrush(
|
||||
handlerType: BrushHandlerType,
|
||||
name: String,
|
||||
icon: Actor,
|
||||
applyAction: (TileInfo)->Unit
|
||||
) {
|
||||
fun setBrush(handlerType: BrushHandlerType, name: String, icon: Actor, applyAction: (TileInfo)->Unit) {
|
||||
setBrush(name, icon, applyAction)
|
||||
brushHandlerType = handlerType
|
||||
}
|
||||
@ -236,7 +217,7 @@ class MapEditorEditTab(
|
||||
resultingTiles.forEach { editorScreen.updateAndHighlight(it, Color.SKY) }
|
||||
}
|
||||
|
||||
private fun paintTilesWithBrush(tile: TileInfo) {
|
||||
internal fun paintTilesWithBrush(tile: TileInfo) {
|
||||
val tiles =
|
||||
if (brushSize == -1) {
|
||||
val bfs = BFS(tile) { it.isSimilarEnough(tile) }
|
||||
|
@ -62,7 +62,7 @@ class MapEditorFilesTable(
|
||||
)
|
||||
if (includeMods) {
|
||||
for (modFolder in RulesetCache.values.mapNotNull { it.folderLocation }) {
|
||||
val mapsFolder = modFolder.child("maps")
|
||||
val mapsFolder = modFolder.child(MapSaver.mapsFolder)
|
||||
if (mapsFolder.exists())
|
||||
sortedFiles.addAll(
|
||||
mapsFolder.list()
|
||||
@ -92,4 +92,14 @@ class MapEditorFilesTable(
|
||||
}
|
||||
layout()
|
||||
}
|
||||
|
||||
fun noMapsAvailable(includeMods: Boolean = false): Boolean {
|
||||
if (MapSaver.getMaps().any()) return true
|
||||
if (!includeMods) return false
|
||||
for (modFolder in RulesetCache.values.mapNotNull { it.folderLocation }) {
|
||||
val mapsFolder = modFolder.child(MapSaver.mapsFolder)
|
||||
if (mapsFolder.exists() && mapsFolder.list().any()) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,11 @@ import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.ButtonGroup
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.CheckBox
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.unciv.logic.map.MapParameters
|
||||
import com.unciv.logic.map.MapType
|
||||
import com.unciv.logic.map.TileMap
|
||||
import com.unciv.logic.map.mapgenerator.MapGenerator
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
@ -48,6 +51,11 @@ class MapEditorGenerateTab(
|
||||
}
|
||||
|
||||
private fun generate(step: MapGeneratorSteps) {
|
||||
if (step <= MapGeneratorSteps.Landmass && step in seedUsedForStep) {
|
||||
// reseed visibly when starting from scratch (new seed shows in advanced settings widget)
|
||||
newTab.mapParametersTable.reseed()
|
||||
seedUsedForStep -= step
|
||||
}
|
||||
val mapParameters = editorScreen.newMapParameters.clone() // this clone is very important here
|
||||
val message = mapParameters.mapSize.fixUndesiredSizes(mapParameters.worldWrap)
|
||||
if (message != null) {
|
||||
@ -76,29 +84,53 @@ class MapEditorGenerateTab(
|
||||
Gdx.input.inputProcessor = null // remove input processing - nothing will be clicked!
|
||||
setButtonsEnabled(false)
|
||||
|
||||
thread(name = "MapGenerator") {
|
||||
fun freshMapCompleted(generatedMap: TileMap, mapParameters: MapParameters, newRuleset: Ruleset, selectPage: Int) {
|
||||
MapEditorScreen.saveDefaultParameters(mapParameters)
|
||||
editorScreen.loadMap(generatedMap, newRuleset, selectPage) // also reactivates inputProcessor
|
||||
editorScreen.isDirty = true
|
||||
setButtonsEnabled(true)
|
||||
}
|
||||
fun stepCompleted(step: MapGeneratorSteps) {
|
||||
if (step == MapGeneratorSteps.NaturalWonders) editorScreen.naturalWondersNeedRefresh = true
|
||||
editorScreen.mapHolder.updateTileGroups()
|
||||
editorScreen.isDirty = true
|
||||
setButtonsEnabled(true)
|
||||
Gdx.input.inputProcessor = editorScreen.stage
|
||||
}
|
||||
|
||||
// Map generation can take a while and we don't want ANRs
|
||||
thread(name = "MapGenerator", isDaemon = true) {
|
||||
try {
|
||||
// Map generation can take a while and we don't want ANRs
|
||||
if (step == MapGeneratorSteps.All) {
|
||||
val newRuleset = RulesetCache.getComplexRuleset(mapParameters.mods, mapParameters.baseRuleset)
|
||||
val generatedMap = MapGenerator(newRuleset).generateMap(mapParameters)
|
||||
|
||||
Gdx.app.postRunnable {
|
||||
MapEditorScreen.saveDefaultParameters(mapParameters)
|
||||
editorScreen.loadMap(generatedMap, newRuleset)
|
||||
editorScreen.isDirty = true
|
||||
setButtonsEnabled(true)
|
||||
Gdx.input.inputProcessor = editorScreen.stage
|
||||
val (newRuleset, generator) = if (step > MapGeneratorSteps.Landmass) null to null
|
||||
else {
|
||||
val newRuleset = RulesetCache.getComplexRuleset(mapParameters.mods, mapParameters.baseRuleset)
|
||||
newRuleset to MapGenerator(newRuleset)
|
||||
}
|
||||
} else {
|
||||
MapGenerator(editorScreen.ruleset).generateSingleStep(editorScreen.tileMap, step)
|
||||
|
||||
Gdx.app.postRunnable {
|
||||
if (step == MapGeneratorSteps.NaturalWonders) editorScreen.naturalWondersNeedRefresh = true
|
||||
editorScreen.mapHolder.updateTileGroups()
|
||||
editorScreen.isDirty = true
|
||||
setButtonsEnabled(true)
|
||||
Gdx.input.inputProcessor = editorScreen.stage
|
||||
when (step) {
|
||||
MapGeneratorSteps.All -> {
|
||||
val generatedMap = generator!!.generateMap(mapParameters)
|
||||
Gdx.app.postRunnable {
|
||||
freshMapCompleted(generatedMap, mapParameters, newRuleset!!, selectPage = 0)
|
||||
}
|
||||
}
|
||||
MapGeneratorSteps.Landmass -> {
|
||||
// This step _could_ run on an existing tileMap, but that opens a loophole where you get hills on water - fixing that is more expensive than always recreating
|
||||
mapParameters.type = MapType.empty
|
||||
val generatedMap = generator!!.generateMap(mapParameters)
|
||||
mapParameters.type = editorScreen.newMapParameters.type
|
||||
generator.generateSingleStep(generatedMap, step)
|
||||
val savedScale = editorScreen.mapHolder.scaleX
|
||||
Gdx.app.postRunnable {
|
||||
freshMapCompleted(generatedMap, mapParameters, newRuleset!!, selectPage = 1)
|
||||
editorScreen.mapHolder.zoom(savedScale)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
editorScreen.tileMap.mapParameters.seed = mapParameters.seed
|
||||
MapGenerator(editorScreen.ruleset).generateSingleStep(editorScreen.tileMap, step)
|
||||
Gdx.app.postRunnable {
|
||||
stepCompleted(step)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
|
@ -47,7 +47,9 @@ class MapEditorLoadTab(
|
||||
|
||||
private fun loadHandler() {
|
||||
if (chosenMap == null) return
|
||||
thread(name = "MapLoader", block = this::loaderThread)
|
||||
editorScreen.askIfDirty("Do you want to load another map without saving the recent changes?") {
|
||||
thread(name = "MapLoader", isDaemon = true, block = this::loaderThread)
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteHandler() {
|
||||
@ -119,7 +121,6 @@ class MapEditorLoadTab(
|
||||
editorScreen.loadMap(map)
|
||||
needPopup = false
|
||||
popup?.close()
|
||||
Gdx.input.inputProcessor = stage
|
||||
} catch (ex: Throwable) {
|
||||
needPopup = false
|
||||
popup?.close()
|
||||
@ -138,4 +139,6 @@ class MapEditorLoadTab(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun noMapsAvailable() = mapFiles.noMapsAvailable()
|
||||
}
|
@ -37,7 +37,7 @@ class MapEditorMainTabs(
|
||||
addPage("Load", load,
|
||||
ImageGetter.getImage("OtherIcons/Load"), 25f,
|
||||
shortcutKey = KeyCharAndCode.ctrl('l'),
|
||||
disabled = MapSaver.getMaps().isEmpty())
|
||||
disabled = load.noMapsAvailable())
|
||||
addPage("Save", save,
|
||||
ImageGetter.getImage("OtherIcons/Checkmark"), 25f,
|
||||
shortcutKey = KeyCharAndCode.ctrl('s'))
|
||||
|
@ -27,6 +27,8 @@ class MapEditorSaveTab(
|
||||
|
||||
private val saveButton = "Save map".toTextButton()
|
||||
private val deleteButton = "Delete map".toTextButton()
|
||||
private val quitButton = "Exit map editor".toTextButton()
|
||||
|
||||
private val mapNameTextField = TextField("", skin)
|
||||
|
||||
private var chosenMap: FileHandle? = null
|
||||
@ -48,6 +50,9 @@ class MapEditorSaveTab(
|
||||
|
||||
deleteButton.onClick(this::deleteHandler)
|
||||
buttonTable.add(deleteButton)
|
||||
|
||||
quitButton.onClick(editorScreen::closeEditor)
|
||||
buttonTable.add(quitButton)
|
||||
buttonTable.pack()
|
||||
|
||||
val fileTableHeight = editorScreen.stage.height - headerHeight - mapNameTextField.prefHeight - buttonTable.height - 22f
|
||||
|
@ -2,6 +2,7 @@ package com.unciv.ui.mapeditor
|
||||
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane
|
||||
import com.unciv.MainMenuScreen
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.HexMath
|
||||
@ -17,26 +18,28 @@ import com.unciv.ui.popup.YesNoPopup
|
||||
import com.unciv.ui.tilegroups.TileGroup
|
||||
import com.unciv.ui.utils.*
|
||||
|
||||
|
||||
//todo normalize properly
|
||||
|
||||
//todo drag painting - migrate from old editor
|
||||
//todo Nat Wonder step generator: *New* wonders?
|
||||
//todo functional Tab for Units
|
||||
//todo copy/paste tile areas? (As tool tab, brush sized, floodfill forbidden, tab displays copied area)
|
||||
//todo Synergy with Civilopedia for drawing loose tiles / terrain icons
|
||||
//todo left-align everything so a half-open drawer is more useful
|
||||
//todo combined brush
|
||||
//todo Load should check isDirty before discarding and replacing the current map
|
||||
//todo New function `convertTerrains` is auto-run after rivers the right decision for step-wise generation? Will paintRiverFromTo need the same? Will painting manually need the conversion?
|
||||
//todo work in Simon's changes to continent/landmass
|
||||
//todo work in Simon's regions - check whether generate and store or discard is the way
|
||||
//todo Regions: If relevant, view and possibly work in Simon's colored visualization
|
||||
//todo Strategic Resource abundance control
|
||||
//todo Tooltips for Edit items with info on placeability? Place this info as Brush description? In Expander?
|
||||
//todo Civilopedia links from edit items by right-click/long-tap?
|
||||
//todo Mod tab change base ruleset - disableAllCheckboxes - instead some intelligence to leave those mods on that stay compatible?
|
||||
//todo The setSkin call in newMapHolder belongs in ImageGetter.setNewRuleset and should be intelligent as resetFont is expensive and the probability a mod touched a few EmojiIcons is low
|
||||
|
||||
//todo new brush: remove natural wonder
|
||||
//todo "random nation" starting location (maybe no new internal representation = all major nations)
|
||||
//todo Nat Wonder step generator: Needs tweaks to avoid placing duplicates or wonders too close together
|
||||
//todo Music? Different suffix? Off? 20% Volume?
|
||||
//todo Unciv Europe Map Example - does not load due to "Gold / Gold ore": Solve problem that multiple errors are not shown nicely, and re-enable fixing the map and displaying it
|
||||
//todo See #6610 - re-layout after the map size dropdown changes to custom and new widgets are inserted - can reach "Create" only by dragging the _header_ of the sub-TabbedPager
|
||||
|
||||
class MapEditorScreen(map: TileMap? = null): BaseScreen() {
|
||||
/** The map being edited, with mod list for that map */
|
||||
@ -82,7 +85,7 @@ class MapEditorScreen(map: TileMap? = null): BaseScreen() {
|
||||
isDirty = false
|
||||
|
||||
tabs = MapEditorMainTabs(this)
|
||||
MapEditorToolsDrawer(tabs, stage)
|
||||
MapEditorToolsDrawer(tabs, stage, mapHolder)
|
||||
|
||||
// The top level pager assigns its own key bindings, but making nested TabbedPagers bind keys
|
||||
// so all levels select to show the tab in question is too complex. Sub-Tabs need to maintain
|
||||
@ -141,7 +144,7 @@ class MapEditorScreen(map: TileMap? = null): BaseScreen() {
|
||||
return result
|
||||
}
|
||||
|
||||
fun loadMap(map: TileMap, newRuleset: Ruleset? = null) {
|
||||
fun loadMap(map: TileMap, newRuleset: Ruleset? = null, selectPage: Int = 0) {
|
||||
mapHolder.remove()
|
||||
tileMap = map
|
||||
checkAndFixMapSize()
|
||||
@ -149,10 +152,8 @@ class MapEditorScreen(map: TileMap? = null): BaseScreen() {
|
||||
RulesetCache.getComplexRuleset(map.mapParameters.mods, map.mapParameters.baseRuleset)
|
||||
mapHolder = newMapHolder()
|
||||
isDirty = false
|
||||
Gdx.app.postRunnable {
|
||||
// Doing this directly freezes the game, despite loadMap already running under postRunnable
|
||||
tabs.selectPage(0)
|
||||
}
|
||||
Gdx.input.inputProcessor = stage
|
||||
tabs.selectPage(selectPage) // must be done _after_ resetting inputProcessor!
|
||||
}
|
||||
|
||||
fun getMapCloneForSave() =
|
||||
@ -171,10 +172,14 @@ class MapEditorScreen(map: TileMap? = null): BaseScreen() {
|
||||
}
|
||||
|
||||
internal fun closeEditor() {
|
||||
if (!isDirty) return game.setScreen(MainMenuScreen())
|
||||
YesNoPopup("Do you want to leave without saving the recent changes?", action = {
|
||||
askIfDirty("Do you want to leave without saving the recent changes?") {
|
||||
game.setScreen(MainMenuScreen())
|
||||
}, screen = this, restoreDefault = {
|
||||
}
|
||||
}
|
||||
|
||||
fun askIfDirty(question: String, action: ()->Unit) {
|
||||
if (!isDirty) return action()
|
||||
YesNoPopup(question, action, screen = this, restoreDefault = {
|
||||
keyPressDispatcher[KeyCharAndCode.BACK] = this::closeEditor
|
||||
}).open()
|
||||
}
|
||||
|
@ -1,24 +1,32 @@
|
||||
package com.unciv.ui.mapeditor
|
||||
|
||||
import com.badlogic.gdx.Application
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.InputEvent
|
||||
import com.badlogic.gdx.scenes.scene2d.InputListener
|
||||
import com.badlogic.gdx.scenes.scene2d.Stage
|
||||
import com.badlogic.gdx.scenes.scene2d.Touchable
|
||||
import com.badlogic.gdx.scenes.scene2d.actions.FloatAction
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Container
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.badlogic.gdx.utils.Align
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.utils.BaseScreen
|
||||
import com.unciv.ui.utils.addSeparatorVertical
|
||||
import kotlin.math.abs
|
||||
|
||||
class MapEditorToolsDrawer(
|
||||
tabs: MapEditorMainTabs,
|
||||
initStage: Stage
|
||||
initStage: Stage,
|
||||
private val mapHolder: EditorMapHolder
|
||||
): Table(BaseScreen.skin) {
|
||||
companion object {
|
||||
const val handleWidth = 10f
|
||||
private companion object {
|
||||
const val arrowImage = "OtherIcons/BackArrow"
|
||||
const val animationDuration = 0.333f
|
||||
const val clickEpsilon = 0.001f
|
||||
}
|
||||
private val handleWidth = if (Gdx.app.type == Application.ApplicationType.Desktop) 10f else 25f
|
||||
private val arrowSize = if (Gdx.app.type == Application.ApplicationType.Desktop) 10f else 20f //todo tweak on actual phone
|
||||
|
||||
var splitAmount = 1f
|
||||
set(value) {
|
||||
@ -26,22 +34,35 @@ class MapEditorToolsDrawer(
|
||||
reposition()
|
||||
}
|
||||
|
||||
private val arrowIcon = ImageGetter.getImage(arrowImage)
|
||||
|
||||
init {
|
||||
touchable = Touchable.childrenOnly
|
||||
addSeparatorVertical(Color.CLEAR, handleWidth) // the "handle"
|
||||
|
||||
arrowIcon.setSize(arrowSize, arrowSize)
|
||||
arrowIcon.setOrigin(Align.center)
|
||||
arrowIcon.rotation = 180f
|
||||
val arrowWrapper = Container(arrowIcon)
|
||||
arrowWrapper.align(Align.center)
|
||||
arrowWrapper.setSize(arrowSize, arrowSize)
|
||||
arrowWrapper.setOrigin(Align.center)
|
||||
add(arrowWrapper).align(Align.center).width(handleWidth).fillY().apply { // the "handle"
|
||||
background = ImageGetter.getBackground(BaseScreen.skin.get("color", Color::class.java))
|
||||
}
|
||||
|
||||
add(tabs)
|
||||
.height(initStage.height)
|
||||
.fill().top()
|
||||
pack()
|
||||
setPosition(initStage.width, 0f, Align.bottomRight)
|
||||
initStage.addActor(this)
|
||||
initStage.addListener(getListener(this))
|
||||
initStage.addCaptureListener(getListener(this))
|
||||
}
|
||||
|
||||
private class SplitAmountAction(
|
||||
private val drawer: MapEditorToolsDrawer,
|
||||
endAmount: Float
|
||||
): FloatAction(drawer.splitAmount, endAmount, 0.333f) {
|
||||
): FloatAction(drawer.splitAmount, endAmount, animationDuration) {
|
||||
override fun act(delta: Float): Boolean {
|
||||
val result = super.act(delta)
|
||||
drawer.splitAmount = value
|
||||
@ -59,6 +80,7 @@ class MapEditorToolsDrawer(
|
||||
if (draggingPointer != -1) return false
|
||||
if (pointer == 0 && button != 0) return false
|
||||
if (x !in drawer.x..(drawer.x + handleWidth)) return false
|
||||
mapHolder.killListeners()
|
||||
draggingPointer = pointer
|
||||
lastX = x
|
||||
handleX = drawer.x
|
||||
@ -67,6 +89,7 @@ class MapEditorToolsDrawer(
|
||||
}
|
||||
override fun touchUp(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int) {
|
||||
if (pointer != draggingPointer) return
|
||||
mapHolder.resurrectListeners()
|
||||
draggingPointer = -1
|
||||
if (oldSplitAmount < 0f) return
|
||||
addAction(SplitAmountAction(drawer, if (splitAmount > 0.5f) 0f else 1f))
|
||||
@ -74,17 +97,32 @@ class MapEditorToolsDrawer(
|
||||
override fun touchDragged(event: InputEvent, x: Float, y: Float, pointer: Int) {
|
||||
if (pointer != draggingPointer) return
|
||||
val delta = x - lastX
|
||||
val availWidth = stage.width - handleWidth
|
||||
handleX += delta
|
||||
lastX = x
|
||||
splitAmount = ((availWidth - handleX) / drawer.width).coerceIn(0f, 1f)
|
||||
if (oldSplitAmount >= 0f && abs(oldSplitAmount - splitAmount) >= 0.0001f) oldSplitAmount = -1f
|
||||
handleX += delta
|
||||
splitAmount = drawer.xToSplitAmount(handleX)
|
||||
if (oldSplitAmount >= 0f && abs(oldSplitAmount - splitAmount) >= clickEpsilon ) oldSplitAmount = -1f
|
||||
}
|
||||
}
|
||||
|
||||
// single-use helpers placed together for readability. One should be the exact inverse of the other except for the clamping.
|
||||
private fun splitAmountToX() =
|
||||
stage.width - width + (1f - splitAmount) * (width - handleWidth)
|
||||
private fun xToSplitAmount(x: Float) =
|
||||
(1f - (x + width - stage.width) / (width - handleWidth)).coerceIn(0f, 1f)
|
||||
|
||||
fun reposition() {
|
||||
if (stage == null) return
|
||||
val dx = stage.width + (1f - splitAmount) * (width - handleWidth)
|
||||
setPosition(dx, 0f, Align.bottomRight)
|
||||
when (splitAmount) {
|
||||
0f -> {
|
||||
arrowIcon.rotation = 0f
|
||||
arrowIcon.isVisible = true
|
||||
}
|
||||
1f -> {
|
||||
arrowIcon.rotation = 180f
|
||||
arrowIcon.isVisible = true
|
||||
}
|
||||
else -> arrowIcon.isVisible = false
|
||||
}
|
||||
setPosition(splitAmountToX(), 0f, Align.bottomLeft)
|
||||
}
|
||||
}
|
||||
|
@ -214,7 +214,7 @@ class MapEditorViewTab(
|
||||
if (tiles.isEmpty()) return
|
||||
if (roundRobinIndex >= tiles.size) roundRobinIndex = 0
|
||||
val tile = tiles[roundRobinIndex++]
|
||||
editorScreen.mapHolder.setCenterPosition(tile.position)
|
||||
editorScreen.mapHolder.setCenterPosition(tile.position, blink = true)
|
||||
tileClickHandler(tile)
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ enum class MapGeneratorSteps(
|
||||
RareFeatures("Spawn rare features", MapGeneratorStepsHelpers.applyRareFeatures),
|
||||
Ice("Distribute ice"),
|
||||
Continents("Assign continent IDs"),
|
||||
NaturalWonders("Natural Wonders"),
|
||||
NaturalWonders("Place Natural Wonders"),
|
||||
Rivers("Let the rivers flow"),
|
||||
Resources("Spread Resources", MapGeneratorStepsHelpers.applyResources),
|
||||
AncientRuins("Create ancient ruins"),
|
||||
|
@ -27,7 +27,7 @@ class MapOptionsTable(private val newGameScreen: NewGameScreen): Table() {
|
||||
private val mapFilesSequence = sequence<FileHandle> {
|
||||
yieldAll(MapSaver.getMaps().asSequence())
|
||||
for (modFolder in RulesetCache.values.mapNotNull { it.folderLocation }) {
|
||||
val mapsFolder = modFolder.child("maps")
|
||||
val mapsFolder = modFolder.child(MapSaver.mapsFolder)
|
||||
if (mapsFolder.exists())
|
||||
yieldAll(mapsFolder.list().asSequence())
|
||||
}
|
||||
|
@ -163,12 +163,8 @@ class ImprovementPickerScreen(
|
||||
|
||||
// icon for removing the resource by replacing improvement
|
||||
if (removeImprovement && tileInfo.hasViewableResource(currentPlayerCiv) && tileInfo.tileResource.improvement == tileInfo.improvement) {
|
||||
val crossedResource = Group()
|
||||
val cross = ImageGetter.getRedCross(30f, 0.8f)
|
||||
val resourceIcon = ImageGetter.getResourceImage(tileInfo.resource.toString(), 30f)
|
||||
crossedResource.addActor(resourceIcon)
|
||||
crossedResource.addActor(cross)
|
||||
statIcons.add(crossedResource).padTop(30f).padRight(33f)
|
||||
val resourceIcon = ImageGetter.getResourceImage(tileInfo.resource!!, 30f)
|
||||
statIcons.add(ImageGetter.getCrossedImage(resourceIcon, 30f))
|
||||
}
|
||||
return statIcons
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user