Unique docs writer makeover (#6516)

* Comments to 'link' implementation (matchFilter) to UniqueParameterType

* Comments to 'link' UniqueParameterType to implementation (matchFilter)

* Fix two mistakes in UniqueParameterType

* Make "and" filter logic for MapUnit and BaseUnit modular, fix mistake

* Make UnitMovementMemory immutable and without lateinit

* Keep UniqueType comment promise that instances can override parameter types

* Reorg UniqueDocsWriter so UniqueParameterType-specific text is moved to the enum

Also some minor improvements - e.g. abbreviations sorted & consistent punctuation, less memory allocations, a/b/c type params get an example,...

* New abilities for UniqueDocsWriter concerning UniqueTarget and inheritsFrom (initially deactivated)

* Actually implement the wish for distinction between absolute and relative amounts

* Change UniqueParameterType.UnitName.docExample to Xander's choice
This commit is contained in:
SomeTroglodyte
2022-04-24 21:47:19 +02:00
committed by GitHub
parent 34105efdda
commit 23c23e5566
16 changed files with 535 additions and 481 deletions

View File

@ -1,103 +1,92 @@
package com.unciv.app.desktop
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.unique.UniqueParameterType
import com.unciv.models.ruleset.unique.UniqueTarget
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.translations.fillPlaceholders
import java.io.File
class UniqueDocsWriter {
companion object {
/**
* Switch from each Unique shown once under one UniqueTarget heading chosen from targetTypes (`true`)
* to showing each Unique repeatedly under each UniqueTarget heading it applies to (`false`).
*/
private const val showUniqueOnOneTarget = true
/** Switch **on** the display of _inherited_ UniqueTargets in "Applicable to:" */
private const val showInheritedTargets = false
private fun UniqueTarget.allTargets(): Sequence<UniqueTarget> = sequence {
if (showInheritedTargets && inheritsFrom != null) yieldAll(inheritsFrom!!.allTargets())
yield(this@allTargets)
}
private fun UniqueType.allTargets(): Sequence<UniqueTarget> =
targetTypes.asSequence().flatMap { it.allTargets() }.distinct()
private fun UniqueTarget.allUniqueTypes(): Sequence<UniqueType> =
UniqueType.values().asSequence().filter {
this in it.targetTypes
}
}
fun toLink(string: String): String {
return "#" + string.split(' ').joinToString("-") { it.lowercase() }
}
fun write() {
val lines = ArrayList<String>()
val targetTypesToUniques = UniqueType.values().groupBy { it.targetTypes.minOrNull()!! }
.toSortedMap()
// This will output each unique only once, even if it has several targets.
// Each is grouped under the UniqueTarget is is allowed for with the lowest enum ordinal.
// UniqueTarget.inheritsFrom is _not_ resolved for this.
// The UniqueType are shown in enum order within their group, and groups are ordered
// by their UniqueTarget.ordinal as well - source code order.
val targetTypesToUniques: Map<UniqueTarget, List<UniqueType>> =
if(showUniqueOnOneTarget)
UniqueType.values().asSequence()
.groupBy { it.targetTypes.minOrNull()!! }
.toSortedMap()
else
// if, on the other hand, we wish to list every UniqueType with multiple targets under
// _each_ of the groups it is applicable to, then this might do:
UniqueTarget.values().asSequence().associateWith { target ->
target.allTargets().flatMap { inheritedTarget ->
inheritedTarget.allUniqueTypes()
}.distinct().toList()
}
fun replaceExamples(text:String):String {
return text
.replace("[amount]", "[20]")
.replace("[combatantFilter]", "[City]")
.replace("[mapUnitFilter]", "[Wounded]")
.replace("[baseUnitFilter]", "[Melee]")
.replace("[unit]","[Musketman]")
.replace("[great person]", "[Great Scientist]")
.replace("[stats]", "[+1 Gold, +2 Production]")
.replace("[stat]", "[Culture]")
.replace("[plunderableStat]", "[Gold]")
.replace("[cityFilter]", "[in all cities]")
.replace("[buildingName]", "[Library]")
.replace("[buildingFilter]", "[Culture]")
.replace("[constructionFilter]", "[Spaceship Part]")
.replace("[terrainFilter]", "[Forest]")
.replace("[tileFilter]", "[Farm]")
.replace("[simpleTerrain]", "[Elevated]")
.replace("[baseTerrain]", "[Grassland]")
.replace("[regionType]", "[Hybrid]")
.replace("[terrainQuality]","[Undesirable]")
.replace("[promotion]","[Shock I]")
.replace("[era]", "[Ancient era]")
.replace("[improvementName]", "[Trading Post]")
.replace("[improvementFilter]", "[All Road]")
.replace("[resource]", "[Iron]")
.replace("[beliefType]", "[Follower]")
.replace("[belief]","[God of War]")
.replace("[foundingOrEnhancing]", "[founding]")
.replace("[tech]", "[Agriculture]")
.replace("[specialist]","[Merchant]")
.replace("[policy]", "[Oligarchy]")
.replace("[victoryType]", "[Domination]")
.replace("[costOrStrength]", "[Cost]")
}
lines += "# Uniques\n" +
"Simple unique parameters are explained by mouseover. Complex parameters are explained in [Unique parameter types](../unique parameters)"
val capacity = 25 + targetTypesToUniques.size + UniqueType.values().size * (if (showUniqueOnOneTarget) 3 else 16)
val lines = ArrayList<String>(capacity)
lines += "# Uniques"
lines += "Simple unique parameters are explained by mouseover. Complex parameters are explained in [Unique parameter types](../unique parameters)"
val deprecatedUniques = ArrayList<UniqueType>()
for (targetType in targetTypesToUniques) {
lines += "## " + targetType.key.name + " uniques"
for (uniqueType in targetType.value) {
for ((targetType, uniqueTypes) in targetTypesToUniques) {
if (uniqueTypes.isEmpty()) continue
lines += "## " + targetType.name + " uniques"
for (uniqueType in uniqueTypes) {
if (uniqueType.getDeprecationAnnotation() != null) continue
val uniqueText = if (targetType.key == UniqueTarget.Conditional) "&lt;${uniqueType.text}&gt;"
val uniqueText = if (targetType == UniqueTarget.Conditional) "&lt;${uniqueType.text}&gt;"
else uniqueType.text
lines += "??? example \"$uniqueText\"" // collapsable material mkdocs block, see https://squidfunk.github.io/mkdocs-material/reference/admonitions/?h=%3F%3F%3F#collapsible-blocks
if (uniqueType.text.contains('['))
lines += "\tExample: \"${replaceExamples(uniqueText)}\"\n"
lines += "\tApplicable to: " + uniqueType.targetTypes.sorted().joinToString()
if (uniqueType.parameterTypeMap.isNotEmpty()) {
// This one will give examples for _each_ filter in a "tileFilter/specialist/buildingFilter" kind of parameter e.g. "Farm/Merchant/Library":
// `val paramExamples = uniqueType.parameterTypeMap.map { it.joinToString("/") { pt -> pt.docExample } }.toTypedArray()`
// Might confuse modders to think "/" can go into the _actual_ unique and mean "or", so better show just one ("Farm" in the example above):
val paramExamples = uniqueType.parameterTypeMap.map { it.first().docExample }.toTypedArray()
lines += "\tExample: \"${uniqueText.fillPlaceholders(*paramExamples)}\"\n"
}
lines += "\tApplicable to: " + uniqueType.allTargets().sorted().joinToString()
lines += ""
}
}
// Abbreviations, for adding short unique parameter help - see https://squidfunk.github.io/mkdocs-material/reference/abbreviations/
// Abbreviations, for adding short unique parameter help - see https://squidfunk.github.io/mkdocs-material/reference/abbreviations/
lines += ""
lines += "*[amount]: This indicates a whole number, possibly with a + or - sign, such as `2`, `+13`, or `-3`."
lines += "*[baseTerrain]: The name of any terrain that is a base terrain according to the json file."
lines += "*[action]: An action that a unit can preform. Currently, there are only two actions part of this: 'Spread Religion' and 'Remove Foreign religions from your own cities'"
lines += "*[belief]: The name of any belief"
lines += "*[beliefType]: 'Pantheon', 'Follower', 'Founder' or 'Enhancer'."
lines += "*[victoryType]: The name of any victory type: 'Neutral', 'Cultural', 'Diplomatic', 'Domination', 'Scientific', 'Time'"
lines += "*[tech]: The name of any tech"
lines += "*[resource]: The name of any resource"
lines += "*[specialist]: The name of any specialist"
lines += "*[promotion]: The name of any promotion"
lines += "*[policy]: The name of any policy"
lines += "*[improvementName]: The name of any improvement"
lines += "*[buildingName]: The name of any building"
lines += "*[era]: The name of any era"
lines += "*[constructionFilter]: A filter for used when testing the current construction of a city. All values of `baseUnitFilter` and `buildingFilter` are allowed."
lines += "*[foundingOrEnhancing]: `founding` or `enhancing`"
lines += "*[costOrStrength]: `Cost` or `Strength`"
lines += "*[combatantFilter]: This indicates a combatant, which can either be a unit or a city (when bombarding). Must either be `City` or a `mapUnitFilter`."
lines += "*[plunderableStat]: All the following stats can be plundered: `Gold`, `Science`, `Culture`, `Faith`"
lines += "*[tileFilter]: Anything that can be used either in an improvementFilter or in a tileFilter can be used here"
lines += "*[stat]: This is one of the 7 major stats in the game - `Gold`, `Science`, `Production`, `Food`, `Happiness`, `Culture` and `Faith`. Note that the stat names need to be capitalized!"
lines += "*[stats]: For example: `+2 Production, +3 Food`. Note that the stat names need to be capitalized!"
// order irrelevant for rendered wiki, but could potentially reduce source control differences
for (paramType in UniqueParameterType.values().asSequence().sortedBy { it.parameterName }) {
if (paramType.docDescription == null) continue
val punctuation = if (paramType.docDescription!!.last().category == '.'.category) "" else "."
lines += "*[${paramType.parameterName}]: ${paramType.docDescription}$punctuation"
}
File("../../docs/Modders/uniques.md").writeText(lines.joinToString("\n"))
}
}
}