mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-26 21:35:14 -04:00
Refactor: Split options into multiple files (#6857)
* Refactor: Move OptionsPopup to own package * Refactor: Split OptionsPopup into multiple classes # Conflicts: # core/src/com/unciv/ui/options/OptionsPopup.kt
This commit is contained in:
parent
a128ea0d59
commit
81379078fa
@ -7,7 +7,7 @@ import com.unciv.ui.utils.enable
|
||||
import com.unciv.ui.utils.onClick
|
||||
import com.unciv.ui.utils.LanguageTable
|
||||
import com.unciv.ui.utils.LanguageTable.Companion.addLanguageTables
|
||||
import com.unciv.ui.worldscreen.mainmenu.OptionsPopup
|
||||
import com.unciv.ui.options.OptionsPopup
|
||||
|
||||
/** A [PickerScreen] to select a language, used once on the initial run after a fresh install.
|
||||
* After that, [OptionsPopup] provides the functionality.
|
||||
|
19
core/src/com/unciv/ui/options/AboutTab.kt
Normal file
19
core/src/com/unciv/ui/options/AboutTab.kt
Normal file
@ -0,0 +1,19 @@
|
||||
package com.unciv.ui.options
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.unciv.ui.civilopedia.FormattedLine
|
||||
import com.unciv.ui.civilopedia.MarkupRenderer
|
||||
import com.unciv.ui.utils.BaseScreen
|
||||
|
||||
fun aboutTab(screen: BaseScreen): Table {
|
||||
val version = screen.game.version
|
||||
val versionAnchor = version.replace(".", "")
|
||||
val lines = sequence {
|
||||
yield(FormattedLine(extraImage = "banner", imageSize = 240f, centered = true))
|
||||
yield(FormattedLine())
|
||||
yield(FormattedLine("{Version}: $version", link = "https://github.com/yairm210/Unciv/blob/master/changelog.md#$versionAnchor"))
|
||||
yield(FormattedLine("See online Readme", link = "https://github.com/yairm210/Unciv/blob/master/README.md#unciv---foss-civ-v-for-androiddesktop"))
|
||||
yield(FormattedLine("Visit repository", link = "https://github.com/yairm210/Unciv"))
|
||||
}
|
||||
return MarkupRenderer.render(lines.toList()).pad(20f)
|
||||
}
|
179
core/src/com/unciv/ui/options/AdvancedTab.kt
Normal file
179
core/src/com/unciv/ui/options/AdvancedTab.kt
Normal file
@ -0,0 +1,179 @@
|
||||
package com.unciv.ui.options
|
||||
|
||||
import com.badlogic.gdx.Application
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.Input
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
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.Table
|
||||
import com.badlogic.gdx.utils.Array
|
||||
import com.unciv.models.metadata.GameSettings
|
||||
import com.unciv.models.translations.TranslationFileWriter
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.crashhandling.launchCrashHandling
|
||||
import com.unciv.ui.crashhandling.postCrashHandlingRunnable
|
||||
import com.unciv.ui.popup.YesNoPopup
|
||||
import com.unciv.ui.utils.*
|
||||
import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip
|
||||
import java.util.*
|
||||
|
||||
fun advancedTab(
|
||||
optionsPopup: OptionsPopup,
|
||||
onFontChange: () -> Unit
|
||||
) = Table(BaseScreen.skin).apply {
|
||||
pad(10f)
|
||||
defaults().pad(5f)
|
||||
|
||||
val settings = optionsPopup.settings
|
||||
|
||||
addAutosaveTurnsSelectBox(this, settings)
|
||||
|
||||
optionsPopup.addCheckbox(
|
||||
this, "{Show experimental world wrap for maps}\n{HIGHLY EXPERIMENTAL - YOU HAVE BEEN WARNED!}",
|
||||
settings.showExperimentalWorldWrap
|
||||
) {
|
||||
settings.showExperimentalWorldWrap = it
|
||||
}
|
||||
|
||||
addMaxZoomSlider(this, settings)
|
||||
|
||||
val screen = optionsPopup.screen
|
||||
if (screen.game.platformSpecificHelper != null) {
|
||||
optionsPopup.addCheckbox(this, "Enable portrait orientation", settings.allowAndroidPortrait) {
|
||||
settings.allowAndroidPortrait = it
|
||||
// Note the following might close the options screen indirectly and delayed
|
||||
screen.game.platformSpecificHelper.allowPortrait(it)
|
||||
}
|
||||
}
|
||||
|
||||
addFontFamilySelect(this, settings, optionsPopup.selectBoxMinWidth, onFontChange)
|
||||
|
||||
addTranslationGeneration(this, optionsPopup)
|
||||
|
||||
addSetUserId(this, settings, screen)
|
||||
}
|
||||
|
||||
private fun addAutosaveTurnsSelectBox(table: Table, settings: GameSettings) {
|
||||
table.add("Turns between autosaves".toLabel()).left().fillX()
|
||||
|
||||
val autosaveTurnsSelectBox = SelectBox<Int>(table.skin)
|
||||
val autosaveTurnsArray = Array<Int>()
|
||||
autosaveTurnsArray.addAll(1, 2, 5, 10)
|
||||
autosaveTurnsSelectBox.items = autosaveTurnsArray
|
||||
autosaveTurnsSelectBox.selected = settings.turnsBetweenAutosaves
|
||||
|
||||
table.add(autosaveTurnsSelectBox).pad(10f).row()
|
||||
|
||||
autosaveTurnsSelectBox.onChange {
|
||||
settings.turnsBetweenAutosaves = autosaveTurnsSelectBox.selected
|
||||
settings.save()
|
||||
}
|
||||
}
|
||||
|
||||
private
|
||||
fun addFontFamilySelect(table: Table, settings: GameSettings, selectBoxMinWidth: Float, onFontChange: () -> Unit) {
|
||||
table.add("Font family".toLabel()).left().fillX()
|
||||
val selectCell = table.add()
|
||||
table.row()
|
||||
|
||||
fun loadFontSelect(fonts: Array<FontFamilyData>, selectCell: Cell<Actor>) {
|
||||
if (fonts.isEmpty) return
|
||||
|
||||
val fontSelectBox = SelectBox<FontFamilyData>(table.skin)
|
||||
fontSelectBox.items = fonts
|
||||
|
||||
// `FontFamilyData` implements kotlin equality contract such that _only_ the invariantName field is compared.
|
||||
// The Gdx SelectBox should honor that - but it doesn't, as it is a _kotlin_ thing to implement
|
||||
// `==` by calling `equals`, and there's precompiled _Java_ `==` in the widget code.
|
||||
// `setSelected` first calls a `contains` which can switch between using `==` and `equals` (set to `equals`)
|
||||
// but just one step later (where it re-checks whether the new selection is equal to the old one)
|
||||
// it does a hard `==`. Also, setSelection copies its argument to the selection var, it doesn't pull a match from `items`.
|
||||
// Therefore, _selecting_ an item in a `SelectBox` by an instance of `FontFamilyData` where only the `invariantName` is valid won't work properly.
|
||||
//
|
||||
// This is why it's _not_ `fontSelectBox.selected = FontFamilyData(settings.fontFamily)`
|
||||
val fontToSelect = settings.fontFamily
|
||||
fontSelectBox.selected = fonts.firstOrNull { it.invariantName == fontToSelect } // will default to first entry if `null` is passed
|
||||
|
||||
selectCell.setActor(fontSelectBox).minWidth(selectBoxMinWidth).pad(10f)
|
||||
|
||||
fontSelectBox.onChange {
|
||||
settings.fontFamily = fontSelectBox.selected.invariantName
|
||||
Fonts.resetFont(settings.fontFamily)
|
||||
onFontChange()
|
||||
}
|
||||
}
|
||||
|
||||
launchCrashHandling("Add Font Select") {
|
||||
// This is a heavy operation and causes ANRs
|
||||
val fonts = Array<FontFamilyData>().apply {
|
||||
add(FontFamilyData.default)
|
||||
for (font in Fonts.getAvailableFontFamilyNames())
|
||||
add(font)
|
||||
}
|
||||
postCrashHandlingRunnable { loadFontSelect(fonts, selectCell) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun addMaxZoomSlider(table: Table, settings: GameSettings) {
|
||||
table.add("Max zoom out".tr()).left().fillX()
|
||||
val maxZoomSlider = UncivSlider(
|
||||
2f, 6f, 1f,
|
||||
initial = settings.maxWorldZoomOut
|
||||
) {
|
||||
settings.maxWorldZoomOut = it
|
||||
settings.save()
|
||||
}
|
||||
table.add(maxZoomSlider).pad(5f).row()
|
||||
}
|
||||
|
||||
private fun addTranslationGeneration(table: Table, optionsPopup: OptionsPopup) {
|
||||
if (Gdx.app.type != Application.ApplicationType.Desktop) return
|
||||
|
||||
val generateTranslationsButton = "Generate translation files".toTextButton()
|
||||
|
||||
val generateAction: () -> Unit = {
|
||||
optionsPopup.tabs.selectPage("Advanced")
|
||||
generateTranslationsButton.setText("Working...".tr())
|
||||
launchCrashHandling("WriteTranslations") {
|
||||
val result = TranslationFileWriter.writeNewTranslationFiles()
|
||||
postCrashHandlingRunnable {
|
||||
// notify about completion
|
||||
generateTranslationsButton.setText(result.tr())
|
||||
generateTranslationsButton.disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
generateTranslationsButton.onClick(generateAction)
|
||||
optionsPopup.keyPressDispatcher[Input.Keys.F12] = generateAction
|
||||
generateTranslationsButton.addTooltip("F12", 18f)
|
||||
table.add(generateTranslationsButton).colspan(2).row()
|
||||
}
|
||||
|
||||
private fun addSetUserId(table: Table, settings: GameSettings, screen: BaseScreen) {
|
||||
val idSetLabel = "".toLabel()
|
||||
val takeUserIdFromClipboardButton = "Take user ID from clipboard".toTextButton()
|
||||
.onClick {
|
||||
try {
|
||||
val clipboardContents = Gdx.app.clipboard.contents.trim()
|
||||
UUID.fromString(clipboardContents)
|
||||
YesNoPopup(
|
||||
"Doing this will reset your current user ID to the clipboard contents - are you sure?",
|
||||
{
|
||||
settings.userId = clipboardContents
|
||||
settings.save()
|
||||
idSetLabel.setFontColor(Color.WHITE).setText("ID successfully set!".tr())
|
||||
},
|
||||
screen
|
||||
).open(true)
|
||||
idSetLabel.isVisible = true
|
||||
} catch (ex: Exception) {
|
||||
idSetLabel.isVisible = true
|
||||
idSetLabel.setFontColor(Color.RED).setText("Invalid ID!".tr())
|
||||
}
|
||||
}
|
||||
table.add(takeUserIdFromClipboardButton).pad(5f).colspan(2).row()
|
||||
table.add(idSetLabel).colspan(2).row()
|
||||
}
|
101
core/src/com/unciv/ui/options/DebugTab.kt
Normal file
101
core/src/com/unciv/ui/options/DebugTab.kt
Normal file
@ -0,0 +1,101 @@
|
||||
package com.unciv.ui.options
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.TextField
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.GameSaver
|
||||
import com.unciv.logic.MapSaver
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
import com.unciv.models.ruleset.tile.ResourceType
|
||||
import com.unciv.ui.utils.*
|
||||
|
||||
fun debugTab() = Table(BaseScreen.skin).apply {
|
||||
pad(10f)
|
||||
defaults().pad(5f)
|
||||
val game = UncivGame.Current
|
||||
|
||||
val simulateButton = "Simulate until turn:".toTextButton()
|
||||
val simulateTextField = TextField(game.simulateUntilTurnForDebug.toString(), BaseScreen.skin)
|
||||
val invalidInputLabel = "This is not a valid integer!".toLabel().also { it.isVisible = false }
|
||||
simulateButton.onClick {
|
||||
val simulateUntilTurns = simulateTextField.text.toIntOrNull()
|
||||
if (simulateUntilTurns == null) {
|
||||
invalidInputLabel.isVisible = true
|
||||
return@onClick
|
||||
}
|
||||
game.simulateUntilTurnForDebug = simulateUntilTurns
|
||||
invalidInputLabel.isVisible = false
|
||||
game.worldScreen.nextTurn()
|
||||
}
|
||||
add(simulateButton)
|
||||
add(simulateTextField).row()
|
||||
add(invalidInputLabel).colspan(2).row()
|
||||
|
||||
add("Supercharged".toCheckBox(game.superchargedForDebug) {
|
||||
game.superchargedForDebug = it
|
||||
}).colspan(2).row()
|
||||
add("View entire map".toCheckBox(game.viewEntireMapForDebug) {
|
||||
game.viewEntireMapForDebug = it
|
||||
}).colspan(2).row()
|
||||
if (game.isGameInfoInitialized()) {
|
||||
add("God mode (current game)".toCheckBox(game.gameInfo.gameParameters.godMode) {
|
||||
game.gameInfo.gameParameters.godMode = it
|
||||
}).colspan(2).row()
|
||||
}
|
||||
add("Save games compressed".toCheckBox(GameSaver.saveZipped) {
|
||||
GameSaver.saveZipped = it
|
||||
}).colspan(2).row()
|
||||
add("Save maps compressed".toCheckBox(MapSaver.saveZipped) {
|
||||
MapSaver.saveZipped = it
|
||||
}).colspan(2).row()
|
||||
|
||||
add("Gdx Scene2D debug".toCheckBox(BaseScreen.enableSceneDebug) {
|
||||
BaseScreen.enableSceneDebug = it
|
||||
}).colspan(2).row()
|
||||
|
||||
add("Allow untyped Uniques in mod checker".toCheckBox(RulesetCache.modCheckerAllowUntypedUniques) {
|
||||
RulesetCache.modCheckerAllowUntypedUniques = it
|
||||
}).colspan(2).row()
|
||||
|
||||
add(Table().apply {
|
||||
add("Unique misspelling threshold".toLabel()).left().fillX()
|
||||
add(
|
||||
UncivSlider(0f, 0.5f, 0.05f, initial = RulesetCache.uniqueMisspellingThreshold.toFloat()) {
|
||||
RulesetCache.uniqueMisspellingThreshold = it.toDouble()
|
||||
}
|
||||
).minWidth(120f).pad(5f)
|
||||
}).colspan(2).row()
|
||||
|
||||
val unlockTechsButton = "Unlock all techs".toTextButton()
|
||||
unlockTechsButton.onClick {
|
||||
if (!game.isGameInfoInitialized())
|
||||
return@onClick
|
||||
for (tech in game.gameInfo.ruleSet.technologies.keys) {
|
||||
if (tech !in game.gameInfo.getCurrentPlayerCivilization().tech.techsResearched) {
|
||||
game.gameInfo.getCurrentPlayerCivilization().tech.addTechnology(tech)
|
||||
game.gameInfo.getCurrentPlayerCivilization().popupAlerts.removeLastOrNull()
|
||||
}
|
||||
}
|
||||
game.gameInfo.getCurrentPlayerCivilization().updateSightAndResources()
|
||||
game.worldScreen.shouldUpdate = true
|
||||
}
|
||||
add(unlockTechsButton).colspan(2).row()
|
||||
|
||||
val giveResourcesButton = "Get all strategic resources".toTextButton()
|
||||
giveResourcesButton.onClick {
|
||||
if (!game.isGameInfoInitialized())
|
||||
return@onClick
|
||||
val ownedTiles = game.gameInfo.tileMap.values.asSequence().filter { it.getOwner() == game.gameInfo.getCurrentPlayerCivilization() }
|
||||
val resourceTypes = game.gameInfo.ruleSet.tileResources.values.asSequence().filter { it.resourceType == ResourceType.Strategic }
|
||||
for ((tile, resource) in ownedTiles zip resourceTypes) {
|
||||
tile.resource = resource.name
|
||||
tile.resourceAmount = 999
|
||||
// Debug option, so if it crashes on this that's relatively fine
|
||||
// If this becomes a problem, check if such an improvement exists and otherwise plop down a great improvement or so
|
||||
tile.improvement = resource.getImprovements().first()
|
||||
}
|
||||
game.gameInfo.getCurrentPlayerCivilization().updateSightAndResources()
|
||||
game.worldScreen.shouldUpdate = true
|
||||
}
|
||||
add(giveResourcesButton).colspan(2).row()
|
||||
}
|
122
core/src/com/unciv/ui/options/DisplayTab.kt
Normal file
122
core/src/com/unciv/ui/options/DisplayTab.kt
Normal file
@ -0,0 +1,122 @@
|
||||
package com.unciv.ui.options
|
||||
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.SelectBox
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.badlogic.gdx.utils.Array
|
||||
import com.unciv.models.metadata.GameSettings
|
||||
import com.unciv.models.tilesets.TileSetCache
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.utils.*
|
||||
import com.unciv.ui.worldscreen.WorldScreen
|
||||
|
||||
private val resolutionArray = com.badlogic.gdx.utils.Array(arrayOf("750x500", "900x600", "1050x700", "1200x800", "1500x1000"))
|
||||
|
||||
fun displayTab(
|
||||
optionsPopup: OptionsPopup,
|
||||
onResolutionChange: () -> Unit,
|
||||
onTilesetChange: () -> Unit
|
||||
) = Table(BaseScreen.skin).apply {
|
||||
pad(10f)
|
||||
defaults().pad(2.5f)
|
||||
|
||||
val settings = optionsPopup.settings
|
||||
|
||||
optionsPopup.addCheckbox(this, "Show unit movement arrows", settings.showUnitMovements, true) { settings.showUnitMovements = it }
|
||||
optionsPopup.addCheckbox(this, "Show tile yields", settings.showTileYields, true) { settings.showTileYields = it } // JN
|
||||
optionsPopup.addCheckbox(this, "Show worked tiles", settings.showWorkedTiles, true) { settings.showWorkedTiles = it }
|
||||
optionsPopup.addCheckbox(this, "Show resources and improvements", settings.showResourcesAndImprovements, true) {
|
||||
settings.showResourcesAndImprovements = it
|
||||
}
|
||||
optionsPopup.addCheckbox(this, "Show tutorials", settings.showTutorials, true) { settings.showTutorials = it }
|
||||
optionsPopup.addCheckbox(this, "Show pixel units", settings.showPixelUnits, true) { settings.showPixelUnits = it }
|
||||
optionsPopup.addCheckbox(this, "Show pixel improvements", settings.showPixelImprovements, true) { settings.showPixelImprovements = it }
|
||||
optionsPopup.addCheckbox(this, "Experimental Demographics scoreboard", settings.useDemographics, true) { settings.useDemographics = it }
|
||||
|
||||
addMinimapSizeSlider(this, settings, optionsPopup.screen, optionsPopup.selectBoxMinWidth)
|
||||
|
||||
addResolutionSelectBox(this, settings, optionsPopup.selectBoxMinWidth, onResolutionChange)
|
||||
|
||||
addTileSetSelectBox(this, settings, optionsPopup.selectBoxMinWidth, onTilesetChange)
|
||||
|
||||
optionsPopup.addCheckbox(this, "Continuous rendering", settings.continuousRendering) {
|
||||
settings.continuousRendering = it
|
||||
Gdx.graphics.isContinuousRendering = it
|
||||
}
|
||||
|
||||
val continuousRenderingDescription = "When disabled, saves battery life but certain animations will be suspended"
|
||||
val continuousRenderingLabel = WrappableLabel(
|
||||
continuousRenderingDescription,
|
||||
optionsPopup.tabs.prefWidth, Color.ORANGE.brighten(0.7f), 14
|
||||
)
|
||||
continuousRenderingLabel.wrap = true
|
||||
add(continuousRenderingLabel).colspan(2).padTop(10f).row()
|
||||
|
||||
}
|
||||
|
||||
private fun addMinimapSizeSlider(table: Table, settings: GameSettings, screen: BaseScreen, selectBoxMinWidth: Float) {
|
||||
table.add("Show minimap".toLabel()).left().fillX()
|
||||
|
||||
// The meaning of the values needs a formula to be synchronized between here and
|
||||
// [Minimap.init]. It goes off-10%-11%..29%-30%-35%-40%-45%-50% - and the percentages
|
||||
// correspond roughly to the minimap's proportion relative to screen dimensions.
|
||||
val offTranslated = "off".tr() // translate only once and cache in closure
|
||||
val getTipText: (Float) -> String = {
|
||||
when (it) {
|
||||
0f -> offTranslated
|
||||
in 0.99f..21.01f -> "%.0f".format(it + 9) + "%"
|
||||
else -> "%.0f".format(it * 5 - 75) + "%"
|
||||
}
|
||||
}
|
||||
val minimapSlider = UncivSlider(
|
||||
0f, 25f, 1f,
|
||||
initial = if (settings.showMinimap) settings.minimapSize.toFloat() else 0f,
|
||||
getTipText = getTipText
|
||||
) {
|
||||
val size = it.toInt()
|
||||
if (size == 0) settings.showMinimap = false
|
||||
else {
|
||||
settings.showMinimap = true
|
||||
settings.minimapSize = size
|
||||
}
|
||||
settings.save()
|
||||
if (screen is WorldScreen)
|
||||
screen.shouldUpdate = true
|
||||
}
|
||||
table.add(minimapSlider).minWidth(selectBoxMinWidth).pad(10f).row()
|
||||
}
|
||||
|
||||
private fun addResolutionSelectBox(table: Table, settings: GameSettings, selectBoxMinWidth: Float, onResolutionChange: () -> Unit) {
|
||||
table.add("Resolution".toLabel()).left().fillX()
|
||||
|
||||
val resolutionSelectBox = SelectBox<String>(table.skin)
|
||||
resolutionSelectBox.items = resolutionArray
|
||||
resolutionSelectBox.selected = settings.resolution
|
||||
table.add(resolutionSelectBox).minWidth(selectBoxMinWidth).pad(10f).row()
|
||||
|
||||
resolutionSelectBox.onChange {
|
||||
settings.resolution = resolutionSelectBox.selected
|
||||
onResolutionChange()
|
||||
}
|
||||
}
|
||||
|
||||
private fun addTileSetSelectBox(table: Table, settings: GameSettings, selectBoxMinWidth: Float, onTilesetChange: () -> Unit) {
|
||||
table.add("Tileset".toLabel()).left().fillX()
|
||||
|
||||
val tileSetSelectBox = SelectBox<String>(table.skin)
|
||||
val tileSetArray = Array<String>()
|
||||
val tileSets = ImageGetter.getAvailableTilesets()
|
||||
for (tileset in tileSets) tileSetArray.add(tileset)
|
||||
tileSetSelectBox.items = tileSetArray
|
||||
tileSetSelectBox.selected = settings.tileSet
|
||||
table.add(tileSetSelectBox).minWidth(selectBoxMinWidth).pad(10f).row()
|
||||
|
||||
tileSetSelectBox.onChange {
|
||||
settings.tileSet = tileSetSelectBox.selected
|
||||
// ImageGetter ruleset should be correct no matter what screen we're on
|
||||
TileSetCache.assembleTileSetConfigs(ImageGetter.ruleset.mods)
|
||||
onTilesetChange()
|
||||
}
|
||||
}
|
36
core/src/com/unciv/ui/options/GameplayTab.kt
Normal file
36
core/src/com/unciv/ui/options/GameplayTab.kt
Normal file
@ -0,0 +1,36 @@
|
||||
package com.unciv.ui.options
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.unciv.logic.civilization.PlayerType
|
||||
import com.unciv.ui.utils.BaseScreen
|
||||
import com.unciv.ui.worldscreen.WorldScreen
|
||||
|
||||
fun gameplayTab(
|
||||
optionsPopup: OptionsPopup
|
||||
) = Table(BaseScreen.skin).apply {
|
||||
pad(10f)
|
||||
defaults().pad(5f)
|
||||
|
||||
val settings = optionsPopup.settings
|
||||
val screen = optionsPopup.screen
|
||||
|
||||
optionsPopup.addCheckbox(this, "Check for idle units", settings.checkForDueUnits, true) { settings.checkForDueUnits = it }
|
||||
optionsPopup.addCheckbox(this, "Move units with a single tap", settings.singleTapMove) { settings.singleTapMove = it }
|
||||
optionsPopup.addCheckbox(this, "Auto-assign city production", settings.autoAssignCityProduction, true) {
|
||||
settings.autoAssignCityProduction = it
|
||||
if (it && screen is WorldScreen &&
|
||||
screen.viewingCiv.isCurrentPlayer() && screen.viewingCiv.playerType == PlayerType.Human
|
||||
) {
|
||||
screen.gameInfo.currentPlayerCiv.cities.forEach { city ->
|
||||
city.cityConstructions.chooseNextConstruction()
|
||||
}
|
||||
}
|
||||
}
|
||||
optionsPopup.addCheckbox(this, "Auto-build roads", settings.autoBuildingRoads) { settings.autoBuildingRoads = it }
|
||||
optionsPopup.addCheckbox(
|
||||
this,
|
||||
"Automated workers replace improvements",
|
||||
settings.automatedWorkersReplaceImprovements
|
||||
) { settings.automatedWorkersReplaceImprovements = it }
|
||||
optionsPopup.addCheckbox(this, "Order trade offers by amount", settings.orderTradeOffersByAmount) { settings.orderTradeOffersByAmount = it }
|
||||
}
|
37
core/src/com/unciv/ui/options/LanguageTab.kt
Normal file
37
core/src/com/unciv/ui/options/LanguageTab.kt
Normal file
@ -0,0 +1,37 @@
|
||||
package com.unciv.ui.options
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.unciv.ui.utils.BaseScreen
|
||||
import com.unciv.ui.utils.LanguageTable.Companion.addLanguageTables
|
||||
import com.unciv.ui.utils.onClick
|
||||
|
||||
fun languageTab(
|
||||
optionsPopup: OptionsPopup,
|
||||
onLanguageSelected: () -> Unit
|
||||
): Table = Table(BaseScreen.skin).apply {
|
||||
val settings = optionsPopup.settings
|
||||
|
||||
val languageTables = this.addLanguageTables(optionsPopup.tabs.prefWidth * 0.9f - 10f)
|
||||
|
||||
var chosenLanguage = settings.language
|
||||
fun selectLanguage() {
|
||||
settings.language = chosenLanguage
|
||||
settings.updateLocaleFromLanguage()
|
||||
optionsPopup.screen.game.translations.tryReadTranslationForCurrentLanguage()
|
||||
onLanguageSelected()
|
||||
}
|
||||
|
||||
fun updateSelection() {
|
||||
languageTables.forEach { it.update(chosenLanguage) }
|
||||
if (chosenLanguage != settings.language)
|
||||
selectLanguage()
|
||||
}
|
||||
updateSelection()
|
||||
|
||||
languageTables.forEach {
|
||||
it.onClick {
|
||||
chosenLanguage = it.language
|
||||
updateSelection()
|
||||
}
|
||||
}
|
||||
}
|
248
core/src/com/unciv/ui/options/ModCheckTab.kt
Normal file
248
core/src/com/unciv/ui/options/ModCheckTab.kt
Normal file
@ -0,0 +1,248 @@
|
||||
package com.unciv.ui.options
|
||||
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Label
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.badlogic.gdx.utils.Align
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
import com.unciv.models.ruleset.unique.Unique
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.crashhandling.launchCrashHandling
|
||||
import com.unciv.ui.crashhandling.postCrashHandlingRunnable
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.newgamescreen.TranslatedSelectBox
|
||||
import com.unciv.ui.popup.ToastPopup
|
||||
import com.unciv.ui.utils.*
|
||||
|
||||
|
||||
private const val MOD_CHECK_WITHOUT_BASE = "-none-"
|
||||
|
||||
class ModCheckTab(
|
||||
val screen: BaseScreen
|
||||
) : Table(), TabbedPager.IPageExtensions {
|
||||
private val fixedContent = Table()
|
||||
|
||||
// marker for automatic first run on selecting the tab
|
||||
private var modCheckFirstRun = true
|
||||
|
||||
private var modCheckBaseSelect: TranslatedSelectBox? = null
|
||||
private val modCheckResultTable = Table()
|
||||
|
||||
init {
|
||||
defaults().pad(10f).align(Align.top)
|
||||
|
||||
fixedContent.defaults().pad(10f).align(Align.top)
|
||||
val reloadModsButton = "Reload mods".toTextButton().onClick(::runAction)
|
||||
fixedContent.add(reloadModsButton).row()
|
||||
|
||||
val labeledBaseSelect = Table().apply {
|
||||
add("Check extension mods based on:".toLabel()).padRight(10f)
|
||||
val baseMods = listOf(MOD_CHECK_WITHOUT_BASE) + RulesetCache.getSortedBaseRulesets()
|
||||
modCheckBaseSelect = TranslatedSelectBox(baseMods, MOD_CHECK_WITHOUT_BASE, BaseScreen.skin).apply {
|
||||
selectedIndex = 0
|
||||
onChange { runAction() }
|
||||
}
|
||||
add(modCheckBaseSelect)
|
||||
}
|
||||
fixedContent.add(labeledBaseSelect).row()
|
||||
|
||||
add(modCheckResultTable)
|
||||
}
|
||||
|
||||
private fun runAction() {
|
||||
if (modCheckFirstRun) runModChecker()
|
||||
else runModChecker(modCheckBaseSelect!!.selected.value)
|
||||
}
|
||||
|
||||
override fun getFixedContent() = fixedContent
|
||||
|
||||
override fun activated(index: Int, caption: String, pager: TabbedPager) {
|
||||
runAction()
|
||||
}
|
||||
|
||||
fun runModChecker(base: String = MOD_CHECK_WITHOUT_BASE) {
|
||||
|
||||
modCheckFirstRun = false
|
||||
if (modCheckBaseSelect == null) return
|
||||
|
||||
modCheckResultTable.clear()
|
||||
|
||||
val rulesetErrors = RulesetCache.loadRulesets()
|
||||
if (rulesetErrors.isNotEmpty()) {
|
||||
val errorTable = Table().apply { defaults().pad(2f) }
|
||||
for (rulesetError in rulesetErrors)
|
||||
errorTable.add(rulesetError.toLabel()).width(stage.width / 2).row()
|
||||
modCheckResultTable.add(errorTable)
|
||||
}
|
||||
|
||||
modCheckResultTable.add("Checking mods for errors...".toLabel()).row()
|
||||
modCheckBaseSelect!!.isDisabled = true
|
||||
|
||||
launchCrashHandling("ModChecker") {
|
||||
for (mod in RulesetCache.values.sortedBy { it.name }) {
|
||||
if (base != MOD_CHECK_WITHOUT_BASE && mod.modOptions.isBaseRuleset) continue
|
||||
|
||||
val modLinks =
|
||||
if (base == MOD_CHECK_WITHOUT_BASE) mod.checkModLinks(forOptionsPopup = true)
|
||||
else RulesetCache.checkCombinedModLinks(linkedSetOf(mod.name), base, forOptionsPopup = true)
|
||||
modLinks.sortByDescending { it.errorSeverityToReport }
|
||||
val noProblem = !modLinks.isNotOK()
|
||||
if (modLinks.isNotEmpty()) modLinks += Ruleset.RulesetError("", Ruleset.RulesetErrorSeverity.OK)
|
||||
if (noProblem) modLinks += Ruleset.RulesetError("No problems found.".tr(), Ruleset.RulesetErrorSeverity.OK)
|
||||
|
||||
postCrashHandlingRunnable {
|
||||
// When the options popup is already closed before this postRunnable is run,
|
||||
// Don't add the labels, as otherwise the game will crash
|
||||
if (stage == null) return@postCrashHandlingRunnable
|
||||
// Don't just render text, since that will make all the conditionals in the mod replacement messages move to the end, which makes it unreadable
|
||||
// Don't use .toLabel() either, since that activates translations as well, which is what we're trying to avoid,
|
||||
// Instead, some manual work needs to be put in.
|
||||
|
||||
val iconColor = modLinks.getFinalSeverity().color
|
||||
val iconName = when (iconColor) {
|
||||
Color.RED -> "OtherIcons/Stop"
|
||||
Color.YELLOW -> "OtherIcons/ExclamationMark"
|
||||
else -> "OtherIcons/Checkmark"
|
||||
}
|
||||
val icon = ImageGetter.getImage(iconName)
|
||||
.apply { color = Color.BLACK }
|
||||
.surroundWithCircle(30f, color = iconColor)
|
||||
|
||||
val expanderTab = ExpanderTab(mod.name, icon = icon, startsOutOpened = false) {
|
||||
it.defaults().align(Align.left)
|
||||
if (!noProblem && mod.folderLocation != null) {
|
||||
val replaceableUniques = getDeprecatedReplaceableUniques(mod)
|
||||
if (replaceableUniques.isNotEmpty())
|
||||
it.add("Autoupdate mod uniques".toTextButton()
|
||||
.onClick { autoUpdateUniques(screen, mod, replaceableUniques) }).pad(10f).row()
|
||||
}
|
||||
for (line in modLinks) {
|
||||
val label = Label(line.text, BaseScreen.skin)
|
||||
.apply { color = line.errorSeverityToReport.color }
|
||||
label.wrap = true
|
||||
it.add(label).width(stage.width / 2).row()
|
||||
}
|
||||
if (!noProblem)
|
||||
it.add("Copy to clipboard".toTextButton().onClick {
|
||||
Gdx.app.clipboard.contents = modLinks
|
||||
.joinToString("\n") { line -> line.text }
|
||||
}).row()
|
||||
}
|
||||
|
||||
val loadingLabel = modCheckResultTable.children.last()
|
||||
modCheckResultTable.removeActor(loadingLabel)
|
||||
modCheckResultTable.add(expanderTab).row()
|
||||
modCheckResultTable.add(loadingLabel).row()
|
||||
}
|
||||
}
|
||||
|
||||
// done with all mods!
|
||||
postCrashHandlingRunnable {
|
||||
modCheckResultTable.removeActor(modCheckResultTable.children.last())
|
||||
modCheckBaseSelect!!.isDisabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun getDeprecatedReplaceableUniques(mod: Ruleset): HashMap<String, String> {
|
||||
|
||||
val objectsToCheck = sequenceOf(
|
||||
mod.beliefs,
|
||||
mod.buildings,
|
||||
mod.nations,
|
||||
mod.policies,
|
||||
mod.technologies,
|
||||
mod.terrains,
|
||||
mod.tileImprovements,
|
||||
mod.unitPromotions,
|
||||
mod.unitTypes,
|
||||
mod.units,
|
||||
)
|
||||
val allDeprecatedUniques = HashSet<String>()
|
||||
val deprecatedUniquesToReplacementText = HashMap<String, String>()
|
||||
|
||||
val deprecatedUniques = objectsToCheck
|
||||
.flatMap { it.values }
|
||||
.flatMap { it.uniqueObjects }
|
||||
.filter { it.getDeprecationAnnotation() != null }
|
||||
|
||||
|
||||
for (deprecatedUnique in deprecatedUniques) {
|
||||
if (allDeprecatedUniques.contains(deprecatedUnique.text)) continue
|
||||
allDeprecatedUniques.add(deprecatedUnique.text)
|
||||
|
||||
// note that this replacement does not contain conditionals attached to the original!
|
||||
|
||||
|
||||
var uniqueReplacementText = deprecatedUnique.getReplacementText(mod)
|
||||
while (Unique(uniqueReplacementText).getDeprecationAnnotation() != null)
|
||||
uniqueReplacementText = Unique(uniqueReplacementText).getReplacementText(mod)
|
||||
|
||||
for (conditional in deprecatedUnique.conditionals)
|
||||
uniqueReplacementText += " <${conditional.text}>"
|
||||
val replacementUnique = Unique(uniqueReplacementText)
|
||||
|
||||
val modInvariantErrors = mod.checkUnique(
|
||||
replacementUnique,
|
||||
false,
|
||||
"",
|
||||
UniqueType.UniqueComplianceErrorSeverity.RulesetInvariant,
|
||||
deprecatedUnique.sourceObjectType!!
|
||||
)
|
||||
for (error in modInvariantErrors)
|
||||
println(error.text + " - " + error.errorSeverityToReport)
|
||||
if (modInvariantErrors.isNotEmpty()) continue // errors means no autoreplace
|
||||
|
||||
if (mod.modOptions.isBaseRuleset) {
|
||||
val modSpecificErrors = mod.checkUnique(
|
||||
replacementUnique,
|
||||
false,
|
||||
"",
|
||||
UniqueType.UniqueComplianceErrorSeverity.RulesetInvariant,
|
||||
deprecatedUnique.sourceObjectType
|
||||
)
|
||||
for (error in modSpecificErrors)
|
||||
println(error.text + " - " + error.errorSeverityToReport)
|
||||
if (modSpecificErrors.isNotEmpty()) continue
|
||||
}
|
||||
|
||||
deprecatedUniquesToReplacementText[deprecatedUnique.text] = uniqueReplacementText
|
||||
println("Replace \"${deprecatedUnique.text}\" with \"$uniqueReplacementText\"")
|
||||
}
|
||||
return deprecatedUniquesToReplacementText
|
||||
}
|
||||
|
||||
private fun autoUpdateUniques(screen: BaseScreen, mod: Ruleset, replaceableUniques: HashMap<String, String>) {
|
||||
|
||||
val filesToReplace = listOf(
|
||||
"Beliefs.json",
|
||||
"Buildings.json",
|
||||
"Nations.json",
|
||||
"Policies.json",
|
||||
"Techs.json",
|
||||
"Terrains.json",
|
||||
"TileImprovements.json",
|
||||
"UnitPromotions.json",
|
||||
"UnitTypes.json",
|
||||
"Units.json",
|
||||
)
|
||||
|
||||
val jsonFolder = mod.folderLocation!!.child("jsons")
|
||||
for (fileName in filesToReplace) {
|
||||
val file = jsonFolder.child(fileName)
|
||||
if (!file.exists() || file.isDirectory) continue
|
||||
var newFileText = file.readString()
|
||||
for ((original, replacement) in replaceableUniques) {
|
||||
newFileText = newFileText.replace("\"$original\"", "\"$replacement\"")
|
||||
}
|
||||
file.writeString(newFileText, false)
|
||||
}
|
||||
val toastText = "Uniques updated!"
|
||||
ToastPopup(toastText, screen).open(true)
|
||||
runModChecker()
|
||||
}
|
||||
}
|
152
core/src/com/unciv/ui/options/MultiplayerTab.kt
Normal file
152
core/src/com/unciv/ui/options/MultiplayerTab.kt
Normal file
@ -0,0 +1,152 @@
|
||||
package com.unciv.ui.options
|
||||
|
||||
import com.badlogic.gdx.Application
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.SelectBox
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.TextField
|
||||
import com.badlogic.gdx.utils.Array
|
||||
import com.unciv.Constants
|
||||
import com.unciv.logic.multiplayer.storage.SimpleHttp
|
||||
import com.unciv.models.metadata.GameSettings
|
||||
import com.unciv.ui.crashhandling.launchCrashHandling
|
||||
import com.unciv.ui.crashhandling.postCrashHandlingRunnable
|
||||
import com.unciv.ui.popup.Popup
|
||||
import com.unciv.ui.utils.*
|
||||
|
||||
fun multiplayerTab(
|
||||
optionsPopup: OptionsPopup
|
||||
): Table = Table(BaseScreen.skin).apply {
|
||||
pad(10f)
|
||||
defaults().pad(5f)
|
||||
|
||||
val settings = optionsPopup.settings
|
||||
|
||||
// at the moment the notification service only exists on Android
|
||||
if (Gdx.app.type == Application.ApplicationType.Android) {
|
||||
optionsPopup.addCheckbox(
|
||||
this, "Enable out-of-game turn notifications",
|
||||
settings.multiplayerTurnCheckerEnabled
|
||||
) {
|
||||
settings.multiplayerTurnCheckerEnabled = it
|
||||
settings.save()
|
||||
}
|
||||
|
||||
if (settings.multiplayerTurnCheckerEnabled) {
|
||||
addMultiplayerTurnCheckerDelayBox(this, settings)
|
||||
|
||||
optionsPopup.addCheckbox(
|
||||
this, "Show persistent notification for turn notifier service",
|
||||
settings.multiplayerTurnCheckerPersistentNotificationEnabled
|
||||
)
|
||||
{ settings.multiplayerTurnCheckerPersistentNotificationEnabled = it }
|
||||
}
|
||||
}
|
||||
|
||||
val connectionToServerButton = "Check connection to server".toTextButton()
|
||||
|
||||
val textToShowForMultiplayerAddress =
|
||||
if (settings.multiplayerServer != Constants.dropboxMultiplayerServer) settings.multiplayerServer
|
||||
else "https://..."
|
||||
val multiplayerServerTextField = TextField(textToShowForMultiplayerAddress, BaseScreen.skin)
|
||||
multiplayerServerTextField.setTextFieldFilter { _, c -> c !in " \r\n\t\\" }
|
||||
multiplayerServerTextField.programmaticChangeEvents = true
|
||||
val serverIpTable = Table()
|
||||
|
||||
serverIpTable.add("Server address".toLabel().onClick {
|
||||
multiplayerServerTextField.text = Gdx.app.clipboard.contents
|
||||
}).row()
|
||||
multiplayerServerTextField.onChange {
|
||||
connectionToServerButton.isEnabled = multiplayerServerTextField.text != Constants.dropboxMultiplayerServer
|
||||
if (connectionToServerButton.isEnabled) {
|
||||
fixTextFieldUrlOnType(multiplayerServerTextField)
|
||||
// we can't trim on 'fixTextFieldUrlOnType' for reasons
|
||||
settings.multiplayerServer = multiplayerServerTextField.text.trimEnd('/')
|
||||
} else {
|
||||
settings.multiplayerServer = multiplayerServerTextField.text
|
||||
}
|
||||
settings.save()
|
||||
}
|
||||
|
||||
val screen = optionsPopup.screen
|
||||
serverIpTable.add(multiplayerServerTextField).minWidth(screen.stage.width / 2).growX()
|
||||
add(serverIpTable).fillX().row()
|
||||
|
||||
add("Reset to Dropbox".toTextButton().onClick {
|
||||
multiplayerServerTextField.text = Constants.dropboxMultiplayerServer
|
||||
}).row()
|
||||
|
||||
add(connectionToServerButton.onClick {
|
||||
val popup = Popup(screen).apply {
|
||||
addGoodSizedLabel("Awaiting response...").row()
|
||||
}
|
||||
popup.open(true)
|
||||
|
||||
successfullyConnectedToServer(settings) { success, _, _ ->
|
||||
popup.addGoodSizedLabel(if (success) "Success!" else "Failed!").row()
|
||||
popup.addCloseButton()
|
||||
}
|
||||
}).row()
|
||||
}
|
||||
|
||||
private fun successfullyConnectedToServer(settings: GameSettings, action: (Boolean, String, Int?) -> Unit) {
|
||||
launchCrashHandling("TestIsAlive") {
|
||||
SimpleHttp.sendGetRequest("${settings.multiplayerServer}/isalive") {
|
||||
success, result, code ->
|
||||
postCrashHandlingRunnable {
|
||||
action(success, result, code)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun fixTextFieldUrlOnType(TextField: TextField) {
|
||||
var text: String = TextField.text
|
||||
var cursor: Int = minOf(TextField.cursorPosition, text.length)
|
||||
|
||||
// if text is 'http:' or 'https:' auto append '//'
|
||||
if (Regex("^https?:$").containsMatchIn(text)) {
|
||||
TextField.appendText("//")
|
||||
return
|
||||
}
|
||||
|
||||
val textBeforeCursor: String = text.substring(0, cursor)
|
||||
|
||||
// replace multiple slash with a single one
|
||||
val multipleSlashes = Regex("/{2,}")
|
||||
text = multipleSlashes.replace(text, "/")
|
||||
|
||||
// calculate updated cursor
|
||||
cursor = multipleSlashes.replace(textBeforeCursor, "/").length
|
||||
|
||||
// operations above makes 'https://' -> 'https:/'
|
||||
// fix that if available and update cursor
|
||||
val i: Int = text.indexOf(":/")
|
||||
if (i > -1) {
|
||||
text = text.replaceRange(i..i + 1, "://")
|
||||
if (cursor > i + 1) ++cursor
|
||||
}
|
||||
|
||||
// update TextField
|
||||
if (text != TextField.text) {
|
||||
TextField.text = text
|
||||
TextField.cursorPosition = cursor
|
||||
}
|
||||
}
|
||||
|
||||
private fun addMultiplayerTurnCheckerDelayBox(table: Table, settings: GameSettings) {
|
||||
table.add("Time between turn checks out-of-game (in minutes)".toLabel()).left().fillX()
|
||||
|
||||
val checkDelaySelectBox = SelectBox<Int>(table.skin)
|
||||
val possibleDelaysArray = Array<Int>()
|
||||
possibleDelaysArray.addAll(1, 2, 5, 15)
|
||||
checkDelaySelectBox.items = possibleDelaysArray
|
||||
checkDelaySelectBox.selected = settings.multiplayerTurnCheckerDelayInMinutes
|
||||
|
||||
table.add(checkDelaySelectBox).pad(10f).row()
|
||||
|
||||
checkDelaySelectBox.onChange {
|
||||
settings.multiplayerTurnCheckerDelayInMinutes = checkDelaySelectBox.selected
|
||||
settings.save()
|
||||
}
|
||||
}
|
141
core/src/com/unciv/ui/options/OptionsPopup.kt
Normal file
141
core/src/com/unciv/ui/options/OptionsPopup.kt
Normal file
@ -0,0 +1,141 @@
|
||||
package com.unciv.ui.options
|
||||
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.Input
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.unciv.MainMenuScreen
|
||||
import com.unciv.models.metadata.BaseRuleset
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.popup.Popup
|
||||
import com.unciv.ui.utils.BaseScreen
|
||||
import com.unciv.ui.utils.TabbedPager
|
||||
import com.unciv.ui.utils.center
|
||||
import com.unciv.ui.utils.toCheckBox
|
||||
import com.unciv.ui.worldscreen.WorldScreen
|
||||
|
||||
/**
|
||||
* The Options (Settings) Popup
|
||||
* @param screen The caller - note if this is a [WorldScreen] or [MainMenuScreen] they will be rebuilt when major options change.
|
||||
*/
|
||||
//region Fields
|
||||
class OptionsPopup(
|
||||
screen: BaseScreen,
|
||||
private val selectPage: Int = defaultPage,
|
||||
private val onClose: () -> Unit = {}
|
||||
) : Popup(screen) {
|
||||
val settings = screen.game.settings
|
||||
val tabs: TabbedPager
|
||||
val selectBoxMinWidth: Float
|
||||
|
||||
//endregion
|
||||
|
||||
companion object {
|
||||
const val defaultPage = 2 // Gameplay
|
||||
}
|
||||
|
||||
init {
|
||||
settings.addCompletedTutorialTask("Open the options table")
|
||||
|
||||
innerTable.pad(0f)
|
||||
val tabMaxWidth: Float
|
||||
val tabMinWidth: Float
|
||||
val tabMaxHeight: Float
|
||||
screen.run {
|
||||
selectBoxMinWidth = if (stage.width < 600f) 200f else 240f
|
||||
tabMaxWidth = if (isPortrait()) stage.width - 10f else 0.8f * stage.width
|
||||
tabMinWidth = 0.6f * stage.width
|
||||
tabMaxHeight = (if (isPortrait()) 0.7f else 0.8f) * stage.height
|
||||
}
|
||||
tabs = TabbedPager(
|
||||
tabMinWidth, tabMaxWidth, 0f, tabMaxHeight,
|
||||
headerFontSize = 21, backgroundColor = Color.CLEAR, keyPressDispatcher = this.keyPressDispatcher, capacity = 8
|
||||
)
|
||||
add(tabs).pad(0f).grow().row()
|
||||
|
||||
tabs.addPage(
|
||||
"About",
|
||||
aboutTab(screen),
|
||||
ImageGetter.getExternalImage("Icon.png"), 24f
|
||||
)
|
||||
tabs.addPage(
|
||||
"Display",
|
||||
displayTab(this, ::reloadWorldAndOptions, ::reloadWorldAndOptions),
|
||||
ImageGetter.getImage("UnitPromotionIcons/Scouting"), 24f
|
||||
)
|
||||
tabs.addPage(
|
||||
"Gameplay",
|
||||
gameplayTab(this),
|
||||
ImageGetter.getImage("OtherIcons/Options"), 24f
|
||||
)
|
||||
tabs.addPage(
|
||||
"Language",
|
||||
languageTab(this, ::reloadWorldAndOptions),
|
||||
ImageGetter.getImage("FlagIcons/${settings.language}"), 24f
|
||||
)
|
||||
tabs.addPage(
|
||||
"Sound",
|
||||
soundTab(this),
|
||||
ImageGetter.getImage("OtherIcons/Speaker"), 24f
|
||||
)
|
||||
tabs.addPage(
|
||||
"Multiplayer",
|
||||
multiplayerTab(this),
|
||||
ImageGetter.getImage("OtherIcons/Multiplayer"), 24f
|
||||
)
|
||||
tabs.addPage(
|
||||
"Advanced",
|
||||
advancedTab(this, ::reloadWorldAndOptions),
|
||||
ImageGetter.getImage("OtherIcons/Settings"), 24f
|
||||
)
|
||||
|
||||
if (RulesetCache.size > BaseRuleset.values().size) {
|
||||
val content = ModCheckTab(screen)
|
||||
tabs.addPage("Locate mod errors", content, ImageGetter.getImage("OtherIcons/Mods"), 24f)
|
||||
}
|
||||
if (Gdx.input.isKeyPressed(Input.Keys.SHIFT_RIGHT) && (Gdx.input.isKeyPressed(Input.Keys.CONTROL_RIGHT) || Gdx.input.isKeyPressed(Input.Keys.ALT_RIGHT))) {
|
||||
tabs.addPage("Debug", debugTab(), ImageGetter.getImage("OtherIcons/SecretOptions"), 24f, secret = true)
|
||||
}
|
||||
tabs.bindArrowKeys() // If we're sharing WorldScreen's dispatcher that's OK since it does revertToCheckPoint on update
|
||||
|
||||
addCloseButton {
|
||||
screen.game.musicController.onChange(null)
|
||||
screen.game.platformSpecificHelper?.allowPortrait(settings.allowAndroidPortrait)
|
||||
onClose()
|
||||
}.padBottom(10f)
|
||||
|
||||
pack() // Needed to show the background.
|
||||
center(screen.stage)
|
||||
}
|
||||
|
||||
override fun setVisible(visible: Boolean) {
|
||||
super.setVisible(visible)
|
||||
if (!visible) return
|
||||
tabs.askForPassword(secretHashCode = 2747985)
|
||||
if (tabs.activePage < 0) tabs.selectPage(selectPage)
|
||||
}
|
||||
|
||||
/** Reload this Popup after major changes (resolution, tileset, language, font) */
|
||||
private fun reloadWorldAndOptions() {
|
||||
settings.save()
|
||||
if (screen is WorldScreen) {
|
||||
screen.game.worldScreen = WorldScreen(screen.gameInfo, screen.viewingCiv)
|
||||
screen.game.setWorldScreen()
|
||||
} else if (screen is MainMenuScreen) {
|
||||
screen.game.setScreen(MainMenuScreen())
|
||||
}
|
||||
(screen.game.screen as BaseScreen).openOptionsPopup(tabs.activePage)
|
||||
}
|
||||
|
||||
fun addCheckbox(table: Table, text: String, initialState: Boolean, updateWorld: Boolean = false, action: ((Boolean) -> Unit)) {
|
||||
val checkbox = text.toCheckBox(initialState) {
|
||||
action(it)
|
||||
settings.save()
|
||||
if (updateWorld && screen is WorldScreen)
|
||||
screen.shouldUpdate = true
|
||||
}
|
||||
table.add(checkbox).colspan(2).left().row()
|
||||
}
|
||||
|
||||
}
|
150
core/src/com/unciv/ui/options/SoundTab.kt
Normal file
150
core/src/com/unciv/ui/options/SoundTab.kt
Normal file
@ -0,0 +1,150 @@
|
||||
package com.unciv.ui.options
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.unciv.models.UncivSound
|
||||
import com.unciv.models.metadata.GameSettings
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.audio.MusicTrackChooserFlags
|
||||
import com.unciv.ui.crashhandling.launchCrashHandling
|
||||
import com.unciv.ui.crashhandling.postCrashHandlingRunnable
|
||||
import com.unciv.ui.utils.*
|
||||
import kotlin.math.floor
|
||||
|
||||
fun soundTab(
|
||||
optionsPopup: OptionsPopup
|
||||
): Table = Table(BaseScreen.skin).apply {
|
||||
pad(10f)
|
||||
defaults().pad(5f)
|
||||
|
||||
val settings = optionsPopup.settings
|
||||
val screen = optionsPopup.screen
|
||||
|
||||
addSoundEffectsVolumeSlider(this, settings)
|
||||
|
||||
if (screen.game.musicController.isMusicAvailable()) {
|
||||
addMusicVolumeSlider(this, settings, screen)
|
||||
addMusicPauseSlider(this, settings, screen)
|
||||
addMusicCurrentlyPlaying(this, screen)
|
||||
}
|
||||
|
||||
if (!screen.game.musicController.isDefaultFileAvailable())
|
||||
addDownloadMusic(this, optionsPopup)
|
||||
}
|
||||
|
||||
private fun addDownloadMusic(table: Table, optionsPopup: OptionsPopup) {
|
||||
val downloadMusicButton = "Download music".toTextButton()
|
||||
table.add(downloadMusicButton).colspan(2).row()
|
||||
val errorTable = Table()
|
||||
table.add(errorTable).colspan(2).row()
|
||||
|
||||
downloadMusicButton.onClick {
|
||||
downloadMusicButton.disable()
|
||||
errorTable.clear()
|
||||
errorTable.add("Downloading...".toLabel())
|
||||
|
||||
// So the whole game doesn't get stuck while downloading the file
|
||||
launchCrashHandling("MusicDownload") {
|
||||
try {
|
||||
val screen = optionsPopup.screen
|
||||
screen.game.musicController.downloadDefaultFile()
|
||||
postCrashHandlingRunnable {
|
||||
optionsPopup.tabs.replacePage("Sound", soundTab(optionsPopup))
|
||||
screen.game.musicController.chooseTrack(flags = MusicTrackChooserFlags.setPlayDefault)
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
postCrashHandlingRunnable {
|
||||
errorTable.clear()
|
||||
errorTable.add("Could not download music!".toLabel(Color.RED))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun addSoundEffectsVolumeSlider(table: Table, settings: GameSettings) {
|
||||
table.add("Sound effects volume".tr()).left().fillX()
|
||||
|
||||
val soundEffectsVolumeSlider = UncivSlider(
|
||||
0f, 1.0f, 0.05f,
|
||||
initial = settings.soundEffectsVolume,
|
||||
getTipText = UncivSlider::formatPercent
|
||||
) {
|
||||
settings.soundEffectsVolume = it
|
||||
settings.save()
|
||||
}
|
||||
table.add(soundEffectsVolumeSlider).pad(5f).row()
|
||||
}
|
||||
|
||||
private fun addMusicVolumeSlider(table: Table, settings: GameSettings, screen: BaseScreen) {
|
||||
table.add("Music volume".tr()).left().fillX()
|
||||
|
||||
val musicVolumeSlider = UncivSlider(
|
||||
0f, 1.0f, 0.05f,
|
||||
initial = settings.musicVolume,
|
||||
sound = UncivSound.Silent,
|
||||
getTipText = UncivSlider::formatPercent
|
||||
) {
|
||||
settings.musicVolume = it
|
||||
settings.save()
|
||||
|
||||
val music = screen.game.musicController
|
||||
music.setVolume(it)
|
||||
if (!music.isPlaying())
|
||||
music.chooseTrack(flags = MusicTrackChooserFlags.setPlayDefault)
|
||||
}
|
||||
table.add(musicVolumeSlider).pad(5f).row()
|
||||
}
|
||||
|
||||
private fun addMusicPauseSlider(table: Table, settings: GameSettings, screen: BaseScreen) {
|
||||
val music = screen.game.musicController
|
||||
|
||||
// map to/from 0-1-2..10-12-14..30-35-40..60-75-90-105-120
|
||||
fun posToLength(pos: Float): Float = when (pos) {
|
||||
in 0f..10f -> pos
|
||||
in 11f..20f -> pos * 2f - 10f
|
||||
in 21f..26f -> pos * 5f - 70f
|
||||
else -> pos * 15f - 330f
|
||||
}
|
||||
|
||||
fun lengthToPos(length: Float): Float = floor(
|
||||
when (length) {
|
||||
in 0f..10f -> length
|
||||
in 11f..30f -> (length + 10f) / 2f
|
||||
in 31f..60f -> (length + 10f) / 5f
|
||||
else -> (length + 330f) / 15f
|
||||
}
|
||||
)
|
||||
|
||||
val getTipText: (Float) -> String = {
|
||||
"%.0f".format(posToLength(it))
|
||||
}
|
||||
|
||||
table.add("Pause between tracks".tr()).left().fillX()
|
||||
|
||||
val pauseLengthSlider = UncivSlider(
|
||||
0f, 30f, 1f,
|
||||
initial = lengthToPos(music.silenceLength),
|
||||
sound = UncivSound.Silent,
|
||||
getTipText = getTipText
|
||||
) {
|
||||
music.silenceLength = posToLength(it)
|
||||
settings.pauseBetweenTracks = music.silenceLength.toInt()
|
||||
}
|
||||
table.add(pauseLengthSlider).pad(5f).row()
|
||||
}
|
||||
|
||||
private fun addMusicCurrentlyPlaying(table: Table, screen: BaseScreen) {
|
||||
val label = WrappableLabel("", table.width - 10f, Color(-0x2f5001), 16)
|
||||
label.wrap = true
|
||||
table.add(label).padTop(20f).colspan(2).fillX().row()
|
||||
screen.game.musicController.onChange {
|
||||
postCrashHandlingRunnable {
|
||||
label.setText("Currently playing: [$it]".tr())
|
||||
}
|
||||
}
|
||||
label.onClick(UncivSound.Silent) {
|
||||
screen.game.musicController.chooseTrack(flags = MusicTrackChooserFlags.none)
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ import com.unciv.models.Tutorial
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.popup.hasOpenPopups
|
||||
import com.unciv.ui.tutorials.TutorialController
|
||||
import com.unciv.ui.worldscreen.mainmenu.OptionsPopup
|
||||
import com.unciv.ui.options.OptionsPopup
|
||||
|
||||
abstract class BaseScreen : Screen {
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user