Spruced up Civilopedia - phase 3 - Interface, flavour text, new Tutorial (#4334)

This commit is contained in:
SomeTroglodyte 2021-07-02 18:22:38 +02:00 committed by GitHub
parent 0776d9b36e
commit 8fdb4f413e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 278 additions and 23 deletions

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

View File

@ -100,14 +100,20 @@
"turnsToBuild": 4,
"techRequired": "The Wheel",
"uniques": ["Can be built outside your borders", "Costs [1] gold per turn when in your territory"],
"shortcutKey": "R"
"shortcutKey": "R",
"civilopediaText": [
{text:"Reduces movement cost to ½ if the other tile also has a Road or Railroad"},
{text:"Reduces movement cost to ⅓ with Machinery",link:"Technology/Machinery"},
{text:"Requires Engineering to bridge rivers",link:"Technology/Engineering"}
]
},
{
"name": "Railroad",
"turnsToBuild": 4,
"techRequired": "Railroad",
"uniques": ["Can be built outside your borders", "Costs [2] gold per turn when in your territory"],
"shortcutKey": "R"
"shortcutKey": "R",
"civilopediaText": [{text:"Reduces movement cost to ⅒ if the other tile also has a Railroad"}]
},
// Removals
@ -117,7 +123,8 @@
"terrainsCanBeBuiltOn": ["Forest"],
"techRequired": "Mining",
"uniques": ["Can be built outside your borders"],
"shortcutKey": "X"
"shortcutKey": "X",
"civilopediaText": [{text:"Provides a one-time Production bonus depending on distance to the closest city once finished"}]
},
{
"name": "Remove Jungle",
@ -184,7 +191,8 @@
},
{
"name": "Citadel",
"uniques": ["Gives a defensive bonus of [100]%", "Deal 30 damage to adjacent enemy units", "Great Improvement", "Can be built just outside your borders"]
"uniques": ["Gives a defensive bonus of [100]%", "Deal 30 damage to adjacent enemy units", "Great Improvement", "Can be built just outside your borders"],
"civilopediaText": [{text:"Constructing it will take over the tiles around it and assign them to your closest city"}]
},
//Civilization unique improvements
@ -210,8 +218,12 @@
"shortcutKey": "F"
},
{ "name": "Ancient ruins", "uniques": ["Unpillagable"] },
{ "name": "City ruins", "uniques": ["Unpillagable"] },
{ "name": "City center", "uniques": ["Unpillagable"] },
{ "name": "Barbarian encampment", "uniques": ["Unpillagable"] }
{ "name": "Ancient ruins", "uniques": ["Unpillagable"],
"civilopediaText": [{text:"Ancient ruins provide a one-time random bonus when explored"}] },
{ "name": "City ruins", "uniques": ["Unpillagable"],
"civilopediaText": [{text:"A bleak reminder of the destruction wreaked by War"}] },
{ "name": "City center", "uniques": ["Unpillagable"],
"civilopediaText": [{text:"Marks the center of a city"},{text:"Appearance changes with the technological era of the owning civilization"}] },
{ "name": "Barbarian encampment", "uniques": ["Unpillagable"],
"civilopediaText": [{text:"Home to uncivilized barbarians, will spawn a hostile unit from time to time"}] }
]

View File

@ -132,5 +132,41 @@
"On the world screen the hotkeys are as follows:", "Space or 'N' - Next unit or turn\n'E' - Empire overview (last viewed page)\n'+', '-' - Zoom in / out\nHome - center on capital",
"F1 - Open Civilopedia\nF2 - Empire overview Trades\nF3 - Empire overview Units\nF4 - Empire overview Diplomacy\nF5 - Social policies\nF6 - Technologies\nF7 - Empire overview Cities\nF8 - Victory Progress\nF9 - Empire overview Stats\nF10 - Empire overview Resources\nF11 - Quicksave\nF12 - Quickload",
"Ctrl-R - Toggle tile resource display\nCtrl-Y - Toggle tile yield display\nCtrl-O - Game options\nCtrl-S - Save game\nCtrl-L - Load game"
],
"World_Screen": [
"",
"This is where you spend most of your time playing Unciv. See the world, control your units, access other screens from here.",
"",
"①: The menu button - civilopedia, save, load, options...",
"②: The player/nation whose turn it is - click for diplomacy overview.",
"③: The Technology Button - shows the tech tree which allows viewing or researching technologies.",
"④: The Social Policies Button - shows enacted and selectable policies, and with enough culture points you can enact new ones.",
"⑤: The Diplomacy Button - shows the diplomacy manager where you can talk to other civilizations.",
"⑥: Unit Action Buttons - while a unit is selected its possible actions appear here.",
"⑦: The unit/city info pane - shows information about a selected unit or city.",
"⑧: The name (and unit icon) of the selected unit or city, with current health if wounded.",
"⑨: The arrow buttons allow jumping to the next/previous unit.",
"⑩: For a selected unit, its promotions appear here, and clicking leads to the promotions screen for that unit.",
"⑪: Remaining/per turn movement points, strength and experience / XP needed for promotion. For cities, you get its combat strength.",
"⑫: This button closes the selected unit/city info pane.",
"⑬: This pane appears when you order a uint to attack an enemy. On top are attacker and defender with their respective base strengths.",
"⑭: Below that are strength boni or mali and health bars projecting before / after the attack.",
"⑮: The Attack Button - let blood flow!",
"⑯: The minimap shows an overview over the world, with known cities, terrain and fog of war. Clicking will position the main map.",
"⑰: To the side of the minimap are display feature toggling buttons - tile yield, worked indicator, show/hide resources. These mirror setting on the options screen and are hidden if you deactivate the minimap.",
"⑱: Tile information for the selected hex - current or potential yield, terrain, effects, present units, city located there and such.",
"⑲: Notifications - what happened during the last 'next turn' phase. Some are clickable to show a relevant place on the map, some even show several when you click repeatedly.",
"⑳: The Next Turn Button - unless there are things to do, in which case the label changes to 'next unit', 'pick policy' and so on.",
"ⓐ: The overview button leads to the empire overview screen with various tabs (the last one viewed is remembered) holding vital information about the state of your civilization in world.",
"ⓑ: The culture icon shows accumulated culture and culture needed for the next policy - in this case, the exclamation mark tells us a next policy can be enacted. Clicking is another way to the policies manager.",
"ⓒ: Your known strategic resources are displayed here with the available (usage already deducted) number - click to go to the resources overview screen.",
"ⓓ: Happiness/unhappiness balance and either golden age with turns left or accumulated happines with amount needed for a golden age is shown next to the smiley. Clicking also leads to the resources overview screen as luxury resources are a way to improve happiness.",
"ⓔ: The science icon shows the number of science points produced per turn. Clicking leads to the technology tree.",
"ⓕ: Number of turns played with translation into calendar years. Click to see the victory overview.",
"ⓖ: The number of gold coins in your treasury and income. Clicks lead to the Stats overview screen.",
"ⓧ: In the center of all this - the world map! Here, the \"X\" marks a spot outside the map. Yes, unless the wrap option was used, Unciv worlds are flat. Don't worry, your ships won't fall off the edge.",
"ⓨ: By the way, here's how an empire border looks like - it's in the national colours of the nation owning the territory.",
"ⓩ: And this is the red targeting circle that led to the attack pane back under ⑬.",
"What you don't see: The phone/tablet's back button will pop the question whether you wish to leave Unciv and go back to Real Life. On desktop versions, you can use the ESC key.",
]
}

View File

@ -34,7 +34,8 @@ enum class Tutorial(val value: String, val isCivilopedia: Boolean = !value.start
CityExpansion("City_Expansion"),
GreatPeople("Great_People"),
RemovingTerrainFeatures("Removing_Terrain_Features"),
Keyboard("Keyboard")
Keyboard("Keyboard"),
WorldScreen("World_Screen"),
;
companion object {

View File

@ -5,10 +5,12 @@ import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.Unique
import com.unciv.models.stats.NamedStats
import com.unciv.models.translations.tr
import com.unciv.ui.civilopedia.FormattedLine
import com.unciv.ui.civilopedia.ICivilopediaText
import java.util.*
import kotlin.math.roundToInt
class TileImprovement : NamedStats() {
class TileImprovement : NamedStats(), ICivilopediaText {
var terrainsCanBeBuiltOn: Collection<String> = ArrayList()
var techRequired: String? = null
@ -18,6 +20,9 @@ class TileImprovement : NamedStats() {
val shortcutKey: Char? = null
val turnsToBuild: Int = 0 // This is the base cost.
override var civilopediaText = listOf<FormattedLine>()
fun getTurnsToBuild(civInfo: CivilizationInfo): Int {
var realTurnsToBuild = turnsToBuild.toFloat() * civInfo.gameInfo.gameParameters.gameSpeed.modifier
for (unique in civInfo.getMatchingUniques("-[]% tile improvement construction time")) {

View File

@ -28,6 +28,7 @@ class CivilopediaScreen(
* @param name From [Ruleset] object [INamed.name]
* @param description Multiline text
* @param image Icon for button
* @param flavour [CivilopediaText]
* @param y Y coordinate for scrolling to
* @param height Cell height
*/
@ -35,10 +36,11 @@ class CivilopediaScreen(
val name: String,
val description: String,
val image: Actor? = null,
val flavour: ICivilopediaText? = null,
val y: Float = 0f, // coordinates of button cell used to scroll to entry
val height: Float = 0f
) {
fun withCoordinates(y: Float, height: Float) = CivilopediaEntry(name, description, image, y, height)
fun withCoordinates(y: Float, height: Float) = CivilopediaEntry(name, description, image, flavour, y, height)
}
private val categoryToEntries = LinkedHashMap<CivilopediaCategories, Collection<CivilopediaEntry>>()
@ -48,6 +50,7 @@ class CivilopediaScreen(
private val entrySelectTable = Table().apply { defaults().pad(6f).left() }
private val entrySelectScroll: ScrollPane
private val descriptionLabel = "".toLabel()
private val flavourTable = Table()
private var currentCategory: CivilopediaCategories = CivilopediaCategories.Tutorial
private var currentEntry: String = ""
@ -84,6 +87,7 @@ class CivilopediaScreen(
entrySelectTable.clear()
entryIndex.clear()
descriptionLabel.setText("")
flavourTable.clear()
for (button in categoryToButtons.values) button.color = Color.WHITE
if (category !in categoryToButtons) return // defense against being passed a bad selector
@ -134,7 +138,22 @@ class CivilopediaScreen(
}
private fun selectEntry(entry: CivilopediaEntry) {
currentEntry = entry.name
descriptionLabel.setText(entry.description)
if(entry.flavour != null && entry.flavour.replacesCivilopediaDescription()) {
descriptionLabel.setText("")
descriptionLabel.isVisible = false
} else {
descriptionLabel.setText(entry.description)
descriptionLabel.isVisible = true
}
flavourTable.clear()
if (entry.flavour != null) {
flavourTable.isVisible = true
flavourTable.add(
entry.flavour.assembleCivilopediaText(ruleset)
.renderCivilopediaText(stage.width * 0.5f) { selectLink(it) })
} else {
flavourTable.isVisible = false
}
entrySelectTable.children.forEach {
it.color = if (it.name == entry.name) Color.BLUE else Color.WHITE
}
@ -150,7 +169,8 @@ class CivilopediaScreen(
CivilopediaEntry(
it.name,
it.getDescription(false, null, ruleset),
CivilopediaCategories.Building.getImage?.invoke(it.name, imageSize)
CivilopediaCategories.Building.getImage?.invoke(it.name, imageSize),
(it as? ICivilopediaText).takeUnless { ct -> ct==null || ct.isCivilopediaTextEmpty() }
)
}
categoryToEntries[CivilopediaCategories.Wonder] = ruleset.buildings.values
@ -159,7 +179,8 @@ class CivilopediaScreen(
CivilopediaEntry(
it.name,
it.getDescription(false, null, ruleset),
CivilopediaCategories.Wonder.getImage?.invoke(it.name, imageSize)
CivilopediaCategories.Wonder.getImage?.invoke(it.name, imageSize),
(it as? ICivilopediaText).takeUnless { ct -> ct==null || ct.isCivilopediaTextEmpty() }
)
}
categoryToEntries[CivilopediaCategories.Resource] = ruleset.tileResources.values
@ -167,7 +188,8 @@ class CivilopediaScreen(
CivilopediaEntry(
it.name,
it.getDescription(ruleset),
CivilopediaCategories.Resource.getImage?.invoke(it.name, imageSize)
CivilopediaCategories.Resource.getImage?.invoke(it.name, imageSize),
(it as? ICivilopediaText).takeUnless { ct -> ct==null || ct.isCivilopediaTextEmpty() }
)
}
categoryToEntries[CivilopediaCategories.Terrain] = ruleset.terrains.values
@ -175,7 +197,8 @@ class CivilopediaScreen(
CivilopediaEntry(
it.name,
it.getDescription(ruleset),
CivilopediaCategories.Terrain.getImage?.invoke(it.name, imageSize)
CivilopediaCategories.Terrain.getImage?.invoke(it.name, imageSize),
(it as? ICivilopediaText).takeUnless { ct -> ct==null || ct.isCivilopediaTextEmpty() }
)
}
categoryToEntries[CivilopediaCategories.Improvement] = ruleset.tileImprovements.values
@ -183,7 +206,8 @@ class CivilopediaScreen(
CivilopediaEntry(
it.name,
it.getDescription(ruleset, false),
CivilopediaCategories.Improvement.getImage?.invoke(it.name, imageSize)
CivilopediaCategories.Improvement.getImage?.invoke(it.name, imageSize),
(it as? ICivilopediaText).takeUnless { ct -> ct==null || ct.isCivilopediaTextEmpty() }
)
}
categoryToEntries[CivilopediaCategories.Unit] = ruleset.units.values
@ -192,7 +216,8 @@ class CivilopediaScreen(
CivilopediaEntry(
it.name,
it.getDescription(false),
CivilopediaCategories.Unit.getImage?.invoke(it.name, imageSize)
CivilopediaCategories.Unit.getImage?.invoke(it.name, imageSize),
(it as? ICivilopediaText).takeUnless { ct -> ct==null || ct.isCivilopediaTextEmpty() }
)
}
categoryToEntries[CivilopediaCategories.Nation] = ruleset.nations.values
@ -201,7 +226,8 @@ class CivilopediaScreen(
CivilopediaEntry(
it.name,
it.getUniqueString(ruleset, false),
CivilopediaCategories.Nation.getImage?.invoke(it.name, imageSize)
CivilopediaCategories.Nation.getImage?.invoke(it.name, imageSize),
(it as? ICivilopediaText).takeUnless { ct -> ct==null || ct.isCivilopediaTextEmpty() }
)
}
categoryToEntries[CivilopediaCategories.Technology] = ruleset.technologies.values
@ -209,7 +235,8 @@ class CivilopediaScreen(
CivilopediaEntry(
it.name,
it.getDescription(ruleset),
CivilopediaCategories.Technology.getImage?.invoke(it.name, imageSize)
CivilopediaCategories.Technology.getImage?.invoke(it.name, imageSize),
(it as? ICivilopediaText).takeUnless { ct -> ct==null || ct.isCivilopediaTextEmpty() }
)
}
categoryToEntries[CivilopediaCategories.Promotion] = ruleset.unitPromotions.values
@ -217,7 +244,8 @@ class CivilopediaScreen(
CivilopediaEntry(
it.name,
it.getDescription(ruleset.unitPromotions.values, true, ruleset),
CivilopediaCategories.Promotion.getImage?.invoke(it.name, imageSize)
CivilopediaCategories.Promotion.getImage?.invoke(it.name, imageSize),
(it as? ICivilopediaText).takeUnless { ct -> ct==null || ct.isCivilopediaTextEmpty() }
)
}
@ -227,6 +255,9 @@ class CivilopediaScreen(
it.key.replace("_", " "),
it.value.joinToString("\n\n") { line -> line.tr() },
// CivilopediaCategories.Tutorial.getImage?.invoke(it.name, imageSize)
flavour = SimpleCivilopediaText(
sequenceOf(FormattedLine(extraImage = it.key)),
it.value.asSequence(), true)
)
}
@ -279,6 +310,7 @@ class CivilopediaScreen(
entrySelectTable.top()
entrySelectScroll.setOverscroll(false, false)
val descriptionTable = Table()
descriptionTable.add(flavourTable).row()
descriptionLabel.wrap = true // requires explicit cell width!
descriptionTable.add(descriptionLabel).width(stage.width * 0.5f).padTop(10f).row()
val entrySplitPane = SplitPane(entrySelectScroll, ScrollPane(descriptionTable), false, skin)

View File

@ -1,9 +1,12 @@
package com.unciv.ui.civilopedia
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.stats.INamed
import com.unciv.ui.utils.*
@ -14,10 +17,19 @@ import com.unciv.ui.utils.*
* @param text Text to display.
* @param link Create link: Line gets a 'Link' icon and is linked to either
* an Unciv object (format `category/entryname`) or an external URL.
* @param extraImage Display an Image instead of text. Can be a path as understood by
* [ImageGetter.getImage] or the name of a png or jpg in ExtraImages.
* @param imageSize Width of the [extraImage], height is calculated preserving aspect ratio. Defaults to available width.
* @param header Header level. 1 means double text size and decreases from there.
* @param separator Renders a separator line instead of text.
*/
class FormattedLine (
val text: String = "",
val link: String = "",
val extraImage: String = "",
val imageSize: Float = Float.NaN,
val header: Int = 0,
val separator: Boolean = false,
) {
// Note: This gets directly deserialized by Json - please keep all attributes meant to be read
// from json in the primary constructor parameters above. Everything else should be a fun(),
@ -46,7 +58,17 @@ class FormattedLine (
}
/** Returns true if this formatted line will not display anything */
fun isEmpty(): Boolean = text.isEmpty() && link.isEmpty()
fun isEmpty(): Boolean = text.isEmpty() && extraImage.isEmpty() && link.isEmpty() && !separator
/** Constants used by [FormattedLine]
* @property defaultSize Mirrors default text size as defined elsewhere
* @property headerSizes Array of text sizes to translate the [header] attribute
*/
companion object {
const val defaultSize = 18
val headerSizes = arrayOf(defaultSize,36,32,27,24,21,15,12,9) // pretty arbitrary, yes
val defaultColor: Color = Color.WHITE
}
/** Extension: determines if a [String] looks like a link understood by the OS */
private fun String.hasProtocol() = startsWith("http://") || startsWith("https://") || startsWith("mailto:")
@ -56,9 +78,35 @@ class FormattedLine (
* @param labelWidth Total width to render into, needed to support wrap on Labels.
*/
fun render(labelWidth: Float): Actor {
if (extraImage.isNotEmpty()) {
val table = Table(CameraStageBaseScreen.skin)
try {
val image = when {
ImageGetter.imageExists(extraImage) ->
ImageGetter.getImage(extraImage)
Gdx.files.internal("ExtraImages/$extraImage.png").exists() ->
ImageGetter.getExternalImage("$extraImage.png")
Gdx.files.internal("ExtraImages/$extraImage.jpg").exists() ->
ImageGetter.getExternalImage("$extraImage.jpg")
else -> return table
}
val width = if (imageSize.isNaN()) labelWidth else imageSize
val height = width * image.height / image.width
table.add(image).size(width, height)
} catch (exception: Exception) {
println ("${exception.message}: ${exception.cause?.message}")
}
return table
}
val fontSize = when {
header in headerSizes.indices -> headerSizes[header]
else -> defaultSize
}
val table = Table(CameraStageBaseScreen.skin)
if (textToDisplay.isNotEmpty()) {
val label = textToDisplay.toLabel()
val label = if (fontSize == defaultSize) textToDisplay.toLabel()
else textToDisplay.toLabel(defaultColor,fontSize)
label.wrap = labelWidth > 0f
if (labelWidth == 0f)
table.add(label)
@ -67,12 +115,26 @@ class FormattedLine (
}
return table
}
// Debug visualization only
override fun toString(): String {
return when {
isEmpty() -> "(empty)"
separator -> "(separator)"
extraImage.isNotEmpty() -> "(extraImage='$extraImage')"
header > 0 -> "(header=$header)'$text'"
linkType == LinkType.None -> "'$text'"
else -> "'$text'->$link"
}
}
}
/** Makes [renderer][render] available outside [ICivilopediaText] */
object MarkupRenderer {
private const val emptyLineHeight = 10f
private const val defaultPadding = 2.5f
private const val separatorTopPadding = 5f
private const val separatorBottomPadding = 15f
/**
* Build a Gdx [Table] showing [formatted][FormattedLine] [content][lines].
@ -94,6 +156,10 @@ object MarkupRenderer {
table.add().padTop(emptyLineHeight).row()
continue
}
if (line.separator) {
table.addSeparator().pad(separatorTopPadding, 0f, separatorBottomPadding, 0f)
continue
}
val actor = line.render(labelWidth)
if (line.linkType == FormattedLine.LinkType.Internal && linkAction != null)
actor.onClick {
@ -111,3 +177,106 @@ object MarkupRenderer {
return table.apply { pack() }
}
}
/** Storage class for interface [ICivilopediaText] for use as base class */
open class CivilopediaText : ICivilopediaText {
override var civilopediaText = listOf<FormattedLine>()
}
/** Storage class for instantiation of the simplest form containing only the lines collection */
class SimpleCivilopediaText(lines: List<FormattedLine>, val isComplete: Boolean = false) : CivilopediaText() {
init {
civilopediaText = lines
}
override fun hasCivilopediaTextLines() = true
override fun replacesCivilopediaDescription() = isComplete
constructor(strings: Sequence<String>, isComplete: Boolean = false) : this(
strings.map { FormattedLine(it) }.toList(), isComplete)
constructor(first: Sequence<FormattedLine>, strings: Sequence<String>, isComplete: Boolean = false) : this(
(first + strings.map { FormattedLine(it) }).toList(), isComplete)
}
/** Addon common to most ruleset game objects managing civilopedia display
*
* ### Usage:
* 1. Let [Ruleset] object implement this (e.g. by inheriting class [CivilopediaText] or adding var [civilopediaText] itself)
* 2. Add `"civilopediaText": ["",],` in the json for these objects
* 3. Optionally override [getCivilopediaTextHeader] to supply a header line
* 4. Optionally override [getCivilopediaTextLines] to supply automatic stuff like tech prerequisites, uniques, etc.
* 4. Optionally override [assembleCivilopediaText] to handle assembly of the final set of lines yourself.
*/
interface ICivilopediaText {
/** List of strings supporting simple [formatting rules][FormattedLine] that [CivilopediaScreen] can render.
* May later be merged with automatic lines generated by the deriving class
* through overridden [getCivilopediaTextHeader] and/or [getCivilopediaTextLines] methods.
*
*/
var civilopediaText: List<FormattedLine>
/** Generate header line from object metadata.
* Default implementation will pull [INamed.name] and render it in 150% normal font size.
* @return A [FormattedLine] that will be inserted on top
*/
fun getCivilopediaTextHeader(): FormattedLine? =
if (this is INamed) FormattedLine(name, header = 2)
else null
/** Generate automatic lines from object metadata.
*
* Default implementation is empty - no need to call super in overrides.
*
* @param ruleset The current ruleset for the Civilopedia viewer
* @return A list of [FormattedLine]s that will be inserted before
* the first line of [civilopediaText] having a [link][FormattedLine.link]
*/
fun getCivilopediaTextLines(ruleset: Ruleset): List<FormattedLine> = listOf()
/** Override this and return true to tell the Civilopedia that the legacy description is no longer needed */
fun replacesCivilopediaDescription() = false
/** Override this and return true to tell the Civilopedia that this is not empty even if nothing came from json */
fun hasCivilopediaTextLines() = false
/** Indicates that neither json nor getCivilopediaTextLines have content */
fun isCivilopediaTextEmpty() = civilopediaText.isEmpty() && !hasCivilopediaTextLines()
/** Build a Gdx [Table] showing our [formatted][FormattedLine] [content][civilopediaText]. */
fun renderCivilopediaText (labelWidth: Float, linkAction: ((id: String)->Unit)? = null): Table {
return MarkupRenderer.render(civilopediaText, labelWidth, linkAction = linkAction)
}
/** Assemble json-supplied lines with automatically generated ones.
*
* The default implementation will insert [getCivilopediaTextLines] before the first [linked][FormattedLine.link] [civilopediaText] line and [getCivilopediaTextHeader] on top.
*
* @param ruleset The current ruleset for the Civilopedia viewer
* @return A new CivilopediaText instance containing original [civilopediaText] lines merged with those from [getCivilopediaTextHeader] and [getCivilopediaTextLines] calls.
*/
fun assembleCivilopediaText(ruleset: Ruleset): CivilopediaText {
val outerLines = civilopediaText.iterator()
val newLines = sequence {
var middleDone = false
var outerNotEmpty = false
val header = getCivilopediaTextHeader()
if (header != null) {
yield(header)
yield(FormattedLine(separator = true))
}
while (outerLines.hasNext()) {
val next = outerLines.next()
if (!middleDone && !next.isEmpty() && next.linkType != FormattedLine.LinkType.None) {
middleDone = true
if (hasCivilopediaTextLines()) {
if (outerNotEmpty) yield(FormattedLine())
yieldAll(getCivilopediaTextLines(ruleset))
yield(FormattedLine())
}
}
outerNotEmpty = true
yield(next)
}
if (!middleDone) {
if (outerNotEmpty && hasCivilopediaTextLines()) yield(FormattedLine())
yieldAll(getCivilopediaTextLines(ruleset))
}
}
return SimpleCivilopediaText(newLines.toList(), isComplete = true)
}
}