mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-04 07:17:50 +07:00
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:
@ -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] }
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
Reference in New Issue
Block a user