Pedia Search (#9997)

* Minor Civilopedia linting

* Civilopedia Search Popup

* Add missing "entire current complex ruleset" scope

* Address comments

* Wording change

* Remove comment
This commit is contained in:
SomeTroglodyte
2023-09-03 08:32:28 +02:00
committed by GitHub
parent 23c8ba05de
commit bb3335aaa8
9 changed files with 271 additions and 65 deletions

View File

@ -1604,6 +1604,13 @@ Toggle UI (World Screen only) =
Overrides yields from underlying terrain = Overrides yields from underlying terrain =
No yields = No yields =
Mod: [modname] = Mod: [modname] =
Search text: =
Invalid regular expression =
Mod filter: =
-Combined- =
Search! =
Results =
Nothing found! =
# Policies # Policies

View File

@ -7,7 +7,6 @@ import com.unciv.models.ruleset.unique.UniqueTarget
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen.Companion.showReligionInCivilopedia import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen.Companion.showReligionInCivilopedia
import com.unciv.ui.screens.civilopediascreen.FormattedLine import com.unciv.ui.screens.civilopediascreen.FormattedLine
import kotlin.collections.ArrayList
class Belief() : RulesetObject() { class Belief() : RulesetObject() {
var type: BeliefType = BeliefType.None var type: BeliefType = BeliefType.None
@ -23,7 +22,6 @@ class Belief() : RulesetObject() {
override fun makeLink() = "Belief/$name" override fun makeLink() = "Belief/$name"
override fun getCivilopediaTextHeader() = FormattedLine(name, icon = makeLink(), header = 2, color = if (type == BeliefType.None) "#e34a2b" else "") override fun getCivilopediaTextHeader() = FormattedLine(name, icon = makeLink(), header = 2, color = if (type == BeliefType.None) "#e34a2b" else "")
override fun getSortGroup(ruleset: Ruleset) = type.ordinal override fun getSortGroup(ruleset: Ruleset) = type.ordinal
override fun getIconName() = if (type == BeliefType.None) "Religion" else type.name
override fun getCivilopediaTextLines(ruleset: Ruleset): List<FormattedLine> { override fun getCivilopediaTextLines(ruleset: Ruleset): List<FormattedLine> {
return getCivilopediaTextLines(false) return getCivilopediaTextLines(false)

View File

@ -56,7 +56,7 @@ class Speed : RulesetObject(), IsPartOfGameInfoSerialization {
override fun getUniqueTarget(): UniqueTarget = UniqueTarget.Speed override fun getUniqueTarget(): UniqueTarget = UniqueTarget.Speed
override fun makeLink(): String = "GameSpeed/$name" override fun makeLink(): String = "Speed/$name"
override fun getCivilopediaTextHeader() = FormattedLine(name, header = 2) override fun getCivilopediaTextHeader() = FormattedLine(name, header = 2)
override fun getCivilopediaTextLines(ruleset: Ruleset) = sequence { override fun getCivilopediaTextLines(ruleset: Ruleset) = sequence {
yield(FormattedLine("General speed modifier: [${modifier * 100}]%${Fonts.turn}")) yield(FormattedLine("General speed modifier: [${modifier * 100}]%${Fonts.turn}"))

View File

@ -92,7 +92,9 @@ class TutorialController(screen: BaseScreen) {
) : INamed, SimpleCivilopediaText( ) : INamed, SimpleCivilopediaText(
sequenceOf(FormattedLine(extraImage = name.replace(' ', '_'))) + tutorial.civilopediaText.asSequence(), sequenceOf(FormattedLine(extraImage = name.replace(' ', '_'))) + tutorial.civilopediaText.asSequence(),
tutorial.steps?.asSequence() ?: emptySequence() tutorial.steps?.asSequence() ?: emptySequence()
) ) {
override fun makeLink() = "Tutorial/$name"
}
/** Get all Tutorials intended to be displayed in the Civilopedia /** Get all Tutorials intended to be displayed in the Civilopedia
* as a List of wrappers supporting INamed and ICivilopediaText * as a List of wrappers supporting INamed and ICivilopediaText

View File

@ -10,13 +10,16 @@ import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.tile.Terrain import com.unciv.models.ruleset.tile.Terrain
import com.unciv.models.ruleset.tile.TerrainType import com.unciv.models.ruleset.tile.TerrainType
import com.unciv.models.ruleset.unit.UnitMovementType import com.unciv.models.ruleset.unit.UnitMovementType
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.components.tilegroups.TileGroup
import com.unciv.ui.components.tilegroups.TileSetStrings
import com.unciv.ui.components.input.KeyCharAndCode
import com.unciv.ui.components.extensions.setSize import com.unciv.ui.components.extensions.setSize
import com.unciv.ui.components.extensions.surroundWithCircle import com.unciv.ui.components.extensions.surroundWithCircle
import com.unciv.ui.components.input.KeyCharAndCode
import com.unciv.ui.components.tilegroups.TileGroup
import com.unciv.ui.components.tilegroups.TileSetStrings
import com.unciv.ui.images.IconCircleGroup import com.unciv.ui.images.IconCircleGroup
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.screens.basescreen.TutorialController
import com.unciv.models.ruleset.Belief as BaseBelief
import com.unciv.models.ruleset.unit.UnitType as BaseUnitType
/** Encapsulates the knowledge on how to get an icon for each of the Civilopedia categories */ /** Encapsulates the knowledge on how to get an icon for each of the Civilopedia categories */
@ -115,87 +118,105 @@ enum class CivilopediaCategories (
val hide: Boolean, // Omitted on CivilopediaScreen val hide: Boolean, // Omitted on CivilopediaScreen
val getImage: ((name: String, size: Float) -> Actor?)?, val getImage: ((name: String, size: Float) -> Actor?)?,
val key: KeyCharAndCode = KeyCharAndCode.UNKNOWN, val key: KeyCharAndCode = KeyCharAndCode.UNKNOWN,
val headerIcon: String val headerIcon: String,
) { val getCategoryIterator: (ruleset: Ruleset, tutorialController: TutorialController) -> Collection<ICivilopediaText>
) {
Building ("Buildings", false, Building ("Buildings", false,
CivilopediaImageGetters.construction, CivilopediaImageGetters.construction,
KeyCharAndCode('B'), KeyCharAndCode('B'),
"OtherIcons/Cities" "OtherIcons/Cities",
{ ruleset, _ -> ruleset.buildings.values.filter { !it.isAnyWonder() } }
), ),
Wonder ("Wonders", false, Wonder ("Wonders", false,
CivilopediaImageGetters.construction, CivilopediaImageGetters.construction,
KeyCharAndCode('W'), KeyCharAndCode('W'),
"OtherIcons/Wonders" "OtherIcons/Wonders",
{ ruleset, _ -> ruleset.buildings.values.filter { it.isAnyWonder() } }
), ),
Resource ("Resources", false, Resource ("Resources", false,
CivilopediaImageGetters.resource, CivilopediaImageGetters.resource,
KeyCharAndCode('R'), KeyCharAndCode('R'),
"OtherIcons/Resources" "OtherIcons/Resources",
{ ruleset, _ -> ruleset.tileResources.values }
), ),
Terrain ("Terrains", false, Terrain ("Terrains", false,
CivilopediaImageGetters.terrain, CivilopediaImageGetters.terrain,
KeyCharAndCode('T'), KeyCharAndCode('T'),
"OtherIcons/Terrains" "OtherIcons/Terrains",
{ ruleset, _ -> ruleset.terrains.values }
), ),
Improvement ("Tile Improvements", false, Improvement ("Tile Improvements", false,
CivilopediaImageGetters.improvement, CivilopediaImageGetters.improvement,
KeyCharAndCode('T'), KeyCharAndCode('T'),
"OtherIcons/Improvements" "OtherIcons/Improvements",
{ ruleset, _ -> ruleset.tileImprovements.values }
), ),
Unit ("Units", false, Unit ("Units", false,
CivilopediaImageGetters.construction, CivilopediaImageGetters.construction,
KeyCharAndCode('U'), KeyCharAndCode('U'),
"OtherIcons/Shield" "OtherIcons/Shield",
{ ruleset, _ -> ruleset.units.values }
), ),
UnitType ("Unit types", false, UnitType ("Unit types", false,
CivilopediaImageGetters.unitType, CivilopediaImageGetters.unitType,
KeyCharAndCode('U'), KeyCharAndCode('U'),
"UnitTypeIcons/UnitTypes" "UnitTypeIcons/UnitTypes",
{ ruleset, _ -> BaseUnitType.getCivilopediaIterator(ruleset) }
), ),
Nation ("Nations", false, Nation ("Nations", false,
CivilopediaImageGetters.nation, CivilopediaImageGetters.nation,
KeyCharAndCode('N'), KeyCharAndCode('N'),
"OtherIcons/Nations" "OtherIcons/Nations",
{ ruleset, _ -> ruleset.nations.values.filter { !it.isSpectator } }
), ),
Technology ("Technologies", false, Technology ("Technologies", false,
CivilopediaImageGetters.technology, CivilopediaImageGetters.technology,
KeyCharAndCode('T'), KeyCharAndCode('T'),
"TechIcons/Philosophy" "TechIcons/Philosophy",
{ ruleset, _ -> ruleset.technologies.values }
), ),
Promotion ("Promotions", false, Promotion ("Promotions", false,
CivilopediaImageGetters.promotion, CivilopediaImageGetters.promotion,
KeyCharAndCode('P'), KeyCharAndCode('P'),
"UnitPromotionIcons/Mobility" "UnitPromotionIcons/Mobility",
{ ruleset, _ -> ruleset.unitPromotions.values }
), ),
Policy ("Policies", false, Policy ("Policies", false,
CivilopediaImageGetters.policy, CivilopediaImageGetters.policy,
KeyCharAndCode('P'), KeyCharAndCode('P'),
"PolicyIcons/Constitution" "PolicyIcons/Constitution",
{ ruleset, _ -> ruleset.policies.values }
), ),
Belief("Religions and Beliefs", false, Belief("Religions and Beliefs", false,
CivilopediaImageGetters.belief, CivilopediaImageGetters.belief,
KeyCharAndCode('R'), KeyCharAndCode('R'),
"ReligionIcons/Religion" "ReligionIcons/Religion",
{ ruleset, _ -> (ruleset.beliefs.values.asSequence() +
BaseBelief.getCivilopediaReligionEntry(ruleset)).toList() }
), ),
Tutorial ("Tutorials", false, Tutorial ("Tutorials", false,
getImage = null, getImage = null,
KeyCharAndCode(Input.Keys.F1), KeyCharAndCode(Input.Keys.F1),
"OtherIcons/ExclamationMark" "OtherIcons/ExclamationMark",
{ _, tutorialController -> tutorialController.getCivilopediaTutorials() }
), ),
Difficulty ("Difficulty levels", false, Difficulty ("Difficulty levels", false,
getImage = null, getImage = null,
KeyCharAndCode('D'), KeyCharAndCode('D'),
"OtherIcons/Quickstart" "OtherIcons/Quickstart",
{ ruleset, _ -> ruleset.difficulties.values }
), ),
Era ("Eras", false, Era ("Eras", false,
getImage = null, getImage = null,
KeyCharAndCode('D'), KeyCharAndCode('D'),
"OtherIcons/Tyrannosaurus" "OtherIcons/Tyrannosaurus",
{ ruleset, _ -> ruleset.eras.values }
), ),
Speed ("Speeds", false, Speed ("Speeds", false,
getImage = null, getImage = null,
KeyCharAndCode('S'), KeyCharAndCode('S'),
"OtherIcons/Timer" "OtherIcons/Timer",
{ ruleset, _ -> ruleset.speeds.values }
); );
private fun getByOffset(offset: Int) = values()[(ordinal + count + offset) % count] private fun getByOffset(offset: Int) = values()[(ordinal + count + offset) % count]

View File

@ -7,23 +7,23 @@ import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.Button import com.badlogic.gdx.scenes.scene2d.ui.Button
import com.badlogic.gdx.scenes.scene2d.ui.SplitPane import com.badlogic.gdx.scenes.scene2d.ui.SplitPane
import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.models.ruleset.Belief
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetCache import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.ruleset.unique.IHasUniques import com.unciv.models.ruleset.unique.IHasUniques
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unit.UnitType
import com.unciv.models.stats.INamed import com.unciv.models.stats.INamed
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.components.Fonts import com.unciv.ui.components.Fonts
import com.unciv.ui.components.input.KeyCharAndCode
import com.unciv.ui.components.UncivTooltip.Companion.addTooltip import com.unciv.ui.components.UncivTooltip.Companion.addTooltip
import com.unciv.ui.components.extensions.colorFromRGB import com.unciv.ui.components.extensions.colorFromRGB
import com.unciv.ui.components.input.onClick import com.unciv.ui.components.extensions.toImageButton
import com.unciv.ui.components.extensions.toLabel import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.components.extensions.toTextButton 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.input.onClick
import com.unciv.ui.images.IconTextButton import com.unciv.ui.images.IconTextButton
import com.unciv.ui.images.ImageGetter import com.unciv.ui.images.ImageGetter
import com.unciv.ui.screens.basescreen.BaseScreen import com.unciv.ui.screens.basescreen.BaseScreen
@ -75,6 +75,11 @@ class CivilopediaScreen(
private var currentEntry: String = "" private var currentEntry: String = ""
private val currentEntryPerCategory = HashMap<CivilopediaCategories, String>() private val currentEntryPerCategory = HashMap<CivilopediaCategories, String>()
private val searchPopup by lazy { CivilopediaSearchPopup(this, tutorialController) {
selectLink(it)
} }
/** Jump to a "link" selecting both category and entry /** Jump to a "link" selecting both category and entry
* *
* Calls [selectCategory] with the substring before the first '/', * Calls [selectCategory] with the substring before the first '/',
@ -160,7 +165,7 @@ class CivilopediaScreen(
* @param name Entry (Ruleset object) name * @param name Entry (Ruleset object) name
* @param noScrollAnimation Disable scroll animation * @param noScrollAnimation Disable scroll animation
*/ */
fun selectEntry(name: String, noScrollAnimation: Boolean = false) { private fun selectEntry(name: String, noScrollAnimation: Boolean = false) {
val entry = entryIndex[name] ?: return val entry = entryIndex[name] ?: return
// fails: entrySelectScroll.scrollTo(0f, entry.y, 0f, entry.h, false, true) // fails: entrySelectScroll.scrollTo(0f, entry.y, 0f, entry.h, false, true)
entrySelectScroll.scrollY = (entry.y + (entry.height - entrySelectScroll.height) / 2) entrySelectScroll.scrollY = (entry.y + (entry.height - entrySelectScroll.height) / 2)
@ -195,7 +200,6 @@ class CivilopediaScreen(
init { init {
val imageSize = 50f val imageSize = 50f
globalShortcuts.add(KeyCharAndCode.BACK) { game.popScreen() }
val religionEnabled = showReligionInCivilopedia(ruleset) val religionEnabled = showReligionInCivilopedia(ruleset)
val victoryTypes = game.gameInfo?.gameParameters?.victoryTypes ?: ruleset.victories.keys val victoryTypes = game.gameInfo?.gameParameters?.victoryTypes ?: ruleset.victories.keys
@ -209,32 +213,11 @@ class CivilopediaScreen(
} }
} }
fun getCategoryIterator(category: CivilopediaCategories): Collection<ICivilopediaText> =
when (category) {
CivilopediaCategories.Building -> ruleset.buildings.values.filter { !it.isAnyWonder() }
CivilopediaCategories.Wonder -> ruleset.buildings.values.filter { it.isAnyWonder() }
CivilopediaCategories.Resource -> ruleset.tileResources.values
CivilopediaCategories.Terrain -> ruleset.terrains.values
CivilopediaCategories.Improvement -> ruleset.tileImprovements.values
CivilopediaCategories.Unit -> ruleset.units.values
CivilopediaCategories.UnitType -> UnitType.getCivilopediaIterator(ruleset)
CivilopediaCategories.Nation -> ruleset.nations.values.filter { !it.isSpectator }
CivilopediaCategories.Technology -> ruleset.technologies.values
CivilopediaCategories.Promotion -> ruleset.unitPromotions.values
CivilopediaCategories.Policy -> ruleset.policies.values
CivilopediaCategories.Tutorial -> tutorialController.getCivilopediaTutorials()
CivilopediaCategories.Difficulty -> ruleset.difficulties.values
CivilopediaCategories.Belief -> (ruleset.beliefs.values.asSequence() +
Belief.getCivilopediaReligionEntry(ruleset)).toList()
CivilopediaCategories.Era -> ruleset.eras.values
CivilopediaCategories.Speed -> ruleset.speeds.values
}
for (loopCategory in CivilopediaCategories.values()) { for (loopCategory in CivilopediaCategories.values()) {
if (loopCategory.hide) continue if (loopCategory.hide) continue
if (!religionEnabled && loopCategory == CivilopediaCategories.Belief) continue if (!religionEnabled && loopCategory == CivilopediaCategories.Belief) continue
categoryToEntries[loopCategory] = categoryToEntries[loopCategory] =
getCategoryIterator(loopCategory) loopCategory.getCategoryIterator(ruleset, tutorialController)
.filter { (it as? IHasUniques)?.let { obj -> shouldBeDisplayed(obj) } ?: true } .filter { (it as? IHasUniques)?.let { obj -> shouldBeDisplayed(obj) } ?: true }
.map { CivilopediaEntry( .map { CivilopediaEntry(
(it as INamed).name, (it as INamed).name,
@ -254,7 +237,6 @@ class CivilopediaScreen(
val icon = if (categoryKey.headerIcon.isNotEmpty()) ImageGetter.getImage(categoryKey.headerIcon) else null val icon = if (categoryKey.headerIcon.isNotEmpty()) ImageGetter.getImage(categoryKey.headerIcon) else null
val button = IconTextButton(categoryKey.label, icon) val button = IconTextButton(categoryKey.label, icon)
button.addTooltip(categoryKey.key) button.addTooltip(categoryKey.key)
// button.style = ImageButton.ImageButtonStyle(button.style)
button.onClick { selectCategory(categoryKey) } button.onClick { selectCategory(categoryKey) }
val cell = buttonTable.add(button) val cell = buttonTable.add(button)
categoryToButtons[categoryKey] = CategoryButtonInfo(button, currentX, cell.prefWidth) categoryToButtons[categoryKey] = CategoryButtonInfo(button, currentX, cell.prefWidth)
@ -265,14 +247,18 @@ class CivilopediaScreen(
buttonTableScroll = ScrollPane(buttonTable) buttonTableScroll = ScrollPane(buttonTable)
buttonTableScroll.setScrollingDisabled(false, true) buttonTableScroll.setScrollingDisabled(false, true)
val goToGameButton = Constants.close.toTextButton() val searchButton = "OtherIcons/Search".toImageButton(imageSize - 16f, imageSize, skinStrings.skinConfig.baseColor, Color.GOLD)
goToGameButton.onClick { searchButton.onActivation { searchPopup.open(true) }
game.popScreen() searchButton.keyShortcuts.add(KeyboardBinding.Civilopedia) // "hit twice to search"
}
val closeButton = "OtherIcons/Close".toImageButton(imageSize - 20f, imageSize, skinStrings.skinConfig.baseColor, Color.RED)
closeButton.onActivation { game.popScreen() }
closeButton.keyShortcuts.add(KeyCharAndCode.BACK)
val topTable = Table() val topTable = Table()
topTable.add(goToGameButton).pad(10f)
topTable.add(buttonTableScroll).growX() topTable.add(buttonTableScroll).growX()
topTable.add(searchButton).padLeft(10f)
topTable.add(closeButton).padLeft(10f).padRight(10f)
topTable.width = stage.width topTable.width = stage.width
topTable.layout() topTable.layout()

View File

@ -0,0 +1,187 @@
package com.unciv.ui.screens.civilopediascreen
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.ui.Cell
import com.badlogic.gdx.scenes.scene2d.ui.SelectBox
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
import com.badlogic.gdx.utils.Align
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.stats.INamed
import com.unciv.models.translations.tr
import com.unciv.ui.components.ExpanderTab
import com.unciv.ui.components.UncivTextField
import com.unciv.ui.components.extensions.disable
import com.unciv.ui.components.extensions.enable
import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.components.input.KeyCharAndCode
import com.unciv.ui.components.input.onClick
import com.unciv.ui.popups.Popup
import com.unciv.ui.popups.ToastPopup
import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.screens.basescreen.TutorialController
import com.unciv.utils.Concurrency
import com.unciv.utils.launchOnGLThread
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.isActive
import com.badlogic.gdx.utils.Array as GdxArray
class CivilopediaSearchPopup(
private val pediaScreen: CivilopediaScreen,
private val tutorialController: TutorialController,
private val linkAction: (String) -> Unit
) : Popup(pediaScreen) {
private var ruleset = pediaScreen.ruleset
private val searchText = UncivTextField.create("") // Always focused, "hint" never seen
private val modSelect = ModSelectBox()
private lateinit var resultExpander: ExpanderTab
private val resultCell: Cell<Actor?>
private val searchButton: TextButton
private var searchJob: Job? = null
private var checkLine: (String) -> Boolean = { _ -> false }
init {
searchText.maxLength = 100
add("Search text:".toLabel())
add(searchText).growX().row()
add("Mod filter:".toLabel())
add(modSelect).growX().row()
resultCell = add().colspan(2).growX()
row()
searchButton = addButton("Search!", KeyCharAndCode.RETURN) {
startSearch(searchText.text)
}.actor
addCloseButton()
showListeners.add {
keyboardFocus = searchText
searchText.selectAll()
}
closeListeners.add {
if (isSearchRunning()) searchJob!!.cancel()
}
}
private fun isSearchRunning() = searchJob?.isActive == true
private fun startSearch(text: String) {
searchButton.disable()
@Suppress("LiftReturnOrAssignment")
if (text.isEmpty()) {
checkLine = { true }
} else if (".*" in text || '\\' in text || '|' in text) {
try {
val regex = Regex(text, RegexOption.IGNORE_CASE)
checkLine = { regex.containsMatchIn(it) }
} catch (ex: Exception) {
ToastPopup("Invalid regular expression", pediaScreen, 4000).open(true)
searchButton.enable()
return
}
} else {
val words = text.split(' ').toSet()
checkLine = { line -> words.all { line.contains(it, ignoreCase = true) } }
}
ruleset = modSelect.selectedRuleset()
if (::resultExpander.isInitialized) {
resultExpander.innerTable.clear()
} else {
resultExpander = ExpanderTab("Results") {}
resultCell.setActor(resultExpander)
resultExpander.innerTable.defaults().growX().pad(2f)
}
searchJob = Concurrency.run("PediaSearch") {
searchLoop()
}
searchJob!!.invokeOnCompletion {
searchJob = null
Concurrency.runOnGLThread {
finishSearch()
}
}
}
private fun CoroutineScope.searchLoop() {
for (category in CivilopediaCategories.values()) {
if (!isActive) break
if (category.hide) continue
if (!ruleset.modOptions.isBaseRuleset && category == CivilopediaCategories.Tutorial)
continue // Search tutorials only when the mod filter is a base ruleset
for (entry in category.getCategoryIterator(ruleset, tutorialController)) {
if (!isActive) break
if (entry !is INamed) continue
if (!ruleset.modOptions.isBaseRuleset) {
val sort = entry.getSortGroup(ruleset)
if (category == CivilopediaCategories.UnitType && sort < 2)
continue // Search "Domain:" entries only when the mod filter is a base ruleset
if (category == CivilopediaCategories.Belief && sort == 0)
continue // Search "Religions" from `getCivilopediaReligionEntry` only when the mod filter is a base ruleset
}
searchEntry(entry)
}
}
}
private fun CoroutineScope.searchEntry(entry: ICivilopediaText) {
val scope = sequence {
entry.getCivilopediaTextHeader()?.let { yield(it) }
yieldAll(entry.civilopediaText)
yieldAll(entry.getCivilopediaTextLines(ruleset))
}
for (line in scope) {
if (!isActive) break
val lineText = line.text.tr(hideIcons = true)
if (!checkLine(lineText)) continue
addResult(entry)
break
}
}
private fun CoroutineScope.addResult(entry: ICivilopediaText) {
launchOnGLThread {
val actor = entry.getIconName().toLabel(alignment = Align.left)
val link = entry.makeLink()
resultExpander.innerTable.add(actor).row()
actor.onClick {
linkAction(link)
close()
}
}
}
private fun finishSearch() {
searchButton.enable()
if (!resultExpander.innerTable.cells.isEmpty) return
val nothingFound = FormattedLine("Nothing found!", color = "#f53", header = 3, centered = true)
.render(0f)
resultExpander.innerTable.add(nothingFound)
}
class ModSelectEntry(val key: String, val translate: Boolean = false) {
override fun toString() = if (translate) key.tr() else key
}
private inner class ModSelectBox : SelectBox<ModSelectEntry>(BaseScreen.skin) {
init {
val mods = pediaScreen.ruleset.mods
val entries = GdxArray<ModSelectEntry>(mods.size + 1)
entries.add(ModSelectEntry("-Combined-", true))
// This intersect is needed when pedia was called from the MainMenuScreen with an easter egg ruleset active -
// they are not in the cache and have their elements not marked with originRuleset anyway.
for (mod in mods.intersect(RulesetCache.keys)) entries.add(ModSelectEntry(mod))
items = entries
selectedIndex = 0
}
fun selectedRuleset(): Ruleset =
if (selectedIndex == 0) pediaScreen.ruleset
else RulesetCache[selected.key]!! // `!!` guarded by the intersect above
}
}

View File

@ -15,7 +15,6 @@ import com.unciv.ui.components.ColorMarkupLabel
import com.unciv.ui.components.extensions.toLabel import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.images.ImageGetter import com.unciv.ui.images.ImageGetter
import com.unciv.ui.screens.basescreen.BaseScreen import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.components.extensions.toLabel
import com.unciv.utils.Log import com.unciv.utils.Log
import kotlin.math.max import kotlin.math.max

View File

@ -4,9 +4,9 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.models.ruleset.IRulesetObject import com.unciv.models.ruleset.IRulesetObject
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetObject
import com.unciv.models.stats.INamed import com.unciv.models.stats.INamed
/** Addon common to most ruleset game objects managing civilopedia display /** Addon common to most ruleset game objects managing civilopedia display
* *
* ### Usage: * ### Usage:
@ -91,7 +91,13 @@ interface ICivilopediaText {
return SimpleCivilopediaText(newLines.toList()) return SimpleCivilopediaText(newLines.toList())
} }
/** Create the correct string for a Civilopedia link */ /** Create the correct string for a Civilopedia link.
*
* To actually make it work both as link and as icon identifier, return a string in the form
* category/entryname where `category` **must** correspond exactly to either name or label of
* the correct [CivilopediaCategories] member. `entryname` must equal the
* [ruleset object name][RulesetObject] as defined by the [INamed] interface.
*/
fun makeLink(): String fun makeLink(): String
/** Overrides alphabetical sorting in Civilopedia /** Overrides alphabetical sorting in Civilopedia