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
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 kotlin.math.abs
import kotlin.math.min
import kotlin.random.Random
open class TileInfo {
@Transient
@ -69,6 +70,10 @@ open class TileInfo {
var naturalWonder: String? = null
var resource: String? = null
set(value) {
tileResourceCache = null
field = value
}
var resourceAmount: Int = 0
var improvement: String? = null
var improvementInProgress: String? = null
@ -162,12 +167,17 @@ open class TileInfo {
else -> getBaseTerrain()
}
@delegate:Transient
val tileResource: TileResource by lazy {
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!")
else ruleset.tileResources[resource!!]!!
}
@Transient
private var tileResourceCache: TileResource? = null
val tileResource: TileResource
get() {
if (tileResourceCache == null) {
if (resource == null) throw Exception("No resource exists for this tile!")
if (!ruleset.tileResources.containsKey(resource!!)) throw Exception("Resource $resource does not exist in this ruleset!")
tileResourceCache = ruleset.tileResources[resource!!]!!
}
return tileResourceCache!!
}
private fun getNaturalWonder(): Terrain =
if (naturalWonder == null) throw Exception("No natural wonder exists for this tile!")
@ -243,7 +253,7 @@ open class TileInfo {
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) }
/** Get all uniques of this type that any TERRAIN on this tile has */
fun getTerrainMatchingUniques(uniqueType: UniqueType, stateForConditionals: StateForConditionals = StateForConditionals(tile=this) ): Sequence<Unique> {
@ -530,15 +540,15 @@ open class TileInfo {
RoadStatus.values().any { it.name == improvement.name } -> !isWater && RoadStatus.valueOf(improvement.name) > roadStatus
// Then we check if there is any reason to not allow this improvement to be build
// Can't build if there is already an irremovable improvement here
this.improvement != null && getTileImprovement()!!.hasUnique(UniqueType.Irremovable, stateForConditionals) -> false
// Can't build if any terrain specifically prevents building this improvement
getTerrainMatchingUniques(UniqueType.RestrictedBuildableImprovements, stateForConditionals).any {
unique -> !improvement.matchesFilter(unique.params[0])
} -> false
// Can't build if the improvement specifically prevents building on some present feature
improvement.getMatchingUniques(UniqueType.CannotBuildOnTile, stateForConditionals).any {
unique -> matchesTerrainFilter(unique.params[0])
@ -549,7 +559,7 @@ open class TileInfo {
improvement.getMatchingUniques(UniqueType.CanOnlyBeBuiltOnTile, stateForConditionals).let {
it.any() && it.any { unique -> !matchesTerrainFilter(unique.params[0]) }
} -> false
// Can't build if the improvement requires an adjacent terrain that is not present
improvement.getMatchingUniques(UniqueType.MustBeNextTo, stateForConditionals).any {
!isAdjacentTo(it.params[0])
@ -557,8 +567,8 @@ open class TileInfo {
// Can't build on unbuildable terrains - EXCEPT when specifically allowed to
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) && (
!resourceIsVisible || !tileResource.isImprovedBy(improvement.name)
) -> false
@ -899,7 +909,15 @@ open class TileInfo {
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
if (newResource.resourceType != ResourceType.Strategic) return
@ -911,22 +929,29 @@ open class TileInfo {
}
}
val majorDepositFinal = majorDeposit ?: (rng.nextDouble() < approximateMajorDepositDistribution())
val depositAmounts = if (majorDepositFinal) newResource.majorDepositAmount else newResource.minorDepositAmount
resourceAmount = when (tileMap.mapParameters.mapResources) {
MapResources.sparse -> {
if (majorDeposit) newResource.majorDepositAmount.sparse
else newResource.minorDepositAmount.sparse
}
MapResources.abundant -> {
if (majorDeposit) newResource.majorDepositAmount.abundant
else newResource.minorDepositAmount.abundant
}
else -> {
if (majorDeposit) newResource.majorDepositAmount.default
else newResource.minorDepositAmount.default
}
MapResources.sparse -> depositAmounts.sparse
MapResources.abundant -> depositAmounts.abundant
else -> depositAmounts.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>) {
terrainFeatures = terrainFeatureList
terrainFeatureObjects = terrainFeatureList.mapNotNull { ruleset.terrains[it] }

View File

@ -280,7 +280,7 @@ class MapGenerator(val ruleset: Ruleset) {
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
val resourceWithLeastAssignments = possibleResources.minByOrNull { resourceToNumber[it.name]!! }!!
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.Ruleset
import com.unciv.models.ruleset.tile.*
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.translations.tr
import com.unciv.ui.civilopedia.FormattedLine
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.TileSetStrings
import com.unciv.ui.utils.*
import kotlin.random.Random
internal interface IMapEditorEditSubTabs {
fun isDisabled(): Boolean
@ -146,15 +148,16 @@ class MapEditorEditResourcesTab(
add(MarkupRenderer.render(
getResources(),
iconDisplay = FormattedLine.IconDisplay.NoLink
) {
editTab.setBrush(it, "Resource/$it") { tile ->
tile.resource = it
) { resourceName ->
val resource = ruleset.tileResources[resourceName]!!
editTab.setBrush(resourceName, resource.makeLink()) {
it.setTileResource(resource, rng = editTab.randomness.RNG)
}
}).padTop(0f).row()
}
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 {
var lastGroup = ResourceType.Bonus
for (resource in allowedResources()) {

View File

@ -28,7 +28,7 @@ class MapEditorEditTab(
private val brushCell: Cell<Actor>
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 }
private var brushHandlerType = BrushHandlerType.None

View File

@ -152,7 +152,9 @@ class MapEditorGenerateTab(
private val parent: MapEditorGenerateTab
): Table(BaseScreen.skin) {
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 {
top()
@ -161,6 +163,12 @@ class MapEditorGenerateTab(
add(mapParametersTable).row()
add(generateButton).padTop(15f).row()
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.graphics.Color
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane
import com.unciv.MainMenuScreen
import com.unciv.UncivGame
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.utils.*
//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 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 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?
@ -38,7 +35,9 @@ import com.unciv.ui.utils.*
//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 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() {
/** The map being edited, with mod list for that map */
@ -101,7 +100,7 @@ class MapEditorScreen(map: TileMap? = null): BaseScreen() {
?: return MapParameters()
return lastSetup.mapParameters.clone().apply {
reseed()
mods.removeAll(RulesetCache.getSortedBaseRulesets())
mods.removeAll(RulesetCache.getSortedBaseRulesets().toSet())
}
}
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
*
* @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(
private val mapParameters: MapParameters,
private val isEmptyMapAllowed: Boolean = false
private val forMapEditor: Boolean = false,
private val sizeChangedCallback: (()->Unit)? = null
) : Table() {
// These are accessed fom outside the class to read _and_ write values,
// namely from MapOptionsTable, NewMapScreen and NewGameScreen
@ -84,7 +85,7 @@ class MapParametersTable(
MapType.perlin,
MapType.archipelago,
MapType.innerSea,
if (isEmptyMapAllowed) MapType.empty else null
if (forMapEditor) MapType.empty else null
)
mapTypeSelectBox = TranslatedSelectBox(mapTypes, mapParameters.type, skin)
@ -166,15 +167,21 @@ class MapParametersTable(
customWorldSizeTable.add(rectangularSizeTable).grow().row()
else
mapParameters.mapSize = MapSizeNew(worldSizeSelectBox.selected.value)
sizeChangedCallback?.invoke()
}
private fun addResourceSelectBox() {
val mapTypes = listOfNotNull(
MapResources.sparse,
MapResources.default,
MapResources.abundant,
MapResources.strategicBalance,
MapResources.legendaryStart
val mapTypes = if (forMapEditor) listOf(
MapResources.sparse,
MapResources.default,
MapResources.abundant,
) else listOf(
MapResources.sparse,
MapResources.default,
MapResources.abundant,
MapResources.strategicBalance,
MapResources.legendaryStart
)
resourceSelectBox = TranslatedSelectBox(mapTypes, mapParameters.mapResources, skin)