Rivers... Moddable Stats and Civilopedia (#10424)

* River yields moddable and Civilopedia entry

* Improve text and better fresh water explanations

* Map editor exclusion as Unique, for River and previously hardcoded Improvements

* Map editor brush to Civilopedia link, starting locations cleaned

* Some SeventhM input applied

* Reword River/Lakes/Oasis civilopediaText and comments again
This commit is contained in:
SomeTroglodyte 2023-11-13 21:18:25 +01:00 committed by GitHub
parent 22a50c7d2d
commit 2847f7a8c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 251 additions and 102 deletions

View File

@ -1,5 +1,6 @@
[
//Spectator
//Not "Excluded from map editor" as it serves as placeholder for "Any Civ" starting locations
{
"name": "Spectator",
"outerColor": [255,255,255],
@ -1348,7 +1349,7 @@
"outerColor": [0, 0, 0],
"innerColor": [211,180,113],
"cities": ["Dublin"],
"uniques": ["Will not be displayed in Civilopedia", "Will not be chosen for new games"]
"uniques": ["Will not be displayed in Civilopedia", "Will not be chosen for new games", "Excluded from map editor"]
},
{
"name": "Edinburgh",
@ -1361,7 +1362,7 @@
"outerColor": [0, 0, 0],
"innerColor": [0,102,102],
"cities": ["Edinburgh"],
"uniques": ["Will not be displayed in Civilopedia", "Will not be chosen for new games"]
"uniques": ["Will not be displayed in Civilopedia", "Will not be chosen for new games", "Excluded from map editor"]
},
{
"name": "M'Banza-Kongo",
@ -1444,7 +1445,7 @@
"name": "Barbarians",
"outerColor": [0,0,0],
"innerColor": [185,12,12],
"uniques": ["Can only heal by pillaging"]
"uniques": ["Can only heal by pillaging", "Excluded from map editor"]
}
]

View File

@ -113,7 +113,15 @@
"RGB": [0, 171, 169],
"uniques": ["Fresh water",
"Considered [Food] when determining start locations",
"Considered [Desirable] when determining start locations"]
"Considered [Desirable] when determining start locations"],
"civilopediaText": [
{"text": "Lakes provide fresh water to adjacent tiles, allowing farming where it would otherwise not be possible (similar to Rivers and Oases)."},
{"text": "Rivers", "link":"Terrain/River"},
{"text": "Oasis", "link":"Terrain/Oasis"},
{"text": "Farm", "link":"Improvement/Farm"},
{"text": "Civil Service", "link":"Technology/Civil Service"},
{"text": "Fertilizer", "link":"Technology/Fertilizer"}
]
},
{
"name": "Mountain",
@ -251,7 +259,15 @@
"Only [All Road] improvements may be built on this tile",
"Always Fertility [4] for Map Generation",
"Considered [Food] when determining start locations",
"Considered [Desirable] when determining start locations"]
"Considered [Desirable] when determining start locations"],
"civilopediaText": [
{"text": "Oases provide fresh water to adjacent tiles, allowing farming where it would otherwise not be possible (similar to Rivers and Lakes)."},
{"text": "Rivers", "link":"Terrain/River"},
{"text": "Lakes", "link":"Terrain/Lakes"},
{"text": "Farm", "link":"Improvement/Farm"},
{"text": "Civil Service", "link":"Technology/Civil Service"},
{"text": "Fertilizer", "link":"Technology/Fertilizer"}
]
},
{
"name": "Flood plains",
@ -284,7 +300,37 @@
"uniques": ["Rare feature"]
},
// Natural Wonders
// Virtual River entry, used for stats, Uniques and Civilopedia
// Modders take heed:
// * This never exists on the map, it's a placeholder to allow you to mod the actual river's stats.
// * The name is hardcoded to apply stats and the Stats Unique when a tile has one or more edges with an actual river.
// * No other Unique is implemented and will work - for now.
{
"name": "River",
"type": "TerrainFeature",
"gold": 1,
"movementCost": 0, // So Civilopedia won't display a cost of 1
"uniques": ["Doesn't generate naturally", "Excluded from map editor"],
"civilopediaText": [
{"text": "Rivers exist on tile edges, not as terrain feature per se."},
{"text": "Tiles on both sides gain its benefits. These benefits do not stack."},
{"text": "The tile has access to fresh water, allowing farming where it would otherwise not be possible (similar to Oases and Lakes)."},
{"text": "Movement across rivers takes all remaining movement points of a unit unless there is a bridge."},
// See BattleConstants.ATTACKING_ACROSS_RIVER_MALUS:
{"text": "When attacking across a river, the attacker gets a -20% strength malus."},
// Related Buildings and Beliefs are automatically linked
{"text": "Lakes", "link":"Terrain/Lakes"},
{"text": "Oasis", "link":"Terrain/Oasis"},
{"text": "Farm", "link":"Improvement/Farm"},
{"text": "Road", "link":"Improvement/Road"},
{"text": "Engineering", "link":"Technology/Engineering"},
{"text": "Civil Service", "link":"Technology/Civil Service"},
{"text": "Fertilizer", "link":"Technology/Fertilizer"},
{"text": "Amphibious", "link":"Promotion/Amphibious"}
]
},
// Natural Wonders
{
"name": "Great Barrier Reef",
"type": "NaturalWonder",

View File

@ -133,7 +133,7 @@
"name": "Remove Forest",
"turnsToBuild": 4,
"techRequired": "Mining",
"uniques": ["Can be built outside your borders"],
"uniques": ["Can be built outside your borders", "Excluded from map editor"],
"shortcutKey": "X",
"civilopediaText": [{"text":"Provides a one-time Production bonus depending on distance to the closest city once finished"}]
},
@ -141,21 +141,21 @@
"name": "Remove Jungle",
"turnsToBuild": 7,
"techRequired": "Bronze Working",
"uniques": ["Can be built outside your borders"],
"uniques": ["Can be built outside your borders", "Excluded from map editor"],
"shortcutKey": "X"
},
{
"name": "Remove Fallout",
"turnsToBuild": 2,
// Has no tech requirements as it can always be built
"uniques": ["Can be built outside your borders"],
"uniques": ["Can be built outside your borders", "Excluded from map editor"],
"shortcutKey": "X"
},
{
"name": "Remove Marsh",
"turnsToBuild": 6,
"techRequired": "Masonry",
"uniques": ["Can be built outside your borders"],
"uniques": ["Can be built outside your borders", "Excluded from map editor"],
"shortcutKey": "X"
},
@ -163,16 +163,16 @@
{
"name": "Remove Road",
"turnsToBuild": 2,
"uniques": ["Can be built outside your borders"]
"uniques": ["Can be built outside your borders", "Excluded from map editor"]
},
{
"name": "Remove Railroad",
"turnsToBuild": 2,
"uniques": ["Can be built outside your borders"]
"uniques": ["Can be built outside your borders", "Excluded from map editor"]
},
{
"name": "Cancel improvement order",
"uniques": ["Can be built outside your borders"],
"uniques": ["Can be built outside your borders", "Excluded from map editor"],
"shortcutKey": "."
},
// Purely for turnsToBuild and civilopediaText. Unbuildable so it doesn't show in ImprovementPicker
@ -181,7 +181,7 @@
"terrainsCanBeBuiltOn": ["Land"],
"turnsToBuild": 2,
"shortcutKey": "E",
"uniques": ["Unbuildable"],
"uniques": ["Unbuildable", "Excluded from map editor"],
"civilopediaText": [{"text":"Repairs a pillaged Improvement or Route"}]
},
@ -282,7 +282,7 @@
"name": "City center",
"terrainsCanBeBuiltOn": ["Land"],
"uniques": ["Ensures a minimum tile yield of [+2 Food, +1 Production]",
"Unpillagable", "Irremovable", "Unbuildable"],
"Unpillagable", "Irremovable", "Unbuildable", "Excluded from map editor"],
"civilopediaText": [
{"text":"Marks the center of a city"},
{"text":"Appearance changes with the technological era of the owning civilization"}
@ -291,7 +291,7 @@
{
"name": "Barbarian encampment",
"terrainsCanBeBuiltOn": ["Land"],
"uniques": ["Unpillagable", "Unbuildable"],
"uniques": ["Unpillagable", "Unbuildable", "Excluded from map editor"],
"civilopediaText": [{"text":"Home to uncivilized barbarians, will spawn a hostile unit from time to time"}]
}
]

View File

@ -1,5 +1,6 @@
[
//Spectator
//Not "Excluded from map editor" as it serves as placeholder for "Any Civ" starting locations
{
"name": "Spectator",
"outerColor": [255,255,255]
@ -1026,7 +1027,7 @@
"outerColor": [0, 0, 0],
"innerColor": [211,180,113],
"cities": ["Dublin"],
"uniques": ["Will not be displayed in Civilopedia", "Will not be chosen for new games"]
"uniques": ["Will not be displayed in Civilopedia", "Will not be chosen for new games", "Excluded from map editor"]
},
{
"name": "Edinburgh",
@ -1039,7 +1040,7 @@
"outerColor": [0, 0, 0],
"innerColor": [0,102,102],
"cities": ["Edinburgh"],
"uniques": ["Will not be displayed in Civilopedia", "Will not be chosen for new games"]
"uniques": ["Will not be displayed in Civilopedia", "Will not be chosen for new games", "Excluded from map editor"]
},
{
"name": "M'Banza-Kongo",
@ -1084,7 +1085,7 @@
"name": "Barbarians",
"outerColor": [0,0,0],
"innerColor": [185,12,12],
"uniques": ["Can only heal by pillaging"]
"uniques": ["Can only heal by pillaging", "Excluded from map editor"]
}
]

View File

@ -113,7 +113,15 @@
"RGB": [0, 171, 169],
"uniques": ["Fresh water",
"Considered [Food] when determining start locations",
"Considered [Desirable] when determining start locations"]
"Considered [Desirable] when determining start locations"],
"civilopediaText": [
{"text": "Lakes provide fresh water to adjacent tiles, allowing farming where it would otherwise not be possible (similar to Rivers and Oases)."},
{"text": "Rivers", "link":"Terrain/River"},
{"text": "Oasis", "link":"Terrain/Oasis"},
{"text": "Farm", "link":"Improvement/Farm"},
{"text": "Civil Service", "link":"Technology/Civil Service"},
{"text": "Fertilizer", "link":"Technology/Fertilizer"}
]
},
{
"name": "Mountain",
@ -252,7 +260,15 @@
"Only [All Road] improvements may be built on this tile",
"Always Fertility [4] for Map Generation",
"Considered [Food] when determining start locations",
"Considered [Desirable] when determining start locations"]
"Considered [Desirable] when determining start locations"],
"civilopediaText": [
{"text": "Oases provide fresh water to adjacent tiles, allowing farming where it would otherwise not be possible (similar to Rivers and Lakes)."},
{"text": "Rivers", "link":"Terrain/River"},
{"text": "Lakes", "link":"Terrain/Lakes"},
{"text": "Farm", "link":"Improvement/Farm"},
{"text": "Civil Service", "link":"Technology/Civil Service"},
{"text": "Fertilizer", "link":"Technology/Fertilizer"}
]
},
{
"name": "Flood plains",
@ -285,7 +301,37 @@
"uniques": ["Rare feature"]
},
// Natural Wonders
// Virtual River entry, used for stats, Uniques and Civilopedia
// Modders take heed:
// * This never exists on the map, it's a placeholder to allow you to mod the actual river's stats.
// * The name is hardcoded to apply stats and the Stats Unique when a tile has one or more edges with an actual river.
// * No other Unique is implemented and will work - for now.
{
"name": "River",
"type": "TerrainFeature",
"gold": 1,
"movementCost": 0, // So Civilopedia won't display a cost of 1
"uniques": ["Doesn't generate naturally", "Excluded from map editor"],
"civilopediaText": [
{"text": "Rivers exist on tile edges, not as terrain feature per se."},
{"text": "Tiles on both sides gain its benefits. These benefits do not stack."},
{"text": "The tile has access to fresh water, allowing farming where it would otherwise not be possible (similar to Oases and Lakes)."},
{"text": "Movement across rivers takes all remaining movement points of a unit unless there is a bridge."},
// See BattleConstants.ATTACKING_ACROSS_RIVER_MALUS:
{"text": "When attacking across a river, the attacker gets a -20% strength malus."},
// Related Buildings are automatically linked
{"text": "Lakes", "link":"Terrain/Lakes"},
{"text": "Oasis", "link":"Terrain/Oasis"},
{"text": "Farm", "link":"Improvement/Farm"},
{"text": "Road", "link":"Improvement/Road"},
{"text": "Engineering", "link":"Technology/Engineering"},
{"text": "Civil Service", "link":"Technology/Civil Service"},
{"text": "Fertilizer", "link":"Technology/Fertilizer"},
{"text": "Amphibious", "link":"Promotion/Amphibious"}
]
},
// Natural Wonders
{
"name": "Great Barrier Reef",
"type": "NaturalWonder",

View File

@ -133,7 +133,7 @@
"name": "Remove Forest",
"turnsToBuild": 4,
"techRequired": "Mining",
"uniques": ["Can be built outside your borders"],
"uniques": ["Can be built outside your borders", "Excluded from map editor"],
"shortcutKey": "X",
"civilopediaText": [{"text":"Provides a one-time Production bonus depending on distance to the closest city once finished"}]
},
@ -141,21 +141,21 @@
"name": "Remove Jungle",
"turnsToBuild": 7,
"techRequired": "Bronze Working",
"uniques": ["Can be built outside your borders"],
"uniques": ["Can be built outside your borders", "Excluded from map editor"],
"shortcutKey": "X"
},
{
"name": "Remove Fallout",
"turnsToBuild": 2,
// Has no tech requirements as it can always be built
"uniques": ["Can be built outside your borders"],
"uniques": ["Can be built outside your borders", "Excluded from map editor"],
"shortcutKey": "X"
},
{
"name": "Remove Marsh",
"turnsToBuild": 6,
"techRequired": "Masonry",
"uniques": ["Can be built outside your borders"],
"uniques": ["Can be built outside your borders", "Excluded from map editor"],
"shortcutKey": "X"
},
@ -163,16 +163,16 @@
{
"name": "Remove Road",
"turnsToBuild": 2,
"uniques": ["Can be built outside your borders"]
"uniques": ["Can be built outside your borders", "Excluded from map editor"]
},
{
"name": "Remove Railroad",
"turnsToBuild": 2,
"uniques": ["Can be built outside your borders"]
"uniques": ["Can be built outside your borders", "Excluded from map editor"]
},
{
"name": "Cancel improvement order",
"uniques": ["Can be built outside your borders"],
"uniques": ["Can be built outside your borders", "Excluded from map editor"],
"shortcutKey": "."
},
// Purely for turnsToBuild and civilopediaText. Unbuildable so it doesn't show in ImprovementPicker
@ -180,7 +180,7 @@
"name": "Repair",
"terrainsCanBeBuiltOn": ["Land"],
"turnsToBuild": 2,
"uniques": ["Unbuildable"],
"uniques": ["Unbuildable", "Excluded from map editor"],
"shortcutKey": "E",
"civilopediaText": [{"text":"Repairs a pillaged Improvement or Route"}]
},
@ -271,7 +271,7 @@
"name": "City center",
"terrainsCanBeBuiltOn": ["Land"],
"uniques": ["Ensures a minimum tile yield of [+2 Food, +1 Production]",
"Unpillagable", "Irremovable", "Unbuildable"],
"Unpillagable", "Irremovable", "Unbuildable", "Excluded from map editor"],
"civilopediaText": [
{"text":"Marks the center of a city"},
{"text":"Appearance changes with the technological era of the owning civilization"}
@ -280,7 +280,7 @@
{
"name": "Barbarian encampment",
"terrainsCanBeBuiltOn": ["Land"],
"uniques": ["Unpillagable", "Unbuildable"],
"uniques": ["Unpillagable", "Unbuildable", "Excluded from map editor"],
"civilopediaText": [{"text":"Home to uncivilized barbarians, will spawn a hostile unit from time to time"}]
}
]

View File

@ -2,6 +2,9 @@
"useColorAsBaseTerrain": "false",
"fallbackTileSet": null,
"ruleVariants": {
// River pseudo-tile used in Civilopedia display only
"Grassland+River": ["Plains","River-BottomLeft","River-Bottom","River-BottomRight"],
//Legacy hill support
"Hill": ["Grassland","Hill"],
"Hill+Forest+Uranium": ["Grassland","Hill","Uranium","Forest"],

View File

@ -3,6 +3,9 @@
"fallbackTileSet": "FantasyHex",
"ruleVariants": {
// River pseudo-tile used in Civilopedia display only
"Grassland+River": ["Plains","River-BottomLeft","River-Bottom","River-BottomRight"],
//forest and jungle
"Grassland+Forest": ["Grassland","ForestG"],
"Grassland+Jungle": ["Grassland","JungleG"],

View File

@ -551,7 +551,6 @@ Spread Resources =
Create ancient ruins =
Floodfill =
[nation] starting location =
Any Civ starting locations =
Any Civ =
Remove features =
Remove improvement =

View File

@ -16,6 +16,9 @@ object Constants {
/** The "Coastal" terrain _filter_ */
const val coastal = "Coastal"
/** Used as filter and the name of the pseudo-TerrainFeature defining river Stats */
const val river = "River"
const val mountain = "Mountain"
const val hill = "Hill"
const val plains = "Plains"

View File

@ -465,7 +465,7 @@ open class Tile : IsPartOfGameInfoSerialization {
// This should be the only adjacency function
fun isAdjacentTo(terrainFilter:String): Boolean {
// Rivers are odd, as they aren't technically part of any specific tile but still count towards adjacency
if (terrainFilter == "River") return isAdjacentToRiver()
if (terrainFilter == Constants.river) return isAdjacentToRiver()
if (terrainFilter == Constants.freshWater && isAdjacentToRiver()) return true
return (neighbors + this).any { neighbor -> neighbor.matchesFilter(terrainFilter) }
}
@ -485,7 +485,7 @@ open class Tile : IsPartOfGameInfoSerialization {
"Water" -> isWater
"Land" -> isLand
Constants.coastal -> isCoastalTile()
"River" -> isAdjacentToRiver()
Constants.river -> isAdjacentToRiver()
naturalWonder -> true
"Open terrain" -> !isRoughTerrain()
"Rough terrain" -> isRoughTerrain()

View File

@ -3,6 +3,7 @@ package com.unciv.logic.map.tile
import com.unciv.Constants
import com.unciv.logic.city.City
import com.unciv.logic.civilization.Civilization
import com.unciv.models.ruleset.tile.Terrain
import com.unciv.models.ruleset.tile.TileImprovement
import com.unciv.models.ruleset.unique.LocalUniqueCache
import com.unciv.models.ruleset.unique.StateForConditionals
@ -12,6 +13,7 @@ import com.unciv.models.stats.Stats
import com.unciv.ui.components.extensions.toPercent
class TileStatFunctions(val tile: Tile) {
private val riverTerrain by lazy { tile.ruleset.terrains[Constants.river] }
fun getTileStats(
observingCiv: Civilization?,
@ -53,7 +55,15 @@ class TileStatFunctions(val tile: Tile) {
}
}
if (tile.isAdjacentToRiver()) stats.gold++
if (tile.isAdjacentToRiver()) {
if (riverTerrain == null)
stats.gold++ // Fallback for legacy mods
else
//TODO this is one approach to get these stats in - supporting only the Stats UniqueType.
// Alternatives: append riverTerrain to allTerrains, or append riverTerrain.uniques to
// the Tile's UniqueObjects/UniqueMap (while copying onl<e> base Stats directly here)
stats.add(getSingleTerrainStats(riverTerrain!!, stateForConditionals))
}
if (observingCiv != null) {
// resource base
@ -92,28 +102,37 @@ class TileStatFunctions(val tile: Tile) {
}
}
/** Gets stats of a single Terrain, unifying the Stats class a Terrain inherits and the Stats Unique
* @return A Stats reference, must not be mutated
*/
private fun getSingleTerrainStats(terrain: Terrain, stateForConditionals: StateForConditionals): Stats {
var stats: Stats = terrain
for (unique in terrain.getMatchingUniques(UniqueType.Stats, stateForConditionals)) {
if (stats === terrain)
stats = stats.clone()
stats.add(unique.stats)
}
return stats
}
/** Gets basic stats to start off [getTileStats] or [getTileStartYield], independently mutable result */
private fun getTerrainStats(stateForConditionals: StateForConditionals = StateForConditionals()): Stats {
var stats: Stats? = null
var stats = Stats()
// allTerrains iterates over base, natural wonder, then features
for (terrain in tile.allTerrains) {
for (unique in terrain.getMatchingUniques(UniqueType.Stats, stateForConditionals)) {
if (stats == null) {
stats = unique.stats.clone()
}
else stats.add(unique.stats)
}
val terrainStats = getSingleTerrainStats(terrain, stateForConditionals)
when {
terrain.hasUnique(UniqueType.NullifyYields, stateForConditionals) ->
return terrain.cloneStats()
terrain.overrideStats || stats == null ->
stats = terrain.cloneStats()
return terrainStats.clone()
terrain.overrideStats ->
stats = terrainStats.clone()
else ->
stats.add(terrain)
stats.add(terrainStats)
}
}
return stats ?: Stats.ZERO // For tests
return stats
}
// Only gets the tile percentage bonus, not the improvement percentage bonus

View File

@ -121,8 +121,8 @@ class Terrain : RulesetStatsObject() {
uniquesToCivilopediaTextLines(textList, leadingSeparator = null)
textList += FormattedLine()
textList += if (impassable) FormattedLine(Constants.impassable, color="#A00")
else FormattedLine("{Movement cost}: $movementCost")
if (impassable) textList += FormattedLine(Constants.impassable, color="#A00")
else if (movementCost > 0) textList += FormattedLine("{Movement cost}: $movementCost")
if (defenceBonus != 0f)
textList += FormattedLine("{Defence bonus}: ${(defenceBonus * 100).toInt()}%")

View File

@ -285,7 +285,7 @@ enum class UniqueParameterType(
/** Implemented by [Tile.matchesTerrainFilter][com.unciv.logic.map.tile.Tile.matchesTerrainFilter] */
TerrainFilter("terrainFilter", Constants.freshWaterFilter, null, "Terrain Filters") {
private val knownValues = setOf("All",
Constants.coastal, "River", "Open terrain", "Rough terrain", "Water resource",
Constants.coastal, Constants.river, "Open terrain", "Rough terrain", "Water resource",
"Foreign Land", "Foreign", "Friendly Land", "Friendly", "Enemy Land", "Enemy",
"Featureless", Constants.freshWaterFilter, "non-fresh water", "Natural Wonder",
"Impassable", "Land", "Water") +

View File

@ -541,6 +541,8 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
FreshWater(Constants.freshWater, UniqueTarget.Terrain),
RoughTerrain("Rough terrain", UniqueTarget.Terrain),
ExcludedFromMapEditor("Excluded from map editor", UniqueTarget.Terrain, UniqueTarget.Improvement, UniqueTarget.Resource, UniqueTarget.Nation, flags = UniqueFlag.setOfHiddenToUsers),
/////// Resource uniques
ResourceAmountOnTiles("Deposits in [tileFilter] tiles always provide [amount] resources", UniqueTarget.Resource),
CityStateOnlyResource("Can only be created by Mercantile City-States", UniqueTarget.Resource),

View File

@ -14,16 +14,17 @@ import com.unciv.models.ruleset.tile.Terrain
import com.unciv.models.ruleset.tile.TerrainType
import com.unciv.models.ruleset.tile.TileImprovement
import com.unciv.models.ruleset.tile.TileResource
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.translations.tr
import com.unciv.ui.audio.MusicMood
import com.unciv.ui.audio.MusicTrackChooserFlags
import com.unciv.ui.components.widgets.TabbedPager
import com.unciv.ui.components.extensions.center
import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.components.input.onClick
import com.unciv.ui.components.tilegroups.TileGroup
import com.unciv.ui.components.tilegroups.TileSetStrings
import com.unciv.ui.components.widgets.TabbedPager
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.screens.civilopediascreen.FormattedLine
@ -57,6 +58,7 @@ class MapEditorEditTerrainTab(
private fun allTerrains() = ruleset.terrains.values.asSequence()
.filter { it.type.isBaseTerrain }
.filterNot { it.hasUnique(UniqueType.ExcludedFromMapEditor, StateForConditionals.IgnoreConditionals) }
private fun getTerrains() = allTerrains()
.map { FormattedLine(it.name, it.name, "Terrain/${it.name}", size = 32) }
.asIterable()
@ -80,7 +82,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 features", eraserIcon, true) { tile ->
editTab.setBrush("Remove features", eraserIcon, pediaLink = "", isRemove = true) { tile ->
tile.removeTerrainFeatures()
}
} }).padBottom(0f).row()
@ -98,6 +100,7 @@ class MapEditorEditFeaturesTab(
private fun allowedFeatures() = ruleset.terrains.values.asSequence()
.filter { it.type == TerrainType.TerrainFeature }
.filterNot { it.hasUnique(UniqueType.ExcludedFromMapEditor, StateForConditionals.IgnoreConditionals) }
private fun getFeatures() = allowedFeatures()
.map { FormattedLine(it.name, it.name, "Terrain/${it.name}", size = 32) }
.asIterable()
@ -131,6 +134,7 @@ class MapEditorEditWondersTab(
private fun allowedWonders() = ruleset.terrains.values.asSequence()
.filter { it.type == TerrainType.NaturalWonder }
.filterNot { it.hasUnique(UniqueType.ExcludedFromMapEditor, StateForConditionals.IgnoreConditionals) }
private fun getWonders() = allowedWonders()
.map { FormattedLine(it.name, it.name, "Terrain/${it.name}", size = 32) }
.asIterable()
@ -154,7 +158,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, true) { tile ->
editTab.setBrush("Remove resource", eraserIcon, pediaLink = "", isRemove = true) { tile ->
tile.resource = null
tile.resourceAmount = 0
}
@ -175,7 +179,8 @@ class MapEditorEditResourcesTab(
}
private fun allowedResources() = ruleset.tileResources.values.asSequence()
.filter { !it.hasUnique(UniqueType.CityStateOnlyResource) }
.filterNot { it.hasUnique(UniqueType.CityStateOnlyResource) }
.filterNot { it.hasUnique(UniqueType.ExcludedFromMapEditor, StateForConditionals.IgnoreConditionals) }
private fun getResources(): Iterable<FormattedLine> = sequence {
var lastGroup = ResourceType.Bonus
for (resource in allowedResources()) {
@ -207,7 +212,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, true) { tile ->
editTab.setBrush("Remove improvement", eraserIcon, pediaLink = "", isRemove = true) { tile ->
tile.removeImprovement()
tile.removeRoad()
}
@ -230,9 +235,7 @@ class MapEditorEditImprovementsTab(
}
private fun allowedImprovements() = ruleset.tileImprovements.values.asSequence()
.filter { improvement ->
disallowImprovements.none { improvement.name.startsWith(it) }
}
.filterNot { it.hasUnique(UniqueType.ExcludedFromMapEditor, StateForConditionals.IgnoreConditionals) }
private fun getImprovements(): Iterable<FormattedLine> = sequence {
var lastGroup = 0
for (improvement in allowedImprovements()) {
@ -249,10 +252,6 @@ class MapEditorEditImprovementsTab(
override fun isDisabled() = allowedImprovements().none()
companion object {
//todo This should really be easier, the attributes should allow such a test in one go
private val disallowImprovements = listOf(
Constants.cityCenter, Constants.repair, Constants.remove, Constants.cancelImprovementOrder
)
private fun TileImprovement.group() = when {
RoadStatus.values().any { it.name == name } -> 2
"Great Improvement" in uniques -> 3
@ -277,48 +276,49 @@ class MapEditorEditStartsTab(
allowedNations().firstOrNull()?.let { addNations(it) }
}
private fun String.spectatorToAnyCiv() = if (this == Constants.spectator) "Any Civ" else this
private fun addNations(firstNation: Nation) {
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", eraserIcon, true) { tile ->
editTab.setBrush(BrushHandlerType.Direct, "Remove", eraserIcon, pediaLink = "", isRemove = true) { tile ->
tile.tileMap.removeStartingLocations(tile.position)
}
} }).padBottom(0f).row()
// Create the nation list with the spectator nation included
// Create the nation list with the spectator nation included, and shown/interpreted as "Any Civ" starting location.
// We use Nation/Spectator because it hasn't been used yet and we need an icon within the Nation.
val anyCiv = FormattedLine("Any Civ starting location", Constants.spectator, "Nation/Spectator", size = 24)
val nations = getNations().toList()
val nationsToAdd = ArrayList<FormattedLine>(1 + nations.count())
nationsToAdd.add((anyCiv)) // An Civ starting location should be first
nationsToAdd.addAll(nations)
add(
MarkupRenderer.render(
nationsToAdd,
iconDisplay = FormattedLine.IconDisplay.NoLink
) {
UncivGame.Current.musicController.chooseTrack(it, MusicMood.Theme, MusicTrackChooserFlags.setSpecific)
editTab.setBrush(BrushHandlerType.Direct, if (it ==Constants.spectator) "Any Civ" else it, "Nation/$it") { tile ->
// toggle the starting location here, note this allows
// both multiple locations per nation and multiple nations per tile
if (!tile.tileMap.addStartingLocation(it, tile))
tile.tileMap.removeStartingLocation(it, tile)
getNations(),
iconDisplay = FormattedLine.IconDisplay.NoLink
) {
UncivGame.Current.musicController.chooseTrack(it, MusicMood.Theme, MusicTrackChooserFlags.setSpecific)
val icon = "Nation/$it"
val pediaLink = if (it == Constants.spectator) "" else icon
editTab.setBrush(BrushHandlerType.Direct, it.spectatorToAnyCiv(), icon, pediaLink) { tile ->
// toggle the starting location here, note this allows
// both multiple locations per nation and multiple nations per tile
if (!tile.tileMap.addStartingLocation(it, tile))
tile.tileMap.removeStartingLocation(it, tile)
}
}
}).padTop(0f).row()
).padTop(0f).row()
}
private fun allowedNations() = ruleset.nations.values.asSequence()
.filter { it.name !in disallowNations && !it.hasUnique(UniqueType.CityStateDeprecated) }
.filterNot { it.hasUnique(UniqueType.ExcludedFromMapEditor) }
private fun getNations() = allowedNations()
.sortedWith(compareBy<Nation>{ it.isCityState }.thenBy(collator) { it.name.tr(hideIcons = true) })
.map { FormattedLine("[${it.name}] starting location", it.name, "Nation/${it.name}", size = 24) }
.asIterable()
.sortedWith(
compareBy<Nation> { !it.isSpectator }
.thenBy { it.isCityState }
.thenBy(collator) { it.name.tr(hideIcons = true) }
).map {
FormattedLine("[${it.name.spectatorToAnyCiv()}] starting location", link = it.name, icon = "Nation/${it.name}", size = 24)
}.asIterable()
override fun isDisabled() = allowedNations().none()
companion object {
private val disallowNations = setOf(Constants.spectator, Constants.barbarians)
}
}
@ -336,13 +336,15 @@ class MapEditorEditRiversTab(
?: ruleset.terrains.values.first()
init {
val pediaLink = "Terrain/River"
top()
defaults().pad(10f).left()
val removeLine = Table().apply {
add(getRemoveRiverIcon()).padRight(10f)
add("Remove rivers".toLabel(fontSize = 32))
onClick {
editTab.setBrush(BrushHandlerType.River,"Remove rivers", getRemoveRiverIcon()) { tile ->
editTab.setBrush(BrushHandlerType.River,"Remove rivers", getRemoveRiverIcon(), pediaLink) { tile ->
tile.hasBottomLeftRiver = false
tile.hasBottomRightRiver = false
tile.hasBottomRiver = false
@ -363,7 +365,7 @@ class MapEditorEditRiversTab(
onClick {
editTab.setBrush(BrushHandlerType.Direct,"Bottom left river", getTileGroupWithRivers(
RiverEdge.Left
)) { tile ->
), pediaLink) { tile ->
tile.hasBottomLeftRiver = !tile.hasBottomLeftRiver
}
}
@ -376,7 +378,7 @@ class MapEditorEditRiversTab(
onClick {
editTab.setBrush(BrushHandlerType.Direct,"Bottom river", getTileGroupWithRivers(
RiverEdge.Bottom
)) { tile ->
), pediaLink) { tile ->
tile.hasBottomRiver = !tile.hasBottomRiver
}
}
@ -389,7 +391,7 @@ class MapEditorEditRiversTab(
onClick {
editTab.setBrush(BrushHandlerType.Direct,"Bottom right river", getTileGroupWithRivers(
RiverEdge.Right
)) { tile ->
), pediaLink) { tile ->
tile.hasBottomRightRiver = !tile.hasBottomRightRiver
}
}
@ -405,6 +407,7 @@ class MapEditorEditRiversTab(
BrushHandlerType.RiverFromTo,
name = "Spawn river from/to",
icon = getTileGroupWithRivers(RiverEdge.All),
pediaLink = pediaLink,
applyAction = {} // Actual effect done via BrushHandlerType
)
}

View File

@ -3,6 +3,7 @@ package com.unciv.ui.screens.mapeditorscreen.tabs
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.Group
import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.Cell
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.logic.map.BFS
@ -12,15 +13,18 @@ import com.unciv.logic.map.mapgenerator.RiverGenerator
import com.unciv.logic.map.tile.Tile
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.translations.tr
import com.unciv.ui.components.widgets.TabbedPager
import com.unciv.ui.components.widgets.UncivSlider
import com.unciv.ui.components.extensions.addSeparator
import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.components.input.KeyCharAndCode
import com.unciv.ui.components.input.KeyboardBinding
import com.unciv.ui.components.input.keyShortcuts
import com.unciv.ui.components.input.onActivation
import com.unciv.ui.components.widgets.TabbedPager
import com.unciv.ui.components.widgets.UncivSlider
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.popups.ToastPopup
import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen
import com.unciv.ui.screens.civilopediascreen.FormattedLine
import com.unciv.ui.screens.mapeditorscreen.MapEditorScreen
import com.unciv.ui.screens.mapeditorscreen.TileInfoNormalizer
@ -136,27 +140,43 @@ class MapEditorEditTab(
private fun selectPage(index: Int) = subTabs.selectPage(index)
fun setBrush(name: String, icon: String, isRemove: Boolean = false, applyAction: (Tile)->Unit) {
private fun linkCivilopedia(brushActor: Actor, link: String) {
if (link.isEmpty()) return
brushActor.touchable = Touchable.enabled
// As so often, doing the binding separately to avoid the tooltip
brushActor.onActivation {
editorScreen.game.pushScreen(CivilopediaScreen(ruleset, link = link))
}
brushActor.keyShortcuts.add(KeyboardBinding.Civilopedia)
}
// "Normal" setBrush overload, using named RulesetObject icon
fun setBrush(name: String, icon: String, pediaLink: String = icon, isRemove: Boolean = false, applyAction: (Tile)->Unit) {
brushHandlerType = BrushHandlerType.Tile
brushCell.setActor(FormattedLine(name, icon = icon, iconCrossed = isRemove).render(0f))
val brushActor = FormattedLine(name, icon = icon, iconCrossed = isRemove).render(0f)
linkCivilopedia(brushActor, pediaLink)
brushCell.setActor(brushActor)
brushAction = applyAction
}
private fun setBrush(name: String, icon: Actor, applyAction: (Tile)->Unit) {
// Helper overload for brushes using icons not existing as RulesetObject
private fun setBrush(name: String, icon: Actor, pediaLink: String, applyAction: (Tile)->Unit) {
brushHandlerType = BrushHandlerType.Tile
val line = Table().apply {
add(icon).padRight(10f)
add(name.toLabel())
}
linkCivilopedia(line, pediaLink)
brushCell.setActor(line)
brushAction = applyAction
}
fun setBrush(handlerType: BrushHandlerType, name: String, icon: String,
isRemove: Boolean = false, applyAction: (Tile)->Unit) {
setBrush(name, icon, isRemove, applyAction)
// This overload is used by Roads and Starting locations
fun setBrush(handlerType: BrushHandlerType, name: String, icon: String, pediaLink: String = icon, isRemove: Boolean = false, applyAction: (Tile)->Unit) {
setBrush(name, icon, pediaLink, isRemove, applyAction)
brushHandlerType = handlerType
}
fun setBrush(handlerType: BrushHandlerType, name: String, icon: Actor, applyAction: (Tile)->Unit) {
setBrush(name, icon, applyAction)
// This overload is used by Rivers
fun setBrush(handlerType: BrushHandlerType, name: String, icon: Actor, pediaLink: String, applyAction: (Tile)->Unit) {
setBrush(name, icon, pediaLink, applyAction)
brushHandlerType = handlerType
}

View File

@ -899,6 +899,9 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
??? example "Units ignore terrain costs when moving into any tile with Hills"
Applicable to: Nation
??? example "Excluded from map editor"
Applicable to: Nation, Terrain, Improvement, Resource
??? example "Will not be displayed in Civilopedia"
Applicable to: Nation, Tech, Policy, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins