Minor Map Editor improvements (#6795)

* Map Editor - fix changing resources bugged by tileResource lazy

* Map Editor - apply resource deposit abundance from map's parameters

* Map Editor - resource abundance - readability
This commit is contained in:
SomeTroglodyte 2022-05-15 09:56:18 +02:00 committed by GitHub
parent 03579a884d
commit 049df797f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 92 additions and 50 deletions

View File

@ -20,6 +20,7 @@ import com.unciv.ui.utils.Fonts
import com.unciv.ui.utils.toPercent import com.unciv.ui.utils.toPercent
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.min import kotlin.math.min
import kotlin.random.Random
open class TileInfo { open class TileInfo {
@Transient @Transient
@ -69,6 +70,10 @@ open class TileInfo {
var naturalWonder: String? = null var naturalWonder: String? = null
var resource: String? = null var resource: String? = null
set(value) {
tileResourceCache = null
field = value
}
var resourceAmount: Int = 0 var resourceAmount: Int = 0
var improvement: String? = null var improvement: String? = null
var improvementInProgress: String? = null var improvementInProgress: String? = null
@ -162,11 +167,16 @@ open class TileInfo {
else -> getBaseTerrain() else -> getBaseTerrain()
} }
@delegate:Transient @Transient
val tileResource: TileResource by lazy { private var tileResourceCache: TileResource? = null
val tileResource: TileResource
get() {
if (tileResourceCache == null) {
if (resource == null) throw Exception("No resource exists for this tile!") if (resource == null) throw Exception("No resource exists for this tile!")
else if (!ruleset.tileResources.containsKey(resource!!)) throw Exception("Resource $resource does not exist in this ruleset!") if (!ruleset.tileResources.containsKey(resource!!)) throw Exception("Resource $resource does not exist in this ruleset!")
else ruleset.tileResources[resource!!]!! tileResourceCache = ruleset.tileResources[resource!!]!!
}
return tileResourceCache!!
} }
private fun getNaturalWonder(): Terrain = private fun getNaturalWonder(): Terrain =
@ -243,7 +253,7 @@ open class TileInfo {
fun isRoughTerrain() = getAllTerrains().any{ it.isRough() } fun isRoughTerrain() = getAllTerrains().any{ it.isRough() }
/** Checks whether any of the TERRAINS of this tile has a certain unqiue */ /** Checks whether any of the TERRAINS of this tile has a certain unique */
fun terrainHasUnique(uniqueType: UniqueType) = getAllTerrains().any { it.hasUnique(uniqueType) } fun terrainHasUnique(uniqueType: UniqueType) = getAllTerrains().any { it.hasUnique(uniqueType) }
/** Get all uniques of this type that any TERRAIN on this tile has */ /** Get all uniques of this type that any TERRAIN on this tile has */
fun getTerrainMatchingUniques(uniqueType: UniqueType, stateForConditionals: StateForConditionals = StateForConditionals(tile=this) ): Sequence<Unique> { fun getTerrainMatchingUniques(uniqueType: UniqueType, stateForConditionals: StateForConditionals = StateForConditionals(tile=this) ): Sequence<Unique> {
@ -558,7 +568,7 @@ open class TileInfo {
// Can't build on unbuildable terrains - EXCEPT when specifically allowed to // Can't build on unbuildable terrains - EXCEPT when specifically allowed to
topTerrain.unbuildable && !improvement.isAllowedOnFeature(topTerrain.name) -> false topTerrain.unbuildable && !improvement.isAllowedOnFeature(topTerrain.name) -> false
// Can't build it if it is only allowed to improve resources and it doesn't improve this reousrce // Can't build it if it is only allowed to improve resources and it doesn't improve this resource
improvement.hasUnique(UniqueType.CanOnlyImproveResource, stateForConditionals) && ( improvement.hasUnique(UniqueType.CanOnlyImproveResource, stateForConditionals) && (
!resourceIsVisible || !tileResource.isImprovedBy(improvement.name) !resourceIsVisible || !tileResource.isImprovedBy(improvement.name)
) -> false ) -> false
@ -899,7 +909,15 @@ open class TileInfo {
for (unit in this.getUnits()) removeUnit(unit) for (unit in this.getUnits()) removeUnit(unit)
} }
fun setTileResource(newResource: TileResource, majorDeposit: Boolean = false) { /**
* Sets this tile's [resource] and, if [newResource] is a Strategic resource, [resourceAmount] fields.
*
* [resourceAmount] is determined by [MapParameters.mapResources] and [majorDeposit], and
* if the latter is `null` a random choice between major and minor deposit is made, approximating
* the frequency [MapRegions][com.unciv.logic.map.mapgenerator.MapRegions] would use.
* A randomness source ([rng]) can optionally be provided for that step (not used otherwise).
*/
fun setTileResource(newResource: TileResource, majorDeposit: Boolean? = null, rng: Random = Random.Default) {
resource = newResource.name resource = newResource.name
if (newResource.resourceType != ResourceType.Strategic) return if (newResource.resourceType != ResourceType.Strategic) return
@ -911,20 +929,27 @@ open class TileInfo {
} }
} }
val majorDepositFinal = majorDeposit ?: (rng.nextDouble() < approximateMajorDepositDistribution())
val depositAmounts = if (majorDepositFinal) newResource.majorDepositAmount else newResource.minorDepositAmount
resourceAmount = when (tileMap.mapParameters.mapResources) { resourceAmount = when (tileMap.mapParameters.mapResources) {
MapResources.sparse -> { MapResources.sparse -> depositAmounts.sparse
if (majorDeposit) newResource.majorDepositAmount.sparse MapResources.abundant -> depositAmounts.abundant
else newResource.minorDepositAmount.sparse else -> depositAmounts.default
}
MapResources.abundant -> {
if (majorDeposit) newResource.majorDepositAmount.abundant
else newResource.minorDepositAmount.abundant
}
else -> {
if (majorDeposit) newResource.majorDepositAmount.default
else newResource.minorDepositAmount.default
} }
} }
private fun approximateMajorDepositDistribution(): Double {
// We can't replicate the MapRegions resource distributor, so let's try to get
// a close probability of major deposits per tile
var probability = 0.0
for (unique in getAllTerrains().flatMap { it.getMatchingUniques(UniqueType.MajorStrategicFrequency) }) {
val frequency = unique.params[0].toIntOrNull() ?: continue
if (frequency <= 0) continue
// The unique param is literally "every N tiles", so to get a probability p=1/f
probability += 1.0 / frequency
}
return if (probability == 0.0) 0.04 // This is the default of 1 per 25 tiles
else probability
} }
fun setTerrainFeatures(terrainFeatureList:List<String>) { fun setTerrainFeatures(terrainFeatureList:List<String>) {

View File

@ -280,7 +280,7 @@ class MapGenerator(val ruleset: Ruleset) {
val locations = randomness.chooseSpreadOutLocations(resourcesPerType, suitableTiles, mapRadius) val locations = randomness.chooseSpreadOutLocations(resourcesPerType, suitableTiles, mapRadius)
for (location in locations) location.setTileResource(resource) for (location in locations) location.setTileResource(resource, rng = randomness.RNG)
} }
} }
@ -307,7 +307,7 @@ class MapGenerator(val ruleset: Ruleset) {
if (possibleResources.isEmpty()) continue if (possibleResources.isEmpty()) continue
val resourceWithLeastAssignments = possibleResources.minByOrNull { resourceToNumber[it.name]!! }!! val resourceWithLeastAssignments = possibleResources.minByOrNull { resourceToNumber[it.name]!! }!!
resourceToNumber.add(resourceWithLeastAssignments.name, 1) resourceToNumber.add(resourceWithLeastAssignments.name, 1)
tile.setTileResource(resourceWithLeastAssignments) tile.setTileResource(resourceWithLeastAssignments, rng = randomness.RNG)
} }
} }

View File

@ -10,6 +10,7 @@ import com.unciv.logic.map.TileInfo
import com.unciv.models.ruleset.Nation import com.unciv.models.ruleset.Nation
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.tile.* import com.unciv.models.ruleset.tile.*
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.civilopedia.FormattedLine
import com.unciv.ui.civilopedia.MarkupRenderer import com.unciv.ui.civilopedia.MarkupRenderer
@ -18,6 +19,7 @@ import com.unciv.ui.mapeditor.MapEditorEditTab.BrushHandlerType
import com.unciv.ui.tilegroups.TileGroup 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.*
import kotlin.random.Random
internal interface IMapEditorEditSubTabs { internal interface IMapEditorEditSubTabs {
fun isDisabled(): Boolean fun isDisabled(): Boolean
@ -146,15 +148,16 @@ class MapEditorEditResourcesTab(
add(MarkupRenderer.render( add(MarkupRenderer.render(
getResources(), getResources(),
iconDisplay = FormattedLine.IconDisplay.NoLink iconDisplay = FormattedLine.IconDisplay.NoLink
) { ) { resourceName ->
editTab.setBrush(it, "Resource/$it") { tile -> val resource = ruleset.tileResources[resourceName]!!
tile.resource = it editTab.setBrush(resourceName, resource.makeLink()) {
it.setTileResource(resource, rng = editTab.randomness.RNG)
} }
}).padTop(0f).row() }).padTop(0f).row()
} }
private fun allowedResources() = ruleset.tileResources.values.asSequence() private fun allowedResources() = ruleset.tileResources.values.asSequence()
.filter { !it.hasUnique("Can only be created by Mercantile City-States") } //todo type-i-fy .filter { !it.hasUnique(UniqueType.CityStateOnlyResource) }
private fun getResources(): List<FormattedLine> = sequence { private fun getResources(): List<FormattedLine> = sequence {
var lastGroup = ResourceType.Bonus var lastGroup = ResourceType.Bonus
for (resource in allowedResources()) { for (resource in allowedResources()) {

View File

@ -28,7 +28,7 @@ class MapEditorEditTab(
private val brushCell: Cell<Actor> private val brushCell: Cell<Actor>
private var ruleset = editorScreen.ruleset private var ruleset = editorScreen.ruleset
private val randomness = MapGenerationRandomness() // for auto river internal val randomness = MapGenerationRandomness() // for auto river
enum class BrushHandlerType { None, Direct, Tile, Road, River, RiverFromTo } enum class BrushHandlerType { None, Direct, Tile, Road, River, RiverFromTo }
private var brushHandlerType = BrushHandlerType.None private var brushHandlerType = BrushHandlerType.None

View File

@ -152,7 +152,9 @@ class MapEditorGenerateTab(
private val parent: MapEditorGenerateTab private val parent: MapEditorGenerateTab
): Table(BaseScreen.skin) { ): Table(BaseScreen.skin) {
val generateButton = "".toTextButton() val generateButton = "".toTextButton()
val mapParametersTable = MapParametersTable(parent.editorScreen.newMapParameters, isEmptyMapAllowed = true) val mapParametersTable = MapParametersTable(parent.editorScreen.newMapParameters, forMapEditor = true) {
parent.replacePage(0, this) // A kludge to get the ScrollPanes to recognize changes in vertical layout??
}
init { init {
top() top()
@ -161,6 +163,12 @@ class MapEditorGenerateTab(
add(mapParametersTable).row() add(mapParametersTable).row()
add(generateButton).padTop(15f).row() add(generateButton).padTop(15f).row()
generateButton.onClick { parent.generate(MapGeneratorSteps.All) } generateButton.onClick { parent.generate(MapGeneratorSteps.All) }
mapParametersTable.resourceSelectBox.onChange {
parent.editorScreen.run {
// normally the 'new map' parameters are independent, this needs to be an exception so strategic resource painting will use it
tileMap.mapParameters.mapResources = newMapParameters.mapResources
}
}
} }
} }

View File

@ -2,7 +2,6 @@ package com.unciv.ui.mapeditor
import com.badlogic.gdx.Gdx import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane
import com.unciv.MainMenuScreen import com.unciv.MainMenuScreen
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.HexMath import com.unciv.logic.HexMath
@ -18,18 +17,16 @@ import com.unciv.ui.popup.YesNoPopup
import com.unciv.ui.tilegroups.TileGroup import com.unciv.ui.tilegroups.TileGroup
import com.unciv.ui.utils.* import com.unciv.ui.utils.*
//todo normalize properly //todo normalize properly
//todo Direct Strategic Resource abundance control
//todo functional Tab for Units (empty Tab is prepared but commented out in MapEditorEditTab.AllEditSubTabs) //todo functional Tab for Units (empty Tab is prepared but commented out in MapEditorEditTab.AllEditSubTabs)
//todo copy/paste tile areas? (As tool tab, brush sized, floodfill forbidden, tab displays copied area) //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 Synergy with Civilopedia for drawing loose tiles / terrain icons
//todo left-align everything so a half-open drawer is more useful //todo left-align everything so a half-open drawer is more useful
//todo combined brush //todo combined brush
//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 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 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 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 Mod tab change base ruleset - disableAllCheckboxes - instead some intelligence to leave those mods on that stay compatible?
@ -38,7 +35,9 @@ import com.unciv.ui.utils.*
//todo "random nation" starting location (maybe no new internal representation = all major nations) //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 Nat Wonder step generator: Needs tweaks to avoid placing duplicates or wonders too close together
//todo Music? Different suffix? Off? 20% Volume? //todo Music? Different suffix? Off? 20% Volume?
//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 //todo See #6694 - allow placing Barbarian encampments (problem: dead on game start - BarbarianManager.encampments)
//todo See #6694 - allow adding tiles to a map (1 cell all around on hex? world-wrapped hex?? all around on rectangular? top bottom only on world-wrapped??)
//todo move map copy&paste to save/load??
class MapEditorScreen(map: TileMap? = null): BaseScreen() { class MapEditorScreen(map: TileMap? = null): BaseScreen() {
/** The map being edited, with mod list for that map */ /** The map being edited, with mod list for that map */
@ -101,7 +100,7 @@ class MapEditorScreen(map: TileMap? = null): BaseScreen() {
?: return MapParameters() ?: return MapParameters()
return lastSetup.mapParameters.clone().apply { return lastSetup.mapParameters.clone().apply {
reseed() reseed()
mods.removeAll(RulesetCache.getSortedBaseRulesets()) mods.removeAll(RulesetCache.getSortedBaseRulesets().toSet())
} }
} }
fun saveDefaultParameters(parameters: MapParameters) { fun saveDefaultParameters(parameters: MapParameters) {

View File

@ -13,11 +13,12 @@ import com.unciv.ui.utils.*
* *
* This is a separate class, because it should be in use both in the New Game screen and the Map Editor screen * This is a separate class, because it should be in use both in the New Game screen and the Map Editor screen
* *
* @param isEmptyMapAllowed whether the [MapType.empty] option should be present. Is used by the Map Editor, but should **never** be used with the New Game * @param forMapEditor whether the [MapType.empty] option should be present. Is used by the Map Editor, but should **never** be used with the New Game
* */ * */
class MapParametersTable( class MapParametersTable(
private val mapParameters: MapParameters, private val mapParameters: MapParameters,
private val isEmptyMapAllowed: Boolean = false private val forMapEditor: Boolean = false,
private val sizeChangedCallback: (()->Unit)? = null
) : Table() { ) : Table() {
// These are accessed fom outside the class to read _and_ write values, // These are accessed fom outside the class to read _and_ write values,
// namely from MapOptionsTable, NewMapScreen and NewGameScreen // namely from MapOptionsTable, NewMapScreen and NewGameScreen
@ -84,7 +85,7 @@ class MapParametersTable(
MapType.perlin, MapType.perlin,
MapType.archipelago, MapType.archipelago,
MapType.innerSea, MapType.innerSea,
if (isEmptyMapAllowed) MapType.empty else null if (forMapEditor) MapType.empty else null
) )
mapTypeSelectBox = TranslatedSelectBox(mapTypes, mapParameters.type, skin) mapTypeSelectBox = TranslatedSelectBox(mapTypes, mapParameters.type, skin)
@ -166,10 +167,16 @@ class MapParametersTable(
customWorldSizeTable.add(rectangularSizeTable).grow().row() customWorldSizeTable.add(rectangularSizeTable).grow().row()
else else
mapParameters.mapSize = MapSizeNew(worldSizeSelectBox.selected.value) mapParameters.mapSize = MapSizeNew(worldSizeSelectBox.selected.value)
sizeChangedCallback?.invoke()
} }
private fun addResourceSelectBox() { private fun addResourceSelectBox() {
val mapTypes = listOfNotNull( val mapTypes = if (forMapEditor) listOf(
MapResources.sparse,
MapResources.default,
MapResources.abundant,
) else listOf(
MapResources.sparse, MapResources.sparse,
MapResources.default, MapResources.default,
MapResources.abundant, MapResources.abundant,