mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-16 02:40:41 +07:00
Spruced up Civilopedia - phase 1 - linking and category Enum (#3975)
This commit is contained in:
@ -1,198 +0,0 @@
|
|||||||
package com.unciv.ui
|
|
||||||
|
|
||||||
import com.badlogic.gdx.graphics.Color
|
|
||||||
import com.unciv.ui.utils.AutoScrollPane as ScrollPane
|
|
||||||
import com.badlogic.gdx.scenes.scene2d.Actor
|
|
||||||
import com.badlogic.gdx.scenes.scene2d.Touchable
|
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.*
|
|
||||||
import com.unciv.Constants
|
|
||||||
import com.unciv.UncivGame
|
|
||||||
import com.unciv.logic.map.TileInfo
|
|
||||||
import com.unciv.models.ruleset.Ruleset
|
|
||||||
import com.unciv.models.ruleset.tile.Terrain
|
|
||||||
import com.unciv.models.ruleset.tile.TerrainType
|
|
||||||
import com.unciv.models.translations.tr
|
|
||||||
import com.unciv.ui.tilegroups.TileGroup
|
|
||||||
import com.unciv.ui.tilegroups.TileSetStrings
|
|
||||||
import com.unciv.ui.utils.*
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class CivilopediaScreen(ruleset: Ruleset) : CameraStageBaseScreen() {
|
|
||||||
class CivilopediaEntry(var name: String, var description: String, var image: Actor? = null)
|
|
||||||
|
|
||||||
private val categoryToEntries = LinkedHashMap<String, Collection<CivilopediaEntry>>()
|
|
||||||
private val categoryToButtons = LinkedHashMap<String, Button>()
|
|
||||||
|
|
||||||
private val entrySelectTable = Table().apply { defaults().pad(6f).left() }
|
|
||||||
val description = "".toLabel()
|
|
||||||
|
|
||||||
|
|
||||||
fun select(category: String) {
|
|
||||||
entrySelectTable.clear()
|
|
||||||
|
|
||||||
for (button in categoryToButtons.values) button.color = Color.WHITE
|
|
||||||
categoryToButtons[category]!!.color = Color.BLUE
|
|
||||||
|
|
||||||
var entries = categoryToEntries[category]!!
|
|
||||||
if (category != "Difficulty levels") // this is the only case where we need them in order
|
|
||||||
entries = entries.sortedBy { it.name.tr() } // Alphabetical order of localized names
|
|
||||||
for (entry in entries) {
|
|
||||||
val entryButton = Table().apply {
|
|
||||||
background = ImageGetter.getBackground(colorFromRGB(50, 75, 125))
|
|
||||||
touchable = Touchable.enabled
|
|
||||||
}
|
|
||||||
if (entry.image != null)
|
|
||||||
if (category == "Terrains")
|
|
||||||
entryButton.add(entry.image).padLeft(20f).padRight(10f)
|
|
||||||
else
|
|
||||||
entryButton.add(entry.image).padLeft(10f)
|
|
||||||
entryButton.left().add(entry.name.toLabel(Color.WHITE, 25)).pad(10f)
|
|
||||||
entryButton.onClick {
|
|
||||||
description.setText(entry.description)
|
|
||||||
entrySelectTable.children.forEach { it.color = Color.WHITE }
|
|
||||||
entryButton.color = Color.BLUE
|
|
||||||
}
|
|
||||||
entrySelectTable.add(entryButton).height(75f).expandX().fillX().row()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
val imageSize = 50f
|
|
||||||
onBackButtonClicked { UncivGame.Current.setWorldScreen() }
|
|
||||||
|
|
||||||
categoryToEntries["Buildings"] = ruleset.buildings.values
|
|
||||||
.filter { "Will not be displayed in Civilopedia" !in it.uniques && !(it.isWonder || it.isNationalWonder) }
|
|
||||||
.map {
|
|
||||||
CivilopediaEntry(it.name, it.getDescription(false, null, ruleset),
|
|
||||||
ImageGetter.getConstructionImage(it.name).surroundWithCircle(imageSize))
|
|
||||||
}
|
|
||||||
categoryToEntries["Wonders"] = ruleset.buildings.values
|
|
||||||
.filter { "Will not be displayed in Civilopedia" !in it.uniques && (it.isWonder || it.isNationalWonder) }
|
|
||||||
.map {
|
|
||||||
CivilopediaEntry(it.name, it.getDescription(false, null, ruleset),
|
|
||||||
ImageGetter.getConstructionImage(it.name).surroundWithCircle(imageSize))
|
|
||||||
}
|
|
||||||
categoryToEntries["Resources"] = ruleset.tileResources.values
|
|
||||||
.map {
|
|
||||||
CivilopediaEntry(it.name, it.getDescription(ruleset),
|
|
||||||
ImageGetter.getResourceImage(it.name, imageSize))
|
|
||||||
}
|
|
||||||
categoryToEntries["Terrains"] = ruleset.terrains.values
|
|
||||||
.map {
|
|
||||||
CivilopediaEntry(it.name, it.getDescription(ruleset),
|
|
||||||
terrainImage(it, ruleset, imageSize))
|
|
||||||
}
|
|
||||||
categoryToEntries["Tile Improvements"] = ruleset.tileImprovements.values
|
|
||||||
.map {
|
|
||||||
CivilopediaEntry(it.name, it.getDescription(ruleset, false),
|
|
||||||
ImageGetter.getImprovementIcon(it.name, imageSize))
|
|
||||||
}
|
|
||||||
categoryToEntries["Units"] = ruleset.units.values
|
|
||||||
.filter { "Will not be displayed in Civilopedia" !in it.uniques }
|
|
||||||
.map {
|
|
||||||
CivilopediaEntry(it.name, it.getDescription(false),
|
|
||||||
ImageGetter.getConstructionImage(it.name).surroundWithCircle(imageSize))
|
|
||||||
}
|
|
||||||
categoryToEntries["Nations"] = ruleset.nations.values
|
|
||||||
.filter { it.isMajorCiv() }
|
|
||||||
.map {
|
|
||||||
CivilopediaEntry(it.name, it.getUniqueString(ruleset, false),
|
|
||||||
ImageGetter.getNationIndicator(it, imageSize))
|
|
||||||
}
|
|
||||||
categoryToEntries["Technologies"] = ruleset.technologies.values
|
|
||||||
.map {
|
|
||||||
CivilopediaEntry(it.name, it.getDescription(ruleset),
|
|
||||||
ImageGetter.getTechIconGroup(it.name, imageSize))
|
|
||||||
}
|
|
||||||
categoryToEntries["Promotions"] = ruleset.unitPromotions.values
|
|
||||||
.map {
|
|
||||||
CivilopediaEntry(it.name, it.getDescription(ruleset.unitPromotions.values, true, ruleset),
|
|
||||||
ImageGetter.getPromotionIcon(it.name, imageSize))
|
|
||||||
}
|
|
||||||
|
|
||||||
categoryToEntries["Tutorials"] = tutorialController.getCivilopediaTutorials()
|
|
||||||
.map { CivilopediaEntry(it.key.replace("_", " "), it.value.joinToString("\n\n") { line -> line.tr() }) }
|
|
||||||
|
|
||||||
categoryToEntries["Difficulty levels"] = ruleset.difficulties.values
|
|
||||||
.map { CivilopediaEntry(it.name, it.getDescription()) }
|
|
||||||
|
|
||||||
val buttonTable = Table()
|
|
||||||
buttonTable.pad(15f)
|
|
||||||
buttonTable.defaults().pad(10f)
|
|
||||||
|
|
||||||
for (category in categoryToEntries.keys) {
|
|
||||||
val button = category.toTextButton()
|
|
||||||
button.style = TextButton.TextButtonStyle(button.style)
|
|
||||||
categoryToButtons[category] = button
|
|
||||||
button.onClick { select(category) }
|
|
||||||
buttonTable.add(button)
|
|
||||||
}
|
|
||||||
|
|
||||||
buttonTable.pack()
|
|
||||||
buttonTable.width = stage.width
|
|
||||||
val buttonTableScroll = ScrollPane(buttonTable)
|
|
||||||
buttonTableScroll.setScrollingDisabled(false, true)
|
|
||||||
|
|
||||||
val goToGameButton = Constants.close.toTextButton()
|
|
||||||
goToGameButton.onClick {
|
|
||||||
game.setWorldScreen()
|
|
||||||
dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
val topTable = Table()
|
|
||||||
topTable.add(goToGameButton).pad(10f)
|
|
||||||
topTable.add(buttonTableScroll)
|
|
||||||
topTable.pack()
|
|
||||||
|
|
||||||
val entryTable = Table()
|
|
||||||
val splitPane = SplitPane(topTable, entryTable, true, skin)
|
|
||||||
splitPane.splitAmount = topTable.prefHeight / stage.height
|
|
||||||
entryTable.height = stage.height - topTable.prefHeight
|
|
||||||
splitPane.setFillParent(true)
|
|
||||||
|
|
||||||
stage.addActor(splitPane)
|
|
||||||
|
|
||||||
description.wrap = true
|
|
||||||
|
|
||||||
val entrySelectScroll = ScrollPane(entrySelectTable)
|
|
||||||
entrySelectTable.top()
|
|
||||||
entrySelectScroll.setOverscroll(false, false)
|
|
||||||
val descriptionTable = Table()
|
|
||||||
descriptionTable.add(description).width(stage.width * 0.5f)
|
|
||||||
val entrySplitPane = SplitPane(entrySelectScroll, ScrollPane(descriptionTable), false, skin)
|
|
||||||
entrySplitPane.splitAmount = 0.3f
|
|
||||||
entryTable.addActor(entrySplitPane)
|
|
||||||
entrySplitPane.setFillParent(true)
|
|
||||||
|
|
||||||
select("Tutorials")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun terrainImage(terrain: Terrain, ruleset: Ruleset, imageSize: Float): Actor {
|
|
||||||
val tileInfo = TileInfo()
|
|
||||||
tileInfo.ruleset = ruleset
|
|
||||||
when (terrain.type) {
|
|
||||||
TerrainType.NaturalWonder -> {
|
|
||||||
tileInfo.naturalWonder = terrain.name
|
|
||||||
tileInfo.baseTerrain = terrain.turnsInto ?: Constants.grassland
|
|
||||||
}
|
|
||||||
TerrainType.TerrainFeature -> {
|
|
||||||
tileInfo.terrainFeatures.add(terrain.name)
|
|
||||||
tileInfo.baseTerrain = terrain.occursOn.lastOrNull() ?: Constants.grassland
|
|
||||||
}
|
|
||||||
else ->
|
|
||||||
tileInfo.baseTerrain = terrain.name
|
|
||||||
}
|
|
||||||
tileInfo.setTransients()
|
|
||||||
val group = TileGroup(tileInfo, TileSetStrings(), imageSize)
|
|
||||||
group.showEntireMap = true
|
|
||||||
group.forMapEditorIcon = true
|
|
||||||
group.update()
|
|
||||||
return group
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resize(width: Int, height: Int) {
|
|
||||||
if (stage.viewport.screenWidth != width || stage.viewport.screenHeight != height) {
|
|
||||||
game.setScreen(CivilopediaScreen(game.worldScreen.gameInfo.ruleSet))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
107
core/src/com/unciv/ui/civilopedia/CivilopediaCategories.kt
Normal file
107
core/src/com/unciv/ui/civilopedia/CivilopediaCategories.kt
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package com.unciv.ui.civilopedia
|
||||||
|
|
||||||
|
import com.badlogic.gdx.graphics.Color
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.Actor
|
||||||
|
import com.unciv.Constants
|
||||||
|
import com.unciv.logic.map.TileInfo
|
||||||
|
import com.unciv.models.ruleset.Ruleset
|
||||||
|
import com.unciv.models.ruleset.tile.Terrain
|
||||||
|
import com.unciv.models.ruleset.tile.TerrainType
|
||||||
|
import com.unciv.ui.tilegroups.TileGroup
|
||||||
|
import com.unciv.ui.tilegroups.TileSetStrings
|
||||||
|
import com.unciv.ui.utils.ImageGetter
|
||||||
|
import com.unciv.ui.utils.surroundWithCircle
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/** Encapsulates the knowledge on how to get an icon for each of the Civilopedia categories */
|
||||||
|
object CivilopediaImageGetters {
|
||||||
|
private const val policyIconFolder = "PolicyIcons"
|
||||||
|
|
||||||
|
// Todo: potential synergy with map editor
|
||||||
|
fun terrainImage(terrain: Terrain, ruleset: Ruleset, imageSize: Float): Actor {
|
||||||
|
val tileInfo = TileInfo()
|
||||||
|
tileInfo.ruleset = ruleset
|
||||||
|
when (terrain.type) {
|
||||||
|
TerrainType.NaturalWonder -> {
|
||||||
|
tileInfo.naturalWonder = terrain.name
|
||||||
|
tileInfo.baseTerrain = terrain.turnsInto ?: Constants.grassland
|
||||||
|
}
|
||||||
|
TerrainType.TerrainFeature -> {
|
||||||
|
tileInfo.terrainFeatures.add(terrain.name)
|
||||||
|
tileInfo.baseTerrain = terrain.occursOn.lastOrNull() ?: Constants.grassland
|
||||||
|
}
|
||||||
|
else ->
|
||||||
|
tileInfo.baseTerrain = terrain.name
|
||||||
|
}
|
||||||
|
tileInfo.setTerrainTransients()
|
||||||
|
val group = TileGroup(tileInfo, TileSetStrings(), imageSize * 36f/54f) // TileGroup normally spills out of its bounding box
|
||||||
|
group.showEntireMap = true
|
||||||
|
group.forMapEditorIcon = true
|
||||||
|
group.update()
|
||||||
|
return group
|
||||||
|
}
|
||||||
|
|
||||||
|
val construction = { name: String, size: Float ->
|
||||||
|
ImageGetter.getConstructionImage(name)
|
||||||
|
.surroundWithCircle(size, color = Color.WHITE)
|
||||||
|
}
|
||||||
|
val improvement = { name: String, size: Float ->
|
||||||
|
ImageGetter.getImprovementIcon(name, size)
|
||||||
|
}
|
||||||
|
val nation = { name: String, size: Float ->
|
||||||
|
val nation = ImageGetter.ruleset.nations[name]
|
||||||
|
if (nation == null) null
|
||||||
|
else ImageGetter.getNationIndicator(nation, size)
|
||||||
|
}
|
||||||
|
val policy = { name: String, size: Float ->
|
||||||
|
ImageGetter.getImage(policyIconFolder + File.separator + name)
|
||||||
|
.apply { setSize(size,size) }
|
||||||
|
}
|
||||||
|
val resource = { name: String, size: Float ->
|
||||||
|
ImageGetter.getResourceImage(name, size)
|
||||||
|
}
|
||||||
|
val technology = { name: String, size: Float ->
|
||||||
|
ImageGetter.getTechIconGroup(name, size)
|
||||||
|
}
|
||||||
|
val promotion = { name: String, size: Float ->
|
||||||
|
ImageGetter.getPromotionIcon(name, size)
|
||||||
|
}
|
||||||
|
val terrain = { name: String, size: Float ->
|
||||||
|
val terrain = ImageGetter.ruleset.terrains[name]
|
||||||
|
if (terrain == null) null
|
||||||
|
else terrainImage(terrain, ImageGetter.ruleset, size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Enum used as keys for Civilopedia "pages" (categories).
|
||||||
|
*
|
||||||
|
* Note names are singular on purpose - a "link" allows both key and label
|
||||||
|
*
|
||||||
|
* @param label Translatable caption for the Civilopedia button
|
||||||
|
*/
|
||||||
|
enum class CivilopediaCategories (
|
||||||
|
val label: String,
|
||||||
|
val hide: Boolean, // Omitted on CivilopediaScreen
|
||||||
|
val getImage: ((name: String, size: Float) -> Actor?)?
|
||||||
|
) {
|
||||||
|
Building ("Buildings", false, CivilopediaImageGetters.construction ),
|
||||||
|
Wonder ("Wonders", false, CivilopediaImageGetters.construction ),
|
||||||
|
Resource ("Resources", false, CivilopediaImageGetters.resource ),
|
||||||
|
Terrain ("Terrains", false, CivilopediaImageGetters.terrain ),
|
||||||
|
Improvement ("Tile Improvements", false, CivilopediaImageGetters.improvement ),
|
||||||
|
Unit ("Units", false, CivilopediaImageGetters.construction ),
|
||||||
|
Nation ("Nations", false, CivilopediaImageGetters.nation ),
|
||||||
|
Technology ("Technologies", false, CivilopediaImageGetters.technology ),
|
||||||
|
Promotion ("Promotions", false, CivilopediaImageGetters.promotion ),
|
||||||
|
Policy ("Policies", true, CivilopediaImageGetters.policy ),
|
||||||
|
Tutorial ("Tutorials", false, null ),
|
||||||
|
Difficulty ("Difficulty levels", false, null ),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromLink(name: String): CivilopediaCategories? =
|
||||||
|
values().firstOrNull { it.name == name }
|
||||||
|
?: values().firstOrNull { it.label == name }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
303
core/src/com/unciv/ui/civilopedia/CivilopediaScreen.kt
Normal file
303
core/src/com/unciv/ui/civilopedia/CivilopediaScreen.kt
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
package com.unciv.ui.civilopedia
|
||||||
|
|
||||||
|
import com.badlogic.gdx.graphics.Color
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.Actor
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.Touchable
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.*
|
||||||
|
import com.unciv.Constants
|
||||||
|
import com.unciv.UncivGame
|
||||||
|
import com.unciv.models.ruleset.Ruleset
|
||||||
|
import com.unciv.models.stats.INamed
|
||||||
|
import com.unciv.models.translations.tr
|
||||||
|
import com.unciv.ui.utils.*
|
||||||
|
import com.unciv.ui.utils.AutoScrollPane as ScrollPane
|
||||||
|
|
||||||
|
/** Screen displaying the Civilopedia
|
||||||
|
* @param ruleset [Ruleset] to display items from
|
||||||
|
* @param category [CivilopediaCategories] key to select category
|
||||||
|
* @param link alternate selector to select category and/or entry. Can have the form `category/entry`
|
||||||
|
* overriding the [category] parameter, or just `entry` to complement it.
|
||||||
|
*/
|
||||||
|
class CivilopediaScreen(
|
||||||
|
val ruleset: Ruleset
|
||||||
|
, category: CivilopediaCategories = CivilopediaCategories.Tutorial
|
||||||
|
, link: String = ""
|
||||||
|
) : CameraStageBaseScreen() {
|
||||||
|
|
||||||
|
/** Container collecting data per Civilopedia entry
|
||||||
|
* @param name From [Ruleset] object [INamed.name]
|
||||||
|
* @param description Multiline text
|
||||||
|
* @param image Icon for button
|
||||||
|
* @param y Y coordinate for scrolling to
|
||||||
|
* @param height Cell height
|
||||||
|
*/
|
||||||
|
private class CivilopediaEntry (
|
||||||
|
val name: String,
|
||||||
|
val description: String,
|
||||||
|
val image: Actor? = 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val categoryToEntries = LinkedHashMap<CivilopediaCategories, Collection<CivilopediaEntry>>()
|
||||||
|
private val categoryToButtons = LinkedHashMap<CivilopediaCategories, Button>()
|
||||||
|
private val entryIndex = LinkedHashMap<String, CivilopediaEntry>()
|
||||||
|
|
||||||
|
private val entrySelectTable = Table().apply { defaults().pad(6f).left() }
|
||||||
|
private val entrySelectScroll: ScrollPane
|
||||||
|
private val descriptionLabel = "".toLabel()
|
||||||
|
|
||||||
|
private var currentCategory: CivilopediaCategories = CivilopediaCategories.Tutorial
|
||||||
|
private var currentEntry: String = ""
|
||||||
|
|
||||||
|
/** Jump to a "link" selecting both category and entry
|
||||||
|
*
|
||||||
|
* Calls [selectCategory] with the substring before the first '/',
|
||||||
|
*
|
||||||
|
* and [selectEntry] with the substring after the first '/'
|
||||||
|
*
|
||||||
|
* @param link Link in the form Category/Entry
|
||||||
|
*/
|
||||||
|
private fun selectLink(link: String) {
|
||||||
|
val parts = link.split('/', limit = 2)
|
||||||
|
if (parts.isEmpty()) return
|
||||||
|
selectCategory(parts[0])
|
||||||
|
if (parts.size >= 2) selectEntry(parts[1], noScrollAnimation = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Select a specified category
|
||||||
|
* @param name Category name or label
|
||||||
|
*/
|
||||||
|
fun selectCategory(name: String) {
|
||||||
|
val category = CivilopediaCategories.fromLink(name)
|
||||||
|
?: return // silently ignore unknown category names in links
|
||||||
|
selectCategory(category)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Select a specified category - unselects entry, rebuilds left side buttons.
|
||||||
|
* @param category Category key
|
||||||
|
*/
|
||||||
|
fun selectCategory(category: CivilopediaCategories) {
|
||||||
|
currentCategory = category
|
||||||
|
entrySelectTable.clear()
|
||||||
|
entryIndex.clear()
|
||||||
|
descriptionLabel.setText("")
|
||||||
|
|
||||||
|
for (button in categoryToButtons.values) button.color = Color.WHITE
|
||||||
|
if (category !in categoryToButtons) return // defense against being passed a bad selector
|
||||||
|
categoryToButtons[category]!!.color = Color.BLUE
|
||||||
|
|
||||||
|
if (category !in categoryToEntries) return // defense, allowing buggy panes to remain emtpy while others work
|
||||||
|
var entries = categoryToEntries[category]!!
|
||||||
|
if (category != CivilopediaCategories.Difficulty) // this is the only case where we need them in order
|
||||||
|
entries = entries.sortedBy { it.name.tr() } // Alphabetical order of localized names
|
||||||
|
var currentY = -1f
|
||||||
|
|
||||||
|
for (entry in entries) {
|
||||||
|
val entryButton = Table().apply {
|
||||||
|
background = ImageGetter.getBackground(colorFromRGB(50, 75, 125))
|
||||||
|
touchable = Touchable.enabled
|
||||||
|
}
|
||||||
|
if (entry.image != null)
|
||||||
|
if (category == CivilopediaCategories.Terrain)
|
||||||
|
entryButton.add(entry.image).padLeft(20f).padRight(10f)
|
||||||
|
else
|
||||||
|
entryButton.add(entry.image).padLeft(10f)
|
||||||
|
entryButton.left().add(entry.name.toLabel(Color.WHITE, 25)).pad(10f)
|
||||||
|
entryButton.onClick { selectEntry(entry) }
|
||||||
|
entryButton.name = entry.name // make button findable
|
||||||
|
val cell = entrySelectTable.add(entryButton).height(75f).expandX().fillX()
|
||||||
|
entrySelectTable.row()
|
||||||
|
if (currentY < 0f) currentY = cell.padTop
|
||||||
|
entryIndex[entry.name] = entry.withCoordinates(currentY, cell.prefHeight)
|
||||||
|
currentY += cell.padBottom + cell.prefHeight + cell.padTop
|
||||||
|
}
|
||||||
|
|
||||||
|
entrySelectScroll.layout() // necessary for positioning in selectRow to work
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Select a specified entry within the current category. Unknown strings are ignored!
|
||||||
|
* @param name Entry (Ruleset object) name
|
||||||
|
* @param noScrollAnimation Disable scroll animation
|
||||||
|
*/
|
||||||
|
fun selectEntry(name: String, noScrollAnimation: Boolean = false) {
|
||||||
|
val entry = entryIndex[name] ?: return
|
||||||
|
// fails: entrySelectScroll.scrollTo(0f, entry.y, 0f, entry.h, false, true)
|
||||||
|
entrySelectScroll.let {
|
||||||
|
it.scrollY = (entry.y + (entry.height - it.height) / 2).coerceIn(0f, it.maxY)
|
||||||
|
}
|
||||||
|
if (noScrollAnimation)
|
||||||
|
entrySelectScroll.updateVisualScroll() // snap without animation on fresh pedia open
|
||||||
|
selectEntry(entry)
|
||||||
|
}
|
||||||
|
private fun selectEntry(entry: CivilopediaEntry) {
|
||||||
|
currentEntry = entry.name
|
||||||
|
descriptionLabel.setText(entry.description)
|
||||||
|
entrySelectTable.children.forEach {
|
||||||
|
it.color = if (it.name == entry.name) Color.BLUE else Color.WHITE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
val imageSize = 50f
|
||||||
|
onBackButtonClicked { UncivGame.Current.setWorldScreen() }
|
||||||
|
|
||||||
|
categoryToEntries[CivilopediaCategories.Building] = ruleset.buildings.values
|
||||||
|
.filter { "Will not be displayed in Civilopedia" !in it.uniques && !(it.isWonder || it.isNationalWonder) }
|
||||||
|
.map {
|
||||||
|
CivilopediaEntry(
|
||||||
|
it.name,
|
||||||
|
it.getDescription(false, null, ruleset),
|
||||||
|
CivilopediaCategories.Building.getImage?.invoke(it.name, imageSize)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
categoryToEntries[CivilopediaCategories.Wonder] = ruleset.buildings.values
|
||||||
|
.filter { "Will not be displayed in Civilopedia" !in it.uniques && (it.isWonder || it.isNationalWonder) }
|
||||||
|
.map {
|
||||||
|
CivilopediaEntry(
|
||||||
|
it.name,
|
||||||
|
it.getDescription(false, null, ruleset),
|
||||||
|
CivilopediaCategories.Wonder.getImage?.invoke(it.name, imageSize)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
categoryToEntries[CivilopediaCategories.Resource] = ruleset.tileResources.values
|
||||||
|
.map {
|
||||||
|
CivilopediaEntry(
|
||||||
|
it.name,
|
||||||
|
it.getDescription(ruleset),
|
||||||
|
CivilopediaCategories.Resource.getImage?.invoke(it.name, imageSize)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
categoryToEntries[CivilopediaCategories.Terrain] = ruleset.terrains.values
|
||||||
|
.map {
|
||||||
|
CivilopediaEntry(
|
||||||
|
it.name,
|
||||||
|
it.getDescription(ruleset),
|
||||||
|
CivilopediaCategories.Terrain.getImage?.invoke(it.name, imageSize)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
categoryToEntries[CivilopediaCategories.Improvement] = ruleset.tileImprovements.values
|
||||||
|
.map {
|
||||||
|
CivilopediaEntry(
|
||||||
|
it.name,
|
||||||
|
it.getDescription(ruleset, false),
|
||||||
|
CivilopediaCategories.Improvement.getImage?.invoke(it.name, imageSize)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
categoryToEntries[CivilopediaCategories.Unit] = ruleset.units.values
|
||||||
|
.filter { "Will not be displayed in Civilopedia" !in it.uniques }
|
||||||
|
.map {
|
||||||
|
CivilopediaEntry(
|
||||||
|
it.name,
|
||||||
|
it.getDescription(false),
|
||||||
|
CivilopediaCategories.Unit.getImage?.invoke(it.name, imageSize)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
categoryToEntries[CivilopediaCategories.Nation] = ruleset.nations.values
|
||||||
|
.filter { it.isMajorCiv() }
|
||||||
|
.map {
|
||||||
|
CivilopediaEntry(
|
||||||
|
it.name,
|
||||||
|
it.getUniqueString(ruleset, false),
|
||||||
|
CivilopediaCategories.Nation.getImage?.invoke(it.name, imageSize)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
categoryToEntries[CivilopediaCategories.Technology] = ruleset.technologies.values
|
||||||
|
.map {
|
||||||
|
CivilopediaEntry(
|
||||||
|
it.name,
|
||||||
|
it.getDescription(ruleset),
|
||||||
|
CivilopediaCategories.Technology.getImage?.invoke(it.name, imageSize)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
categoryToEntries[CivilopediaCategories.Promotion] = ruleset.unitPromotions.values
|
||||||
|
.map {
|
||||||
|
CivilopediaEntry(
|
||||||
|
it.name,
|
||||||
|
it.getDescription(ruleset.unitPromotions.values, true, ruleset),
|
||||||
|
CivilopediaCategories.Promotion.getImage?.invoke(it.name, imageSize)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
categoryToEntries[CivilopediaCategories.Tutorial] = tutorialController.getCivilopediaTutorials()
|
||||||
|
.map {
|
||||||
|
CivilopediaEntry(
|
||||||
|
it.key.replace("_", " "),
|
||||||
|
it.value.joinToString("\n\n") { line -> line.tr() },
|
||||||
|
// CivilopediaCategories.Tutorial.getImage?.invoke(it.name, imageSize)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
categoryToEntries[CivilopediaCategories.Difficulty] = ruleset.difficulties.values
|
||||||
|
.map {
|
||||||
|
CivilopediaEntry(
|
||||||
|
it.name,
|
||||||
|
it.getDescription(),
|
||||||
|
// CivilopediaCategories.Difficulty.getImage?.invoke(it.name, imageSize)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val buttonTable = Table()
|
||||||
|
buttonTable.pad(15f)
|
||||||
|
buttonTable.defaults().pad(10f)
|
||||||
|
|
||||||
|
for (categoryKey in categoryToEntries.keys) {
|
||||||
|
val button = categoryKey.label.toTextButton()
|
||||||
|
button.style = TextButton.TextButtonStyle(button.style)
|
||||||
|
categoryToButtons[categoryKey] = button
|
||||||
|
button.onClick { selectCategory(categoryKey) }
|
||||||
|
buttonTable.add(button)
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonTable.pack()
|
||||||
|
buttonTable.width = stage.width
|
||||||
|
val buttonTableScroll = ScrollPane(buttonTable)
|
||||||
|
buttonTableScroll.setScrollingDisabled(false, true)
|
||||||
|
|
||||||
|
val goToGameButton = Constants.close.toTextButton()
|
||||||
|
goToGameButton.onClick {
|
||||||
|
game.setWorldScreen()
|
||||||
|
dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
val topTable = Table()
|
||||||
|
topTable.add(goToGameButton).pad(10f)
|
||||||
|
topTable.add(buttonTableScroll)
|
||||||
|
topTable.pack()
|
||||||
|
|
||||||
|
val entryTable = Table()
|
||||||
|
val splitPane = SplitPane(topTable, entryTable, true, skin)
|
||||||
|
splitPane.splitAmount = topTable.prefHeight / stage.height
|
||||||
|
entryTable.height = stage.height - topTable.prefHeight
|
||||||
|
splitPane.setFillParent(true)
|
||||||
|
|
||||||
|
stage.addActor(splitPane)
|
||||||
|
|
||||||
|
entrySelectScroll = ScrollPane(entrySelectTable)
|
||||||
|
entrySelectTable.top()
|
||||||
|
entrySelectScroll.setOverscroll(false, false)
|
||||||
|
val descriptionTable = Table()
|
||||||
|
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)
|
||||||
|
entrySplitPane.splitAmount = 0.3f
|
||||||
|
entryTable.addActor(entrySplitPane)
|
||||||
|
entrySplitPane.setFillParent(true)
|
||||||
|
|
||||||
|
if (link.isEmpty() || '/' !in link)
|
||||||
|
selectCategory(category)
|
||||||
|
if (link.isNotEmpty())
|
||||||
|
if ('/' in link)
|
||||||
|
selectLink(link)
|
||||||
|
else
|
||||||
|
selectEntry(link, noScrollAnimation = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun resize(width: Int, height: Int) {
|
||||||
|
if (stage.viewport.screenWidth != width || stage.viewport.screenHeight != height) {
|
||||||
|
game.setScreen(CivilopediaScreen(game.worldScreen.gameInfo.ruleSet, currentCategory, currentEntry))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -25,7 +25,7 @@ import com.unciv.models.UncivSound
|
|||||||
import com.unciv.models.ruleset.tile.ResourceType
|
import com.unciv.models.ruleset.tile.ResourceType
|
||||||
import com.unciv.models.ruleset.unit.UnitType
|
import com.unciv.models.ruleset.unit.UnitType
|
||||||
import com.unciv.models.translations.tr
|
import com.unciv.models.translations.tr
|
||||||
import com.unciv.ui.CivilopediaScreen
|
import com.unciv.ui.civilopedia.CivilopediaScreen
|
||||||
import com.unciv.ui.cityscreen.CityScreen
|
import com.unciv.ui.cityscreen.CityScreen
|
||||||
import com.unciv.ui.overviewscreen.EmpireOverviewScreen
|
import com.unciv.ui.overviewscreen.EmpireOverviewScreen
|
||||||
import com.unciv.ui.pickerscreens.GreatPersonPickerScreen
|
import com.unciv.ui.pickerscreens.GreatPersonPickerScreen
|
||||||
@ -40,19 +40,38 @@ import com.unciv.ui.victoryscreen.VictoryScreen
|
|||||||
import com.unciv.ui.worldscreen.bottombar.BattleTable
|
import com.unciv.ui.worldscreen.bottombar.BattleTable
|
||||||
import com.unciv.ui.worldscreen.bottombar.TileInfoTable
|
import com.unciv.ui.worldscreen.bottombar.TileInfoTable
|
||||||
import com.unciv.ui.worldscreen.mainmenu.OnlineMultiplayer
|
import com.unciv.ui.worldscreen.mainmenu.OnlineMultiplayer
|
||||||
import com.unciv.ui.worldscreen.mainmenu.OptionsPopup
|
|
||||||
import com.unciv.ui.worldscreen.unit.UnitActionsTable
|
import com.unciv.ui.worldscreen.unit.UnitActionsTable
|
||||||
import com.unciv.ui.worldscreen.unit.UnitTable
|
import com.unciv.ui.worldscreen.unit.UnitTable
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
import kotlin.concurrent.timer
|
import kotlin.concurrent.timer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unciv's world screen
|
||||||
|
* @param gameInfo The game state the screen should represent
|
||||||
|
* @param viewingCiv The currently active [civilization][CivilizationInfo]
|
||||||
|
* @see [shouldUpdate]
|
||||||
|
* @see [isPlayersTurn]
|
||||||
|
* @see [selectedCiv]
|
||||||
|
* @see [canChangeState]
|
||||||
|
* @see [mapHolder]
|
||||||
|
* @see [bottomUnitTable]
|
||||||
|
* @see [enableNextTurnButtonAfterOptions]
|
||||||
|
* @property shouldUpdate When set, causes the screen to update in the next [render][CameraStageBaseScreen.render] event
|
||||||
|
* @property isPlayersTurn (readonly) Indicates it's the player's ([viewingCiv]) turn
|
||||||
|
* @property selectedCiv Selected civilization, used in spectator and replay mode, equals viewingCiv in ordinary games
|
||||||
|
* @property canChangeState (readonly) `true` when it's the player's turn unless he is a spectator
|
||||||
|
* @property mapHolder A [MinimapHolder] instance
|
||||||
|
* @property bottomUnitTable Bottom left widget holding information about a selected unit or city
|
||||||
|
*/
|
||||||
class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
|
class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
|
||||||
|
|
||||||
var isPlayersTurn = viewingCiv == gameInfo.currentPlayerCiv
|
var isPlayersTurn = viewingCiv == gameInfo.currentPlayerCiv
|
||||||
var selectedCiv = viewingCiv // Selected civilization, used in spectator and replay mode, equals viewingCiv in ordinary games
|
private set // only this class is allowed to make changes
|
||||||
|
var selectedCiv = viewingCiv
|
||||||
private var fogOfWar = true
|
private var fogOfWar = true
|
||||||
val canChangeState = isPlayersTurn && !viewingCiv.isSpectator()
|
val canChangeState
|
||||||
|
get() = isPlayersTurn && !viewingCiv.isSpectator()
|
||||||
private var waitingForAutosave = false
|
private var waitingForAutosave = false
|
||||||
|
|
||||||
val mapHolder = WorldMapHolder(this, gameInfo.tileMap)
|
val mapHolder = WorldMapHolder(this, gameInfo.tileMap)
|
||||||
@ -140,9 +159,9 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Cam
|
|||||||
|
|
||||||
// Don't select unit and change selectedCiv when centering as spectator
|
// Don't select unit and change selectedCiv when centering as spectator
|
||||||
if (viewingCiv.isSpectator())
|
if (viewingCiv.isSpectator())
|
||||||
mapHolder.setCenterPosition(tileToCenterOn, true, false)
|
mapHolder.setCenterPosition(tileToCenterOn, immediately = true, selectUnit = false)
|
||||||
else
|
else
|
||||||
mapHolder.setCenterPosition(tileToCenterOn, true, true)
|
mapHolder.setCenterPosition(tileToCenterOn, immediately = true, selectUnit = true)
|
||||||
|
|
||||||
|
|
||||||
tutorialController.allTutorialsShowedCallback = { shouldUpdate = true }
|
tutorialController.allTutorialsShowedCallback = { shouldUpdate = true }
|
||||||
@ -544,7 +563,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Cam
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun createNewWorldScreen(gameInfo: GameInfo) {
|
private fun createNewWorldScreen(gameInfo: GameInfo) {
|
||||||
|
|
||||||
game.gameInfo = gameInfo
|
game.gameInfo = gameInfo
|
||||||
val newWorldScreen = WorldScreen(gameInfo, gameInfo.getPlayerToViewAs())
|
val newWorldScreen = WorldScreen(gameInfo, gameInfo.getPlayerToViewAs())
|
||||||
@ -596,7 +615,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Cam
|
|||||||
|
|
||||||
val shouldAutoSave = gameInfoClone.turns % game.settings.turnsBetweenAutosaves == 0
|
val shouldAutoSave = gameInfoClone.turns % game.settings.turnsBetweenAutosaves == 0
|
||||||
|
|
||||||
// create a new worldscreen to show the new stuff we've changed, and switch out the current screen.
|
// create a new WorldScreen to show the new stuff we've changed, and switch out the current screen.
|
||||||
// do this on main thread - it's the only one that has a GL context to create images from
|
// do this on main thread - it's the only one that has a GL context to create images from
|
||||||
Gdx.app.postRunnable {
|
Gdx.app.postRunnable {
|
||||||
|
|
||||||
@ -635,6 +654,10 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Cam
|
|||||||
nextTurnButton.setPosition(stage.width - nextTurnButton.width - 10f, topBar.y - nextTurnButton.height - 10f)
|
nextTurnButton.setPosition(stage.width - nextTurnButton.width - 10f, topBar.y - nextTurnButton.height - 10f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by [OptionsPopup][com.unciv.ui.worldscreen.mainmenu.OptionsPopup]
|
||||||
|
* to re-enable the next turn button within its Close button action
|
||||||
|
*/
|
||||||
fun enableNextTurnButtonAfterOptions() {
|
fun enableNextTurnButtonAfterOptions() {
|
||||||
nextTurnButton.isEnabled = isPlayersTurn && !waitingForAutosave
|
nextTurnButton.isEnabled = isPlayersTurn && !waitingForAutosave
|
||||||
}
|
}
|
||||||
@ -647,7 +670,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Cam
|
|||||||
NextTurnAction("Next unit", Color.LIGHT_GRAY) {
|
NextTurnAction("Next unit", Color.LIGHT_GRAY) {
|
||||||
val nextDueUnit = viewingCiv.getNextDueUnit()
|
val nextDueUnit = viewingCiv.getNextDueUnit()
|
||||||
if (nextDueUnit != null) {
|
if (nextDueUnit != null) {
|
||||||
mapHolder.setCenterPosition(nextDueUnit.currentTile.position, false, false)
|
mapHolder.setCenterPosition(nextDueUnit.currentTile.position, immediately = false, selectUnit = false)
|
||||||
bottomUnitTable.selectUnit(nextDueUnit)
|
bottomUnitTable.selectUnit(nextDueUnit)
|
||||||
shouldUpdate = true
|
shouldUpdate = true
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Cell
|
|||||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||||
import com.unciv.Constants
|
import com.unciv.Constants
|
||||||
import com.unciv.MainMenuScreen
|
import com.unciv.MainMenuScreen
|
||||||
import com.unciv.ui.CivilopediaScreen
|
import com.unciv.ui.civilopedia.CivilopediaScreen
|
||||||
import com.unciv.ui.newgamescreen.GameSetupInfo
|
import com.unciv.ui.newgamescreen.GameSetupInfo
|
||||||
import com.unciv.ui.newgamescreen.NewGameScreen
|
import com.unciv.ui.newgamescreen.NewGameScreen
|
||||||
import com.unciv.ui.saves.LoadGameScreen
|
import com.unciv.ui.saves.LoadGameScreen
|
||||||
|
Reference in New Issue
Block a user