Refactor: Change UncivGame.worldScreen and UncivGame.gameInfo to be of nullable type (#7114)

* Refactor: Make Popups work on Stages instead of BaseScreens

* Refactor: Change UncivGame.worldScreen and UncivGame.gameInfo to be of nullable type

* Fix "Resume" game loading not fully handling exceptions

* Fix one missed refactoring

* Refactor: remove useless postRunnable
This commit is contained in:
Timo T 2022-06-11 21:14:44 +02:00 committed by GitHub
parent 72b197fdc3
commit 9008d242a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
75 changed files with 427 additions and 346 deletions

View File

@ -70,13 +70,13 @@ open class AndroidLauncher : AndroidApplication() {
override fun onPause() {
if (UncivGame.isCurrentInitialized()
&& UncivGame.Current.isGameInfoInitialized()
&& UncivGame.Current.gameInfo != null
&& UncivGame.Current.settings.multiplayer.turnCheckerEnabled
&& UncivGame.Current.gameSaver.getMultiplayerSaves().any()
) {
MultiplayerTurnCheckWorker.startTurnChecker(
applicationContext, UncivGame.Current.gameSaver,
UncivGame.Current.gameInfo, UncivGame.Current.settings.multiplayer
UncivGame.Current.gameInfo!!, UncivGame.Current.settings.multiplayer
)
}
super.onPause()

View File

@ -198,7 +198,7 @@ class MainMenuScreen: BaseScreen() {
private fun resumeGame() {
val curWorldScreen = game.getWorldScreenOrNull()
val curWorldScreen = game.worldScreen
if (curWorldScreen != null) {
game.resetToWorldScreen()
curWorldScreen.popups.filterIsInstance(WorldScreenMenuPopup::class.java).forEach(Popup::close)

View File

@ -4,6 +4,7 @@ import com.badlogic.gdx.Application
import com.badlogic.gdx.Game
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.Input
import com.badlogic.gdx.Screen
import com.badlogic.gdx.scenes.scene2d.actions.Actions
import com.badlogic.gdx.utils.Align
import com.unciv.logic.GameInfo
@ -22,13 +23,13 @@ import com.unciv.ui.audio.SoundPlayer
import com.unciv.ui.crashhandling.closeExecutors
import com.unciv.ui.crashhandling.launchCrashHandling
import com.unciv.ui.crashhandling.postCrashHandlingRunnable
import com.unciv.ui.crashhandling.wrapCrashHandlingUnit
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.multiplayer.LoadDeepLinkScreen
import com.unciv.ui.multiplayer.MultiplayerHelpers
import com.unciv.ui.popup.Popup
import com.unciv.ui.utils.BaseScreen
import com.unciv.ui.utils.extensions.center
import com.unciv.ui.crashhandling.wrapCrashHandlingUnit
import com.unciv.ui.worldscreen.PlayerReadyScreen
import com.unciv.ui.worldscreen.WorldScreen
import com.unciv.utils.debug
@ -49,8 +50,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
private val audioExceptionHelper = parameters.audioExceptionHelper
var deepLinkedMultiplayerGame: String? = null
lateinit var gameInfo: GameInfo
fun isGameInfoInitialized() = this::gameInfo.isInitialized
var gameInfo: GameInfo? = null
lateinit var settings: GameSettings
lateinit var musicController: MusicController
lateinit var onlineMultiplayer: OnlineMultiplayer
@ -70,9 +70,8 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
*/
var simulateUntilTurnForDebug: Int = 0
lateinit var worldScreen: WorldScreen
var worldScreen: WorldScreen? = null
private set
fun getWorldScreenOrNull() = if (this::worldScreen.isInitialized) worldScreen else null
var isInitialized = false
@ -105,7 +104,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
* - Font (hence Fonts.resetFont() inside setSkin())
*/
settings = gameSaver.getGeneralSettings() // needed for the screen
screen = LoadingScreen() // NOT dependent on any atlas or skin
setScreen(LoadingScreen()) // NOT dependent on any atlas or skin
GameSounds.init()
musicController = MusicController() // early, but at this point does only copy volume from settings
audioExceptionHelper?.installHooks(
@ -152,49 +151,66 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
}
}
fun loadGame(gameInfo: GameInfo) {
fun loadGame(gameInfo: GameInfo): WorldScreen {
this.gameInfo = gameInfo
ImageGetter.setNewRuleset(gameInfo.ruleSet)
// Clone the mod list and add the base ruleset to it
val fullModList = gameInfo.gameParameters.getModsAndBaseRuleset()
musicController.setModList(fullModList)
Gdx.input.inputProcessor = null // Since we will set the world screen when we're ready,
if (gameInfo.civilizations.count { it.playerType == PlayerType.Human } > 1 && !gameInfo.gameParameters.isOnlineMultiplayer)
setScreen(PlayerReadyScreen(gameInfo, gameInfo.getPlayerToViewAs()))
val worldScreen = WorldScreen(gameInfo, gameInfo.getPlayerToViewAs())
val newScreen = if (gameInfo.civilizations.count { it.playerType == PlayerType.Human } > 1 && !gameInfo.gameParameters.isOnlineMultiplayer)
PlayerReadyScreen(worldScreen)
else {
resetToWorldScreen(WorldScreen(gameInfo, gameInfo.getPlayerToViewAs()))
worldScreen
}
}
fun setScreen(screen: BaseScreen) {
val oldScreen = getScreen()
Gdx.input.inputProcessor = screen.stage
super.setScreen(screen)
if (oldScreen != getWorldScreenOrNull()) oldScreen.dispose()
setScreen(newScreen)
return worldScreen
}
/**
* If called with null [newWorldScreen], disposes of the current screen and sets it to the current stored world screen.
* If the current screen is already the world screen, the only thing that happens is that the world screen updates.
* Sets the screen of the game and automatically disposes the old screen as long as it isn't the world screen.
*
* @param screen must be a subclass of [BaseScreen].
*/
fun resetToWorldScreen(newWorldScreen: WorldScreen? = null) {
if (newWorldScreen != null) {
debug("Reset to new WorldScreen, gameId: %s, turn: %s, curCiv: %s",
newWorldScreen.gameInfo.gameId, newWorldScreen.gameInfo.turns, newWorldScreen.gameInfo.currentPlayer)
val oldWorldScreen = getWorldScreenOrNull()
worldScreen = newWorldScreen
// setScreen disposes the current screen, but the old world screen is not the current screen, so need to dispose here
if (screen != oldWorldScreen) {
oldWorldScreen?.dispose()
}
override fun setScreen(screen: Screen) {
if (screen !is BaseScreen) throw IllegalArgumentException("Call to setScreen with screen that does not inherit BaseScreen: " + screen.javaClass.simpleName)
setScreen(screen)
}
override fun getScreen(): BaseScreen? {
val curScreen = super.getScreen()
return if (curScreen == null) { null } else { curScreen as BaseScreen }
}
/** Sets the screen of the game and automatically [disposes][Screen.dispose] the old screen as long as it isn't the world screen. */
fun setScreen(newScreen: BaseScreen) {
if (newScreen is WorldScreen) {
debug(
"Setting new world screen: gameId: %s, turn: %s, curCiv: %s",
newScreen.gameInfo.gameId, newScreen.gameInfo.turns, newScreen.gameInfo.currentPlayer
)
if (newScreen != worldScreen) worldScreen?.dispose()
worldScreen = newScreen
newScreen.shouldUpdate = true
Gdx.graphics.requestRendering()
} else {
val oldWorldScreen = getWorldScreenOrNull()!!
debug("Reset to old WorldScreen, gameId: %s, turn: %s, curCiv: %s",
oldWorldScreen.gameInfo.gameId, oldWorldScreen.gameInfo.turns, oldWorldScreen.gameInfo.currentPlayer)
debug("Setting new screen: %s", newScreen)
}
setScreen(worldScreen)
worldScreen.shouldUpdate = true // This can set the screen to the policy picker or tech picker screen, so the input processor must come before
Gdx.graphics.requestRendering()
val oldScreen = screen
Gdx.input.inputProcessor = newScreen.stage
super.setScreen(newScreen) // This can set the screen to the policy picker or tech picker screen, so the input processor must be set before
if (oldScreen !is WorldScreen) { // we want to keep the world screen around, because it's expensive to re-create it
oldScreen?.dispose()
}
}
/**
* Resets the game to the stored world screen and automatically [disposes][Screen.dispose] the old screen.
*/
fun resetToWorldScreen() {
setScreen(worldScreen!!)
}
private fun tryLoadDeepLinkedGame() = launchCrashHandling("LoadDeepLinkedGame") {
@ -233,7 +249,8 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
}
override fun pause() {
if (isGameInfoInitialized()) gameSaver.autoSave(this.gameInfo)
val curGameInfo = gameInfo
if (curGameInfo != null) gameSaver.autoSave(curGameInfo)
musicController.pause()
super.pause()
}
@ -252,14 +269,15 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
if (::musicController.isInitialized) musicController.gracefulShutdown() // Do allow fade-out
closeExecutors()
if (isGameInfoInitialized()) {
val curGameInfo = gameInfo
if (curGameInfo != null) {
val autoSaveJob = gameSaver.autoSaveJob
if (autoSaveJob != null && autoSaveJob.isActive) {
// auto save is already in progress (e.g. started by onPause() event)
// let's allow it to finish and do not try to autosave second time
autoSaveJob.join()
} else {
gameSaver.autoSaveSingleThreaded(gameInfo) // NO new thread
gameSaver.autoSaveSingleThreaded(curGameInfo) // NO new thread
}
}
settings.save()
@ -277,10 +295,15 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
}
}
/** Returns the [worldScreen] if it is the currently active screen of the game */
fun getWorldScreenIfActive(): WorldScreen? {
return if (screen == worldScreen) worldScreen else null
}
companion object {
lateinit var Current: UncivGame
fun isCurrentInitialized() = this::Current.isInitialized
fun isCurrentGame(gameId: String): Boolean = isCurrentInitialized() && Current.isGameInfoInitialized() && Current.gameInfo.gameId == gameId
fun isCurrentGame(gameId: String): Boolean = isCurrentInitialized() && Current.gameInfo != null && Current.gameInfo!!.gameId == gameId
fun isDeepLinkedGameLoading() = isCurrentInitialized() && Current.deepLinkedMultiplayerGame != null
}
}

View File

@ -208,7 +208,7 @@ object Battle {
// CS friendship from killing barbarians
if (defeatedUnit.matchesCategory("Barbarian") && defeatedUnit.matchesCategory("Military") && civUnit.getCivInfo().isMajorCiv()) {
for (cityState in UncivGame.Current.gameInfo.getAliveCityStates()) {
for (cityState in UncivGame.Current.gameInfo!!.getAliveCityStates()) {
if (civUnit.getCivInfo().knows(cityState) && defeatedUnit.unit.threatensCiv(cityState)) {
cityState.cityStateFunctions.threateningBarbarianKilledBy(civUnit.getCivInfo())
}
@ -216,7 +216,7 @@ object Battle {
}
// CS war with major pseudo-quest
for (cityState in UncivGame.Current.gameInfo.getAliveCityStates()) {
for (cityState in UncivGame.Current.gameInfo!!.getAliveCityStates()) {
cityState.questManager.militaryUnitKilledBy(civUnit.getCivInfo(), defeatedUnit.getCivInfo())
}
}

View File

@ -663,8 +663,8 @@ class CityConstructions {
cityInfo.cityStats.update()
cityInfo.civInfo.updateDetailedCivResources()
// If bought the worldscreen will not have been marked to update, and the new improvement won't show until later...
if (UncivGame.isCurrentInitialized())
UncivGame.Current.worldScreen.shouldUpdate = true
if (UncivGame.isCurrentInitialized() && UncivGame.Current.worldScreen != null)
UncivGame.Current.worldScreen!!.shouldUpdate = true
}
/** Support for [UniqueType.CreatesOneImprovement]:

View File

@ -155,8 +155,10 @@ class CityInfoConquestFunctions(val city: CityInfo){
city.isPuppet = false
city.cityConstructions.inProgressConstructions.clear() // undo all progress of the previous civ on units etc.
city.cityStats.update()
if (!UncivGame.Current.consoleMode)
UncivGame.Current.worldScreen.shouldUpdate = true
val worldScreen = UncivGame.Current.worldScreen
if (!UncivGame.Current.consoleMode && worldScreen != null) {
worldScreen.shouldUpdate = true
}
}
private fun diplomaticRepercussionsForConqueringCity(oldCiv: CivilizationInfo, conqueringCiv: CivilizationInfo) {

View File

@ -62,8 +62,11 @@ class CivilizationInfo {
/** Returns an instance of WorkerAutomation valid for the duration of the current turn
* This instance carries cached data common for all Workers of this civ */
fun getWorkerAutomation(): WorkerAutomation {
val currentTurn = if (UncivGame.Current.isInitialized && UncivGame.Current.isGameInfoInitialized())
UncivGame.Current.gameInfo.turns else 0
val currentTurn = if (UncivGame.Current.isInitialized && UncivGame.Current.gameInfo != null) {
UncivGame.Current.gameInfo!!.turns
} else {
0
}
if (workerAutomationCache == null || workerAutomationCache!!.cachedForTurn != currentTurn)
workerAutomationCache = WorkerAutomation(this, currentTurn)
return workerAutomationCache!!

View File

@ -56,7 +56,7 @@ interface NotificationAction {
}
/** A notification action that cycles through tiles.
*
*
* Constructors accept any kind of [Vector2] collection, including [Iterable], [Sequence], `vararg`.
* `varargs` allows nulls which are ignored, a resulting empty list is allowed and equivalent to no [NotificationAction].
*/

View File

@ -919,11 +919,11 @@ class AssignedQuest(val questName: String = "",
when (questName) {
QuestName.ClearBarbarianCamp.value -> {
game.resetToWorldScreen()
game.worldScreen.mapHolder.setCenterPosition(Vector2(data1.toFloat(), data2.toFloat()), selectUnit = false)
game.worldScreen!!.mapHolder.setCenterPosition(Vector2(data1.toFloat(), data2.toFloat()), selectUnit = false)
}
QuestName.Route.value -> {
game.resetToWorldScreen()
game.worldScreen.mapHolder.setCenterPosition(gameInfo.getCivilization(assigner).getCapital()!!.location, selectUnit = false)
game.worldScreen!!.mapHolder.setCenterPosition(gameInfo.getCivilization(assigner).getCapital()!!.location, selectUnit = false)
}
}
}

View File

@ -67,8 +67,9 @@ class OnlineMultiplayer {
}
private fun getCurrentGame(): OnlineMultiplayerGame? {
if (UncivGame.isCurrentInitialized() && UncivGame.Current.isGameInfoInitialized()) {
return getGameByGameId(UncivGame.Current.gameInfo.gameId)
val gameInfo = UncivGame.Current.gameInfo
if (gameInfo != null) {
return getGameByGameId(gameInfo.gameId)
} else {
return null
}

View File

@ -10,10 +10,10 @@ import com.unciv.logic.trade.TradeType.TradeTypeNumberType
data class TradeOffer(val name: String, val type: TradeType, var amount: Int = 1, var duration: Int) {
constructor(
name: String,
type: TradeType,
amount: Int = 1,
gameSpeed: GameSpeed = UncivGame.Current.gameInfo.gameParameters.gameSpeed
name: String,
type: TradeType,
amount: Int = 1,
gameSpeed: GameSpeed = UncivGame.Current.gameInfo!!.gameParameters.gameSpeed
) : this(name, type, amount, duration = -1) {
duration = when {
type.isImmediate -> -1 // -1 for offers that are immediate (e.g. gold transfer)
@ -22,7 +22,7 @@ data class TradeOffer(val name: String, val type: TradeType, var amount: Int = 1
else -> (30 * gameSpeed.modifier).toInt()
}
}
constructor() : this("", TradeType.Gold, duration = -1) // so that the json deserializer can work
@Suppress("CovariantEquals") // This is an overload, not an override of the built-in equals(Any?)
@ -36,13 +36,13 @@ data class TradeOffer(val name: String, val type: TradeType, var amount: Int = 1
var offerText = when(type){
TradeType.WarDeclaration -> "Declare war on [$name]"
TradeType.Introduction -> "Introduction to [$name]"
TradeType.City -> UncivGame.Current.gameInfo.getCities().firstOrNull{ it.id == name }?.name ?: "Non-existent city"
TradeType.City -> UncivGame.Current.gameInfo!!.getCities().firstOrNull{ it.id == name }?.name ?: "Non-existent city"
else -> name
}.tr()
if (type.numberType == TradeTypeNumberType.Simple || name == Constants.researchAgreement) offerText += " ($amount)"
else if (type.numberType == TradeTypeNumberType.Gold) offerText += " ($amount)"
if (duration > 0) offerText += "\n" + duration + Fonts.turn
if (untradable == 1) {

View File

@ -47,8 +47,9 @@ class Belief() : RulesetObject() {
// private but potentially reusable, therefore not folded into getCivilopediaTextMatching
private fun getBeliefsMatching(name: String, ruleset: Ruleset): Sequence<Belief> {
if (!UncivGame.isCurrentInitialized()) return sequenceOf()
if (!UncivGame.Current.isGameInfoInitialized()) return sequenceOf()
if (!UncivGame.Current.gameInfo.isReligionEnabled()) return sequenceOf()
val gameInfo = UncivGame.Current.gameInfo
if (gameInfo == null) return sequenceOf()
if (!gameInfo.isReligionEnabled()) return sequenceOf()
return ruleset.beliefs.asSequence().map { it.value }
.filter { belief -> belief.uniqueObjects.any { unique -> unique.params.any { it == name } }
}

View File

@ -137,10 +137,11 @@ class Nation : RulesetObject() {
textList += FormattedLine("{Type}: {$cityStateType}", header = 4, color = cityStateType!!.color)
val era = if (UncivGame.isCurrentInitialized() && UncivGame.Current.isGameInfoInitialized())
UncivGame.Current.gameInfo.currentPlayerCiv.getEra()
else
val era = if (UncivGame.isCurrentInitialized() && UncivGame.Current.gameInfo != null) {
UncivGame.Current.gameInfo!!.currentPlayerCiv.getEra()
} else {
ruleset.eras.values.first()
}
var showResources = false
val friendBonus = era.friendBonus[cityStateType!!.name]
@ -185,7 +186,7 @@ class Nation : RulesetObject() {
when {
building.uniqueTo != name -> continue
building.hasUnique(UniqueType.HiddenFromCivilopedia) -> continue
UncivGame.Current.isGameInfoInitialized() && !UncivGame.Current.gameInfo.isReligionEnabled() && building.hasUnique(UniqueType.HiddenWithoutReligion) -> continue // This seems consistent with existing behaviour of CivilopediaScreen's init.<locals>.shouldBeDisplayed(), and Technology().getEnabledUnits(). Otherwise there are broken links in the Civilopedia (E.G. to "Pyramid" and "Shrine", from "The Maya").
UncivGame.Current.gameInfo != null && !UncivGame.Current.gameInfo!!.isReligionEnabled() && building.hasUnique(UniqueType.HiddenWithoutReligion) -> continue // This seems consistent with existing behaviour of CivilopediaScreen's init.<locals>.shouldBeDisplayed(), and Technology().getEnabledUnits(). Otherwise there are broken links in the Civilopedia (E.G. to "Pyramid" and "Shrine", from "The Maya").
}
yield(FormattedLine("{${building.name}} -", link=building.makeLink()))
if (building.replaces != null && ruleset.buildings.containsKey(building.replaces!!)) {

View File

@ -51,7 +51,7 @@ class Technology: RulesetObject() {
}
}
val viewingCiv = UncivGame.Current.worldScreen.viewingCiv
val viewingCiv = UncivGame.Current.worldScreen!!.viewingCiv
val enabledUnits = getEnabledUnits(ruleset, viewingCiv)
if (enabledUnits.any()) {
lineList += "{Units enabled}: "
@ -158,11 +158,11 @@ class Technology: RulesetObject() {
ruleset.tileImprovements.values
.asSequence()
.filter { improvement ->
improvement.uniqueObjects.any {
unique -> unique.conditionals.any {
(it.isOfType(UniqueType.ConditionalTech) || it.isOfType(UniqueType.ConditionalNoTech))
&& it.params[0] == name
}
improvement.uniqueObjects.any {
unique -> unique.conditionals.any {
(it.isOfType(UniqueType.ConditionalTech) || it.isOfType(UniqueType.ConditionalNoTech))
&& it.params[0] == name
}
}
}
@ -225,7 +225,7 @@ class Technology: RulesetObject() {
}
}
val viewingCiv = UncivGame.Current.getWorldScreenOrNull()?.viewingCiv
val viewingCiv = UncivGame.Current.worldScreen?.viewingCiv
val enabledUnits = getEnabledUnits(ruleset, viewingCiv)
if (enabledUnits.any()) {
lineList += FormattedLine()

View File

@ -176,9 +176,9 @@ class TileImprovement : RulesetStatsObject() {
if (isAncientRuinsEquivalent() && ruleset.ruinRewards.isNotEmpty()) {
val difficulty: String
val religionEnabled: Boolean
if (UncivGame.isCurrentInitialized() && UncivGame.Current.isGameInfoInitialized()) {
difficulty = UncivGame.Current.gameInfo.gameParameters.difficulty
religionEnabled = UncivGame.Current.gameInfo.isReligionEnabled()
if (UncivGame.isCurrentInitialized() && UncivGame.Current.gameInfo != null) {
difficulty = UncivGame.Current.gameInfo!!.gameParameters.difficulty
religionEnabled = UncivGame.Current.gameInfo!!.isReligionEnabled()
} else {
difficulty = "Prince" // most factors == 1
religionEnabled = true

View File

@ -38,7 +38,7 @@ class Simulation(
for (civ in civilizations) {
this.winRate[civ] = MutableInt(0)
winRateByVictory[civ] = mutableMapOf()
for (victory in UncivGame.Current.gameInfo.ruleSet.victories.keys)
for (victory in UncivGame.Current.gameInfo!!.ruleSet.victories.keys)
winRateByVictory[civ]!![victory] = MutableInt(0)
}
}
@ -121,7 +121,7 @@ class Simulation(
outString += "\n$civ:\n"
val wins = winRate[civ]!!.value * 100 / max(steps.size, 1)
outString += "$wins% total win rate \n"
for (victory in UncivGame.Current.gameInfo.ruleSet.victories.keys) {
for (victory in UncivGame.Current.gameInfo!!.ruleSet.victories.keys) {
val winsVictory = winRateByVictory[civ]!![victory]!!.value * 100 / max(winRate[civ]!!.value, 1)
outString += "$victory: $winsVictory% "
}

View File

@ -250,22 +250,28 @@ object TranslationActiveModsCache {
}
private set
private fun getCurrentHash() = UncivGame.Current.run {
if (isGameInfoInitialized())
gameInfo.gameParameters.mods.hashCode() + gameInfo.gameParameters.baseRuleset.hashCode() * 31
else translations.translationActiveMods.hashCode() * 31 * 31
private fun getCurrentHash(): Int {
val gameInfo = UncivGame.Current.gameInfo
return if (gameInfo != null) {
gameInfo.gameParameters.mods.hashCode() + gameInfo.gameParameters.baseRuleset.hashCode() * 31
} else {
UncivGame.Current.translations.translationActiveMods.hashCode() * 31 * 31
}
}
private fun getCurrentSet() = UncivGame.Current.run {
if (isGameInfoInitialized()) {
val par = gameInfo.gameParameters
// This is equivalent to (par.mods + par.baseRuleset) without the cast down to `Set`
LinkedHashSet<String>(par.mods.size + 1).apply {
addAll(par.mods)
add(par.baseRuleset)
}
} else translations.translationActiveMods
private fun getCurrentSet(): LinkedHashSet<String> {
val gameInfo = UncivGame.Current.gameInfo
return if (gameInfo != null) {
val par = gameInfo.gameParameters
// This is equivalent to (par.mods + par.baseRuleset) without the cast down to `Set`
LinkedHashSet<String>(par.mods.size + 1).apply {
addAll(par.mods)
add(par.baseRuleset)
}
} else {
UncivGame.Current.translations.translationActiveMods
}
}
}
/**

View File

@ -49,6 +49,5 @@ class LanguagePickerScreen : PickerScreen() {
game.translations.tryReadTranslationForCurrentLanguage()
game.setScreen(MainMenuScreen())
dispose()
}
}

View File

@ -59,7 +59,8 @@ object SoundPlayer {
val game = UncivGame.Current
// Get a hash covering all mods - quickly, so don't map, cast or copy the Set types
val hash1 = if (game.isGameInfoInitialized()) game.gameInfo.ruleSet.mods.hashCode() else 0
val gameInfo = game.gameInfo
val hash1 = if (gameInfo != null) gameInfo.ruleSet.mods.hashCode() else 0
val newHash = hash1.xor(game.settings.visualMods.hashCode())
// If hash the same, leave the cache as is
@ -91,8 +92,10 @@ object SoundPlayer {
// audiovisual mods after game mods but before built-in sounds
// (these can already be available when game.gameInfo is not)
val modList: MutableSet<String> = mutableSetOf()
if (game.isGameInfoInitialized())
modList.addAll(game.gameInfo.ruleSet.mods) // Sounds from game mods
val gameInfo = game.gameInfo
if (gameInfo != null) {
modList.addAll(gameInfo.ruleSet.mods) // Sounds from game mods
}
modList.addAll(game.settings.visualMods)
// Translate the basic mod list into relative folder names so only sounds/name.ext needs

View File

@ -516,10 +516,9 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
"Would you like to purchase [${construction.name}] for [$constructionBuyCost] [${stat.character}]?".tr()
YesNoPopup(
purchasePrompt,
action = { purchaseConstruction(construction, stat, tile) },
screen = cityScreen,
restoreDefault = { cityScreen.update() }
).open()
) { purchaseConstruction(construction, stat, tile) }.open()
}
/** This tests whether the buy button should be _shown_ */

View File

@ -138,17 +138,17 @@ class CityInfoTable(private val cityScreen: CityScreen) : Table(BaseScreen.skin)
cityScreen.closeAllPopups()
YesNoPopup("Are you sure you want to sell this [${building.name}]?".tr(),
{
cityScreen.city.sellBuilding(building.name)
cityScreen, {
cityScreen.update()
}, cityScreen,
{
cityScreen.update()
}).open()
}
) {
cityScreen.city.sellBuilding(building.name)
cityScreen.update()
}.open()
}
if (cityScreen.city.hasSoldBuildingThisTurn && !cityScreen.city.civInfo.gameInfo.gameParameters.godMode
|| cityScreen.city.isPuppet
|| !UncivGame.Current.worldScreen.isPlayersTurn || !cityScreen.canChangeState)
|| !UncivGame.Current.worldScreen!!.isPlayersTurn || !cityScreen.canChangeState)
sellBuildingButton.disable()
}
it.addSeparator()

View File

@ -81,7 +81,7 @@ class CityReligionInfoTable(
icon.onClick {
val newScreen = if (religion == iconName)
EmpireOverviewScreen(civInfo, EmpireOverviewCategories.Religion.name, religion)
else CivilopediaScreen(gameInfo.ruleSet, UncivGame.Current.screen as BaseScreen, CivilopediaCategories.Belief, religion )
else CivilopediaScreen(gameInfo.ruleSet, UncivGame.Current.screen!!, CivilopediaCategories.Belief, religion )
UncivGame.Current.setScreen(newScreen)
}
return icon

View File

@ -41,7 +41,7 @@ class CityScreen(
}
/** Toggles or adds/removes all state changing buttons */
val canChangeState = UncivGame.Current.worldScreen.canChangeState
val canChangeState = UncivGame.Current.worldScreen!!.canChangeState
/** Toggle between Constructions and cityInfo (buildings, specialists etc. */
var showConstructionsTable = true
@ -402,8 +402,8 @@ class CityScreen(
fun exit() {
game.resetToWorldScreen()
game.worldScreen.mapHolder.setCenterPosition(city.location)
game.worldScreen.bottomUnitTable.selectUnit()
game.worldScreen!!.mapHolder.setCenterPosition(city.location)
game.worldScreen!!.bottomUnitTable.selectUnit()
}
fun page(delta: Int) {

View File

@ -112,15 +112,14 @@ class CityScreenTileTable(private val cityScreen: CityScreen): Table() {
"Would you like to purchase [Tile] for [$goldCostOfTile] [${Stat.Gold.character}]?".tr()
YesNoPopup(
purchasePrompt,
action = {
SoundPlayer.play(UncivSound.Coin)
city.expansion.buyTile(selectedTile)
// preselect the next tile on city screen rebuild so bulk buying can go faster
UncivGame.Current.setScreen(CityScreen(city, initSelectedTile = city.expansion.chooseNewTileToOwn()))
},
screen = cityScreen,
restoreDefault = { cityScreen.update() }
).open()
) {
SoundPlayer.play(UncivSound.Coin)
city.expansion.buyTile(selectedTile)
// preselect the next tile on city screen rebuild so bulk buying can go faster
UncivGame.Current.setScreen(CityScreen(city, initSelectedTile = city.expansion.chooseNewTileToOwn()))
}.open()
}
/** This tests whether the buy button should be _shown_ */

View File

@ -81,7 +81,7 @@ class SpecialistAllocationTable(val cityScreen: CityScreen) : Table(BaseScreen.s
cityInfo.cityStats.update()
cityScreen.update()
}
if (cityInfo.population.getFreePopulation() == 0 || !UncivGame.Current.worldScreen.isPlayersTurn)
if (cityInfo.population.getFreePopulation() == 0 || !UncivGame.Current.worldScreen!!.isPlayersTurn)
assignButton.clear()
return assignButton
}
@ -98,7 +98,7 @@ class SpecialistAllocationTable(val cityScreen: CityScreen) : Table(BaseScreen.s
}
if (assignedSpecialists <= 0 || cityInfo.isPuppet) unassignButton.isVisible = false
if (!UncivGame.Current.worldScreen.isPlayersTurn) unassignButton.clear()
if (!UncivGame.Current.worldScreen!!.isPlayersTurn) unassignButton.clear()
return unassignButton
}

View File

@ -183,10 +183,9 @@ class CivilopediaScreen(
val imageSize = 50f
onBackButtonClicked { game.setScreen(previousScreen) }
val religionEnabled = if (game.isGameInfoInitialized()) game.gameInfo.isReligionEnabled()
else ruleset.beliefs.isNotEmpty()
val victoryTypes = if (game.isGameInfoInitialized()) game.gameInfo.gameParameters.victoryTypes
else listOf()
val curGameInfo = game.gameInfo
val religionEnabled = if (curGameInfo != null) curGameInfo.isReligionEnabled() else ruleset.beliefs.isNotEmpty()
val victoryTypes = if (curGameInfo != null) curGameInfo.gameParameters.victoryTypes else emptyList()
fun shouldBeDisplayed(obj: IHasUniques): Boolean {
return when {
@ -254,7 +253,6 @@ class CivilopediaScreen(
val goToGameButton = Constants.close.toTextButton()
goToGameButton.onClick {
game.setScreen(previousScreen)
dispose()
}
val topTable = Table()

View File

@ -164,10 +164,13 @@ class FormattedLine (
}
return ""
}
private fun getCurrentRuleset() = when {
!UncivGame.isCurrentInitialized() -> Ruleset()
!UncivGame.Current.isGameInfoInitialized() -> RulesetCache[BaseRuleset.Civ_V_Vanilla.fullName]!!
else -> UncivGame.Current.gameInfo.ruleSet
private fun getCurrentRuleset(): Ruleset {
val gameInfo = UncivGame.Current.gameInfo
return when {
!UncivGame.isCurrentInitialized() -> Ruleset()
gameInfo == null -> RulesetCache[BaseRuleset.Civ_V_Vanilla.fullName]!!
else -> gameInfo.ruleSet
}
}
private fun initNamesCategoryMap(ruleSet: Ruleset): HashMap<String, CivilopediaCategories> {
//val startTime = System.nanoTime()

View File

@ -47,7 +47,7 @@ class CrashScreen(val exception: Throwable): BaseScreen() {
/** Qualified class name of the game screen that was active at the construction of this instance, or an error note. */
private val lastScreenType = try {
UncivGame.Current.screen::class.qualifiedName.toString()
UncivGame.Current.screen!!::class.qualifiedName.toString()
} catch (e: Throwable) {
"Could not get screen type: $e"
}
@ -58,11 +58,11 @@ class CrashScreen(val exception: Throwable): BaseScreen() {
/** @return The last active save game serialized as a compressed string if any, or an informational note otherwise. */
private fun tryGetSaveGame(): String {
if (!UncivGame.isCurrentInitialized() || !UncivGame.Current.isGameInfoInitialized())
if (!UncivGame.isCurrentInitialized() || UncivGame.Current.gameInfo == null)
return ""
return "\n**Save Data:**\n<details><summary>Show Saved Game</summary>\n\n```\n" +
try {
GameSaver.gameInfoToString(UncivGame.Current.gameInfo, forceZip = true)
GameSaver.gameInfoToString(UncivGame.Current.gameInfo!!, forceZip = true)
} catch (e: Throwable) {
"No save data: $e" // In theory .toString() could still error here.
} + "\n```\n</details>\n"
@ -70,11 +70,11 @@ class CrashScreen(val exception: Throwable): BaseScreen() {
/** @return Mods from the last active save game if any, or an informational note otherwise. */
private fun tryGetSaveMods(): String {
if (!UncivGame.isCurrentInitialized() || !UncivGame.Current.isGameInfoInitialized())
if (!UncivGame.isCurrentInitialized() || UncivGame.Current.gameInfo == null)
return ""
return "\n**Save Mods:**\n```\n" +
try { // Also from old CrashController().buildReport(), also could still error at .toString().
LinkedHashSet(UncivGame.Current.gameInfo.gameParameters.getModsAndBaseRuleset()).toString()
LinkedHashSet(UncivGame.Current.gameInfo!!.gameParameters.getModsAndBaseRuleset()).toString()
} catch (e: Throwable) {
"No mod data: $e"
} + "\n```\n"

View File

@ -62,10 +62,10 @@ class MapEditorLoadTab(
private fun deleteHandler() {
if (chosenMap == null) return
YesNoPopup("Are you sure you want to delete this map?", {
YesNoPopup("Are you sure you want to delete this map?", editorScreen) {
chosenMap!!.delete()
mapFiles.update()
}, editorScreen).open()
}.open()
}
override fun activated(index: Int, caption: String, pager: TabbedPager) {

View File

@ -131,7 +131,7 @@ class MapEditorModsTab(
add(inc.toLabel()).row()
}
add(ScrollPane(incompatibilityTable)).colspan(2)
.maxHeight(screen.stage.height * 0.8f).row()
.maxHeight(stage.height * 0.8f).row()
addGoodSizedLabel("Change map to fit selected ruleset?", 24).colspan(2).row()
addButtonInRow(Constants.yes, 'y') {
onOK()

View File

@ -78,10 +78,10 @@ class MapEditorSaveTab(
private fun deleteHandler() {
if (chosenMap == null) return
YesNoPopup("Are you sure you want to delete this map?", {
YesNoPopup("Are you sure you want to delete this map?", editorScreen) {
chosenMap!!.delete()
mapFiles.update()
}, editorScreen).open()
}.open()
}
override fun activated(index: Int, caption: String, pager: TabbedPager) {

View File

@ -191,9 +191,9 @@ class MapEditorScreen(map: TileMap? = null): BaseScreen() {
fun askIfDirty(question: String, action: ()->Unit) {
if (!isDirty) return action()
YesNoPopup(question, action, screen = this, restoreDefault = {
YesNoPopup(question, screen = this, restoreDefault = {
keyPressDispatcher[KeyCharAndCode.BACK] = this::closeEditor
}).open()
}, action).open()
}
fun hideSelection() {

View File

@ -40,11 +40,11 @@ class EditFriendScreen(selectedFriend: FriendList.Friend, backScreen: ViewFriend
topTable.add(gameIDTable).padBottom(30f).row()
deleteFriendButton.onClick {
val askPopup = YesNoPopup("Are you sure you want to delete this friend?", {
val askPopup = YesNoPopup("Are you sure you want to delete this friend?", this) {
friendlist.delete(selectedFriend)
backScreen.game.setScreen(backScreen)
backScreen.refreshFriendsList()
}, this)
}
askPopup.open()
}.apply { color = Color.RED }
topTable.add(deleteFriendButton)

View File

@ -27,22 +27,22 @@ class EditMultiplayerGameInfoScreen(val multiplayerGame: OnlineMultiplayerGame,
val deleteButton = "Delete save".toTextButton()
deleteButton.onClick {
val askPopup = YesNoPopup("Are you sure you want to delete this map?", {
val askPopup = YesNoPopup("Are you sure you want to delete this map?", this) {
try {
game.onlineMultiplayer.deleteGame(multiplayerGame)
game.setScreen(backScreen)
} catch (ex: Exception) {
ToastPopup("Could not delete game!", this)
}
}, this)
}
askPopup.open()
}.apply { color = Color.RED }
val giveUpButton = "Resign".toTextButton()
giveUpButton.onClick {
val askPopup = YesNoPopup("Are you sure you want to resign?", {
val askPopup = YesNoPopup("Are you sure you want to resign?", this) {
resign(multiplayerGame, backScreen)
}, this)
}
askPopup.open()
}
giveUpButton.apply { color = Color.RED }

View File

@ -36,6 +36,7 @@ import com.unciv.ui.utils.extensions.onClick
import com.unciv.ui.utils.extensions.pad
import com.unciv.ui.utils.extensions.toLabel
import com.unciv.ui.utils.extensions.toTextButton
import com.unciv.utils.Log
import java.net.URL
import java.util.*
import com.unciv.ui.utils.AutoScrollPane as ScrollPane
@ -74,9 +75,9 @@ class NewGameScreen(
val resetToDefaultsButton = "Reset to defaults".toTextButton()
rightSideGroup.addActorAt(0, resetToDefaultsButton)
resetToDefaultsButton.onClick {
YesNoPopup("Are you sure you want to reset all game options to defaults?", {
YesNoPopup("Are you sure you want to reset all game options to defaults?", this) {
game.setScreen(NewGameScreen(previousScreen, GameSetupInfo()))
}, this).open(true)
}.open(true)
}
}
@ -155,7 +156,7 @@ class NewGameScreen(
val message = mapSize.fixUndesiredSizes(gameSetupInfo.mapParameters.worldWrap)
if (message != null) {
postCrashHandlingRunnable {
ToastPopup( message, UncivGame.Current.screen as BaseScreen, 4000 )
ToastPopup( message, UncivGame.Current.screen!!, 4000 )
with (mapOptionsTable.generatedMapOptionsTable) {
customMapSizeRadius.text = mapSize.radius.toString()
customMapWidth.text = mapSize.width.toString()
@ -275,6 +276,7 @@ class NewGameScreen(
rightSideButton.setText("Start game!".tr())
return
} catch (ex: Exception) {
Log.error("Error while creating game", ex)
postCrashHandlingRunnable {
popup.reuseWith("Could not upload game!", true)
}
@ -286,13 +288,12 @@ class NewGameScreen(
}
postCrashHandlingRunnable {
game.loadGame(newGame)
previousScreen.dispose()
val worldScreen = game.loadGame(newGame)
if (newGame.gameParameters.isOnlineMultiplayer) {
// Save gameId to clipboard because you have to do it anyway.
Gdx.app.clipboard.contents = newGame.gameId
// Popup to notify the User that the gameID got copied to the clipboard
ToastPopup("Game ID copied to clipboard!".tr(), game.worldScreen, 2500)
ToastPopup("Game ID copied to clipboard!".tr(), worldScreen, 2500)
}
}
}

View File

@ -344,7 +344,7 @@ private class NationPickerPopup(
private val ruleset = previousScreen.ruleset
// This Popup's body has two halves of same size, either side by side or arranged vertically
// depending on screen proportions - determine height for one of those
private val partHeight = screen.stage.height * (if (screen.isNarrowerThan4to3()) 0.45f else 0.8f)
private val partHeight = stage.height * (if (stage.isNarrowerThan4to3()) 0.45f else 0.8f)
private val civBlocksWidth = playerPicker.civBlocksWidth
private val nationListTable = Table()
private val nationListScroll = ScrollPane(nationListTable)
@ -356,7 +356,7 @@ private class NationPickerPopup(
nationListScroll.setOverscroll(false, false)
add(nationListScroll).size( civBlocksWidth + 10f, partHeight )
// +10, because the nation table has a 5f pad, for a total of +10f
if (screen.isNarrowerThan4to3()) row()
if (stage.isNarrowerThan4to3()) row()
nationDetailsScroll.setOverscroll(false, false)
add(nationDetailsScroll).size(civBlocksWidth + 10f, partHeight) // Same here, see above

View File

@ -9,6 +9,7 @@ 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.UncivGame
import com.unciv.models.metadata.GameSettings
import com.unciv.models.translations.TranslationFileWriter
import com.unciv.models.translations.tr
@ -48,12 +49,12 @@ fun advancedTab(
addMaxZoomSlider(this, settings)
val screen = optionsPopup.screen
if (screen.game.platformSpecificHelper != null && Gdx.app.type == Application.ApplicationType.Android) {
val helper = UncivGame.Current.platformSpecificHelper
if (helper != null && Gdx.app.type == Application.ApplicationType.Android) {
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)
helper.allowPortrait(it)
}
}
@ -61,7 +62,7 @@ fun advancedTab(
addTranslationGeneration(this, optionsPopup)
addSetUserId(this, settings, screen)
addSetUserId(this, settings)
}
private fun addAutosaveTurnsSelectBox(table: Table, settings: GameSettings) {
@ -161,22 +162,18 @@ private fun addTranslationGeneration(table: Table, optionsPopup: OptionsPopup) {
table.add(generateTranslationsButton).colspan(2).row()
}
private fun addSetUserId(table: Table, settings: GameSettings, screen: BaseScreen) {
private fun addSetUserId(table: Table, settings: GameSettings) {
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.multiplayer.userId = clipboardContents
settings.save()
idSetLabel.setFontColor(Color.WHITE).setText("ID successfully set!".tr())
},
screen
).open(true)
YesNoPopup("Doing this will reset your current user ID to the clipboard contents - are you sure?",table.stage) {
settings.multiplayer.userId = clipboardContents
settings.save()
idSetLabel.setFontColor(Color.WHITE).setText("ID successfully set!".tr())
}.open(true)
idSetLabel.isVisible = true
} catch (ex: Exception) {
idSetLabel.isVisible = true

View File

@ -19,22 +19,25 @@ fun debugTab() = Table(BaseScreen.skin).apply {
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
val worldScreen = game.worldScreen
if (worldScreen != null) {
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
worldScreen.nextTurn()
}
game.simulateUntilTurnForDebug = simulateUntilTurns
invalidInputLabel.isVisible = false
game.worldScreen.nextTurn()
add(simulateButton)
add(simulateTextField).row()
add(invalidInputLabel).colspan(2).row()
}
add(simulateButton)
add(simulateTextField).row()
add(invalidInputLabel).colspan(2).row()
add("Supercharged".toCheckBox(game.superchargedForDebug) {
game.superchargedForDebug = it
@ -42,9 +45,10 @@ fun debugTab() = Table(BaseScreen.skin).apply {
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
val curGameInfo = game.gameInfo
if (curGameInfo != null) {
add("God mode (current game)".toCheckBox(curGameInfo.gameParameters.godMode) {
curGameInfo.gameParameters.godMode = it
}).colspan(2).row()
}
add("Save games compressed".toCheckBox(GameSaver.saveZipped) {
@ -73,25 +77,25 @@ fun debugTab() = Table(BaseScreen.skin).apply {
val unlockTechsButton = "Unlock all techs".toTextButton()
unlockTechsButton.onClick {
if (!game.isGameInfoInitialized())
if (curGameInfo == null)
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()
for (tech in curGameInfo.ruleSet.technologies.keys) {
if (tech !in curGameInfo.getCurrentPlayerCivilization().tech.techsResearched) {
curGameInfo.getCurrentPlayerCivilization().tech.addTechnology(tech)
curGameInfo.getCurrentPlayerCivilization().popupAlerts.removeLastOrNull()
}
}
game.gameInfo.getCurrentPlayerCivilization().updateSightAndResources()
game.worldScreen.shouldUpdate = true
curGameInfo.getCurrentPlayerCivilization().updateSightAndResources()
if (worldScreen != null) worldScreen.shouldUpdate = true
}
add(unlockTechsButton).colspan(2).row()
val giveResourcesButton = "Get all strategic resources".toTextButton()
giveResourcesButton.onClick {
if (!game.isGameInfoInitialized())
if (curGameInfo == null)
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 }
val ownedTiles = curGameInfo.tileMap.values.asSequence().filter { it.getOwner() == curGameInfo.getCurrentPlayerCivilization() }
val resourceTypes = curGameInfo.ruleSet.tileResources.values.asSequence().filter { it.resourceType == ResourceType.Strategic }
for ((tile, resource) in ownedTiles zip resourceTypes) {
tile.resource = resource.name
tile.resourceAmount = 999
@ -99,8 +103,8 @@ fun debugTab() = Table(BaseScreen.skin).apply {
// 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
curGameInfo.getCurrentPlayerCivilization().updateSightAndResources()
if (worldScreen != null) worldScreen.shouldUpdate = true
}
add(giveResourcesButton).colspan(2).row()
}

View File

@ -5,6 +5,7 @@ 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.UncivGame
import com.unciv.models.metadata.GameSettings
import com.unciv.models.tilesets.TileSetCache
import com.unciv.models.translations.tr
@ -41,7 +42,7 @@ fun displayTab(
optionsPopup.addCheckbox(this, "Experimental Demographics scoreboard", settings.useDemographics, true) { settings.useDemographics = it }
optionsPopup.addCheckbox(this, "Show zoom buttons in world screen", settings.showZoomButtons, true) { settings.showZoomButtons = it }
addMinimapSizeSlider(this, settings, optionsPopup.screen, optionsPopup.selectBoxMinWidth)
addMinimapSizeSlider(this, settings, optionsPopup.selectBoxMinWidth)
addResolutionSelectBox(this, settings, optionsPopup.selectBoxMinWidth, onResolutionChange)
@ -62,7 +63,7 @@ fun displayTab(
}
private fun addMinimapSizeSlider(table: Table, settings: GameSettings, screen: BaseScreen, selectBoxMinWidth: Float) {
private fun addMinimapSizeSlider(table: Table, settings: GameSettings, selectBoxMinWidth: Float) {
table.add("Show minimap".toLabel()).left().fillX()
// The meaning of the values needs a formula to be synchronized between here and
@ -88,8 +89,9 @@ private fun addMinimapSizeSlider(table: Table, settings: GameSettings, screen: B
settings.minimapSize = size
}
settings.save()
if (screen is WorldScreen)
screen.shouldUpdate = true
val worldScreen = UncivGame.Current.getWorldScreenIfActive()
if (worldScreen != null)
worldScreen.shouldUpdate = true
}
table.add(minimapSlider).minWidth(selectBoxMinWidth).pad(10f).row()
}

View File

@ -1,6 +1,7 @@
package com.unciv.ui.options
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.UncivGame
import com.unciv.logic.civilization.PlayerType
import com.unciv.ui.utils.BaseScreen
import com.unciv.ui.worldscreen.WorldScreen
@ -12,16 +13,16 @@ fun gameplayTab(
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
optionsPopup.addCheckbox(this, "Auto-assign city production", settings.autoAssignCityProduction, true) { shouldAutoAssignCityProduction ->
settings.autoAssignCityProduction = shouldAutoAssignCityProduction
val worldScreen = UncivGame.Current.getWorldScreenIfActive()
if (shouldAutoAssignCityProduction && worldScreen != null &&
worldScreen.viewingCiv.isCurrentPlayer() && worldScreen.viewingCiv.playerType == PlayerType.Human
) {
screen.gameInfo.currentPlayerCiv.cities.forEach { city ->
worldScreen.gameInfo.currentPlayerCiv.cities.forEach { city ->
city.cityConstructions.chooseNextConstruction()
}
}

View File

@ -1,6 +1,7 @@
package com.unciv.ui.options
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.UncivGame
import com.unciv.ui.utils.BaseScreen
import com.unciv.ui.utils.LanguageTable.Companion.addLanguageTables
import com.unciv.ui.utils.extensions.onClick
@ -17,7 +18,7 @@ fun languageTab(
fun selectLanguage() {
settings.language = chosenLanguage
settings.updateLocaleFromLanguage()
optionsPopup.screen.game.translations.tryReadTranslationForCurrentLanguage()
UncivGame.Current.translations.tryReadTranslationForCurrentLanguage()
onLanguageSelected()
}

View File

@ -12,7 +12,6 @@ import com.unciv.models.metadata.GameSetting
import com.unciv.models.metadata.GameSettings
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.translations.tr
import com.unciv.ui.crashhandling.launchCrashHandling
import com.unciv.ui.crashhandling.postCrashHandlingRunnable
import com.unciv.ui.popup.Popup
@ -145,8 +144,7 @@ private fun addMultiplayerServerOptions(
settings.save()
}
val screen = optionsPopup.screen
serverIpTable.add(multiplayerServerTextField).minWidth(screen.stage.width / 2).growX()
serverIpTable.add(multiplayerServerTextField).minWidth(optionsPopup.stageToShowOn.width / 2).growX()
tab.add(serverIpTable).colspan(2).fillX().row()
tab.add("Reset to Dropbox".toTextButton().onClick {
@ -156,7 +154,7 @@ private fun addMultiplayerServerOptions(
}).colspan(2).row()
tab.add(connectionToServerButton.onClick {
val popup = Popup(screen).apply {
val popup = Popup(optionsPopup.stage).apply {
addGoodSizedLabel("Awaiting response...").row()
}
popup.open(true)

View File

@ -8,6 +8,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener
import com.badlogic.gdx.utils.Array
import com.unciv.MainMenuScreen
import com.unciv.UncivGame
import com.unciv.logic.event.EventBus
import com.unciv.models.UncivSound
import com.unciv.models.metadata.BaseRuleset
@ -133,20 +134,20 @@ class OptionsPopup(
/** Reload this Popup after major changes (resolution, tileset, language, font) */
private fun reloadWorldAndOptions() {
settings.save()
if (screen is WorldScreen) {
screen.game.resetToWorldScreen(WorldScreen(screen.gameInfo, screen.viewingCiv))
} else if (screen is MainMenuScreen) {
screen.game.setScreen(MainMenuScreen())
val worldScreen = UncivGame.Current.getWorldScreenIfActive()
if (worldScreen != null) {
val newWorldScreen = WorldScreen(worldScreen.gameInfo, worldScreen.viewingCiv)
worldScreen.game.setScreen(newWorldScreen)
newWorldScreen.openOptionsPopup(tabs.activePage)
}
(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
val worldScreen = UncivGame.Current.getWorldScreenIfActive()
if (updateWorld && worldScreen != null) worldScreen.shouldUpdate = true
}
table.add(checkbox).colspan(2).left().row()
}

View File

@ -2,9 +2,11 @@ package com.unciv.ui.options
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.UncivGame
import com.unciv.models.UncivSound
import com.unciv.models.metadata.GameSettings
import com.unciv.models.translations.tr
import com.unciv.ui.audio.MusicController
import com.unciv.ui.audio.MusicTrackChooserFlags
import com.unciv.ui.crashhandling.launchCrashHandling
import com.unciv.ui.crashhandling.postCrashHandlingRunnable
@ -24,17 +26,17 @@ fun soundTab(
defaults().pad(5f)
val settings = optionsPopup.settings
val screen = optionsPopup.screen
val music = UncivGame.Current.musicController
addSoundEffectsVolumeSlider(this, settings)
if (screen.game.musicController.isMusicAvailable()) {
addMusicVolumeSlider(this, settings, screen)
addMusicPauseSlider(this, settings, screen)
addMusicCurrentlyPlaying(this, screen)
if (UncivGame.Current.musicController.isMusicAvailable()) {
addMusicVolumeSlider(this, settings, music)
addMusicPauseSlider(this, settings, music)
addMusicCurrentlyPlaying(this, music)
}
if (!screen.game.musicController.isDefaultFileAvailable())
if (!UncivGame.Current.musicController.isDefaultFileAvailable())
addDownloadMusic(this, optionsPopup)
}
@ -52,11 +54,10 @@ private fun addDownloadMusic(table: Table, optionsPopup: OptionsPopup) {
// So the whole game doesn't get stuck while downloading the file
launchCrashHandling("MusicDownload") {
try {
val screen = optionsPopup.screen
screen.game.musicController.downloadDefaultFile()
UncivGame.Current.musicController.downloadDefaultFile()
postCrashHandlingRunnable {
optionsPopup.tabs.replacePage("Sound", soundTab(optionsPopup))
screen.game.musicController.chooseTrack(flags = MusicTrackChooserFlags.setPlayDefault)
UncivGame.Current.musicController.chooseTrack(flags = MusicTrackChooserFlags.setPlayDefault)
}
} catch (ex: Exception) {
postCrashHandlingRunnable {
@ -83,7 +84,7 @@ private fun addSoundEffectsVolumeSlider(table: Table, settings: GameSettings) {
table.add(soundEffectsVolumeSlider).pad(5f).row()
}
private fun addMusicVolumeSlider(table: Table, settings: GameSettings, screen: BaseScreen) {
private fun addMusicVolumeSlider(table: Table, settings: GameSettings, music: MusicController) {
table.add("Music volume".tr()).left().fillX()
val musicVolumeSlider = UncivSlider(
@ -95,7 +96,6 @@ private fun addMusicVolumeSlider(table: Table, settings: GameSettings, screen: B
settings.musicVolume = it
settings.save()
val music = screen.game.musicController
music.setVolume(it)
if (!music.isPlaying())
music.chooseTrack(flags = MusicTrackChooserFlags.setPlayDefault)
@ -103,9 +103,7 @@ private fun addMusicVolumeSlider(table: Table, settings: GameSettings, screen: B
table.add(musicVolumeSlider).pad(5f).row()
}
private fun addMusicPauseSlider(table: Table, settings: GameSettings, screen: BaseScreen) {
val music = screen.game.musicController
private fun addMusicPauseSlider(table: Table, settings: GameSettings, music: 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
@ -141,16 +139,16 @@ private fun addMusicPauseSlider(table: Table, settings: GameSettings, screen: Ba
table.add(pauseLengthSlider).pad(5f).row()
}
private fun addMusicCurrentlyPlaying(table: Table, screen: BaseScreen) {
private fun addMusicCurrentlyPlaying(table: Table, music: MusicController) {
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 {
music.onChange {
postCrashHandlingRunnable {
label.setText("Currently playing: [$it]".tr())
}
}
label.onClick(UncivSound.Silent) {
screen.game.musicController.chooseTrack(flags = MusicTrackChooserFlags.none)
music.chooseTrack(flags = MusicTrackChooserFlags.none)
}
}

View File

@ -127,7 +127,6 @@ class DiplomacyOverviewTab (
table.touchable = Touchable.enabled
table.onClick {
if (civInfo.isDefeated() || viewingPlayer.isSpectator() || civInfo == viewingPlayer) return@onClick
overviewScreen.dispose()
UncivGame.Current.setScreen(DiplomacyScreen(viewingPlayer, civInfo))
}
return table

View File

@ -138,7 +138,7 @@ class StatsOverviewTab(
for (city in viewingPlayer.cities) { city.cityStats.update() }
update()
}
slider.isDisabled = !UncivGame.Current.worldScreen.canChangeState
slider.isDisabled = !UncivGame.Current.worldScreen!!.canChangeState
sliderTable.add(slider).padTop(15f)
add(sliderTable).colspan(2)

View File

@ -78,7 +78,7 @@ class UnitOverviewTab(
private fun showWorldScreenAt(position: Vector2, unit: MapUnit?) {
val game = overviewScreen.game
game.resetToWorldScreen()
game.worldScreen.mapHolder.setCenterPosition(position, forceSelectUnit = unit)
game.worldScreen!!.mapHolder.setCenterPosition(position, forceSelectUnit = unit)
}
private fun showWorldScreenAt(unit: MapUnit) = showWorldScreenAt(unit.currentTile.position, unit)
private fun showWorldScreenAt(tile: TileInfo) = showWorldScreenAt(tile.position, null)
@ -190,7 +190,7 @@ class UnitOverviewTab(
if (unit.promotions.canBePromoted())
promotionsTable.add(
ImageGetter.getImage("OtherIcons/Star").apply {
color = if (game.worldScreen.canChangeState && unit.currentMovement > 0f && unit.attacksThisTurn == 0)
color = if (game.worldScreen!!.canChangeState && unit.currentMovement > 0f && unit.attacksThisTurn == 0)
Color.GOLDENROD
else Color.GOLDENROD.darken(0.25f)
}
@ -198,7 +198,6 @@ class UnitOverviewTab(
promotionsTable.onClick {
if (unit.promotions.canBePromoted() || unit.promotions.promotions.isNotEmpty()) {
game.setScreen(PromotionPickerScreen(unit))
overviewScreen.dispose()
}
}
add(promotionsTable)

View File

@ -238,8 +238,11 @@ class WonderOverviewTab(
val locationLabel = locationText.toLabel()
if (wonder.location != null)
locationLabel.onClick{
UncivGame.Current.resetToWorldScreen()
UncivGame.Current.worldScreen.mapHolder.setCenterPosition(wonder.location.position)
val worldScreen = UncivGame.Current.worldScreen
if (worldScreen != null) {
UncivGame.Current.resetToWorldScreen()
worldScreen.mapHolder.setCenterPosition(wonder.location.position)
}
}
add(locationLabel).fillY()
}

View File

@ -59,7 +59,6 @@ class ImprovementPickerScreen(
onAccept()
}
game.resetToWorldScreen()
dispose()
}
init {

View File

@ -87,7 +87,6 @@ class ModManagementScreen(
game.settings.tileSet = tileSets.first()
}
game.setScreen(MainMenuScreen())
dispose()
}
closeButton.onClick(closeAction)
onBackButtonClicked(closeAction)
@ -581,7 +580,6 @@ class ModManagementScreen(
override fun resize(width: Int, height: Int) {
if (stage.viewport.screenWidth != width || stage.viewport.screenHeight != height) {
game.setScreen(ModManagementScreen(installedModInfo, onlineModInfo))
dispose() // interrupt background loader - sorry, the resized new screen won't continue
}
}

View File

@ -73,7 +73,7 @@ class PickerPane(
/** Sets the text of the [rightSideButton] and enables it if it's the player's turn */
fun pick(rightButtonText: String) {
if (UncivGame.Current.worldScreen.isPlayersTurn) rightSideButton.enable()
if (UncivGame.Current.worldScreen!!.isPlayersTurn) rightSideButton.enable()
rightSideButton.setText(rightButtonText)
}

View File

@ -43,7 +43,6 @@ open class PickerScreen(disableScroll: Boolean = false) : BaseScreen() {
val closeAction = {
if (previousScreen != null) game.setScreen(previousScreen)
else game.resetToWorldScreen()
dispose()
}
pickerPane.closeButton.onClick(closeAction)
onBackButtonClicked(closeAction)

View File

@ -58,7 +58,6 @@ class PolicyPickerScreen(val worldScreen: WorldScreen, civInfo: CivilizationInfo
// If we've moved to another screen in the meantime (great person pick, victory screen) ignore this
if (game.screen !is PolicyPickerScreen || !policies.canAdoptPolicy()) {
game.resetToWorldScreen()
dispose()
} else {
val policyScreen = PolicyPickerScreen(worldScreen)
policyScreen.scrollPane.scrollPercentX = scrollPane.scrollPercentX

View File

@ -30,8 +30,6 @@ class PromotionPickerScreen(val unit: MapUnit) : PickerScreen() {
game.setScreen(PromotionPickerScreen(unit).setScrollY(scrollPane.scrollY))
else
game.resetToWorldScreen()
dispose()
game.worldScreen.shouldUpdate = true
}
init {
@ -43,7 +41,7 @@ class PromotionPickerScreen(val unit: MapUnit) : PickerScreen() {
acceptPromotion(selectedPromotion)
}
val canBePromoted = unit.promotions.canBePromoted()
val canChangeState = game.worldScreen.canChangeState
val canChangeState = game.worldScreen!!.canChangeState
val canPromoteNow = canBePromoted && canChangeState
&& unit.currentMovement > 0 && unit.attacksThisTurn == 0
rightSideButton.isEnabled = canPromoteNow

View File

@ -67,7 +67,6 @@ abstract class ReligionPickerScreenCommon(
rightSideButton.onClick(UncivSound.Choir) {
choosingCiv.religionManager.action()
UncivGame.Current.resetToWorldScreen()
dispose()
}
}

View File

@ -88,8 +88,6 @@ class TechPickerScreen(
game.settings.addCompletedTutorialTask("Pick technology")
game.resetToWorldScreen()
game.worldScreen.shouldUpdate = true
dispose()
}
// per default show current/recent technology,
@ -265,7 +263,7 @@ class TechPickerScreen(
return
}
if (!UncivGame.Current.worldScreen.canChangeState) {
if (!UncivGame.Current.worldScreen!!.canChangeState) {
rightSideButton.disable()
return
}

View File

@ -2,6 +2,7 @@ package com.unciv.ui.popup
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.Stage
import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.Button
import com.badlogic.gdx.scenes.scene2d.ui.Cell
@ -27,7 +28,12 @@ import com.unciv.ui.utils.extensions.toTextButton
* Base class for all Popups, i.e. Tables that get rendered in the middle of a screen and on top of everything else
*/
@Suppress("MemberVisibilityCanBePrivate")
open class Popup(val screen: BaseScreen): Table(BaseScreen.skin) {
open class Popup(
val stageToShowOn: Stage
): Table(BaseScreen.skin) {
constructor(screen: BaseScreen) : this(screen.stage)
// This exists to differentiate the actual popup (the inner table)
// from the 'screen blocking' part of the popup (which covers the entire screen)
val innerTable = Table(BaseScreen.skin)
@ -38,11 +44,15 @@ open class Popup(val screen: BaseScreen): Table(BaseScreen.skin) {
*/
val keyPressDispatcher = KeyPressDispatcher(this.javaClass.simpleName)
val closeListeners = mutableListOf<() -> Unit>()
val scrollPane: AutoScrollPane
init {
// Set actor name for debugging
name = javaClass.simpleName
val scrollPane = AutoScrollPane(innerTable, BaseScreen.skin)
scrollPane = AutoScrollPane(innerTable, BaseScreen.skin)
background = ImageGetter.getBackground(Color.GRAY.cpy().apply { a=.5f })
innerTable.background = ImageGetter.getBackground(ImageGetter.getBlue().darken(0.5f))
@ -61,11 +71,11 @@ open class Popup(val screen: BaseScreen): Table(BaseScreen.skin) {
* closed. Use [force] = true if you want to open this popup above the other one anyway.
*/
fun open(force: Boolean = false) {
screen.stage.addActor(this)
stageToShowOn.addActor(this)
innerTable.pack()
pack()
center(screen.stage)
if (force || !screen.hasOpenPopups()) {
center(stageToShowOn)
if (force || !stageToShowOn.hasOpenPopups()) {
show()
}
}
@ -73,18 +83,19 @@ open class Popup(val screen: BaseScreen): Table(BaseScreen.skin) {
/** Subroutine for [open] handles only visibility and [keyPressDispatcher] */
private fun show() {
this.isVisible = true
val currentCount = screen.countOpenPopups()
val currentCount = stageToShowOn.countOpenPopups()
// the lambda is for stacked key dispatcher precedence:
keyPressDispatcher.install(screen.stage) { screen.countOpenPopups() > currentCount }
keyPressDispatcher.install(stageToShowOn) { stageToShowOn.countOpenPopups() > currentCount }
}
/**
* Close this popup and - if any other popups are pending - display the next one.
*/
open fun close() {
for (listener in closeListeners) listener()
keyPressDispatcher.uninstall()
remove()
val nextPopup = screen.stage.actors.firstOrNull { it is Popup }
val nextPopup = stageToShowOn.actors.firstOrNull { it is Popup }
if (nextPopup != null) (nextPopup as Popup).show()
}
@ -104,7 +115,7 @@ open class Popup(val screen: BaseScreen): Table(BaseScreen.skin) {
val label = text.toLabel(fontSize = size)
label.wrap = true
label.setAlignment(Align.center)
return add(label).width(screen.stage.width / 2)
return add(label).width(stageToShowOn.width / 2)
}
/**
@ -219,9 +230,9 @@ open class Popup(val screen: BaseScreen): Table(BaseScreen.skin) {
* [FocusListener][com.badlogic.gdx.scenes.scene2d.utils.FocusListener] cancels the event.
*/
var keyboardFocus: Actor?
get() = screen.stage.keyboardFocus
get() = stageToShowOn.keyboardFocus
set(value) {
if (screen.stage.setKeyboardFocus(value))
if (stageToShowOn.setKeyboardFocus(value))
(value as? TextField)?.selectAll()
}
}
@ -230,25 +241,22 @@ open class Popup(val screen: BaseScreen): Table(BaseScreen.skin) {
* Checks if there are visible [Popup]s.
* @return `true` if any were found.
*/
fun BaseScreen.hasOpenPopups(): Boolean = stage.actors.any { it is Popup && it.isVisible }
fun BaseScreen.hasOpenPopups(): Boolean = stage.hasOpenPopups()
private fun Stage.hasOpenPopups(): Boolean = actors.any { it is Popup && it.isVisible }
/**
* Counts number of visible[Popup]s.
*
* Used for key dispatcher precedence.
*/
fun BaseScreen.countOpenPopups() = stage.actors.count { it is Popup && it.isVisible }
private fun Stage.countOpenPopups() = actors.count { it is Popup && it.isVisible }
/** Closes all [Popup]s. */
fun BaseScreen.closeAllPopups() = popups.forEach { it.close() }
/**
* Closes the topmost visible [Popup].
* @return The [name][Popup.name] of the closed [Popup] if any popup was closed and if it had a name.
*/
fun BaseScreen.closeOneVisiblePopup() = popups.lastOrNull { it.isVisible }?.apply { close() }?.name
fun BaseScreen.closeAllPopups() = stage.popups.forEach { it.close() }
/** @return A [List] of currently active or pending [Popup] screens. */
val BaseScreen.popups: List<Popup>
get() = stage.actors.filterIsInstance<Popup>()
val BaseScreen.popups
get() = stage.popups
private val Stage.popups: List<Popup>
get() = actors.filterIsInstance<Popup>()

View File

@ -1,5 +1,6 @@
package com.unciv.ui.popup
import com.badlogic.gdx.scenes.scene2d.Stage
import com.unciv.ui.crashhandling.launchCrashHandling
import com.unciv.ui.crashhandling.postCrashHandlingRunnable
import com.unciv.ui.utils.BaseScreen
@ -10,7 +11,10 @@ import kotlinx.coroutines.delay
* This is an unobtrusive popup which will close itself after a given amount of time.
* Default time is two seconds (in milliseconds)
*/
class ToastPopup (message: String, screen: BaseScreen, val time: Long = 2000) : Popup(screen){
class ToastPopup (message: String, stage: Stage, val time: Long = 2000) : Popup(stage){
constructor(message: String, screen: BaseScreen, time: Long = 2000) : this(message, screen.stage, time)
init {
//Make this popup unobtrusive
setFillParent(false)
@ -20,7 +24,7 @@ class ToastPopup (message: String, screen: BaseScreen, val time: Long = 2000) :
open()
//move it to the top so its not in the middle of the screen
//have to be done after open() because open() centers the popup
y = screen.stage.height - (height + 20f)
y = stage.height - (height + 20f)
}
private fun startTimer(){

View File

@ -1,6 +1,7 @@
package com.unciv.ui.popup
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.scenes.scene2d.Stage
import com.badlogic.gdx.utils.Align
import com.unciv.Constants
import com.unciv.UncivGame
@ -14,12 +15,19 @@ import com.unciv.ui.utils.extensions.toLabel
* @param screen The parent screen - see [Popup.screen]. Optional, defaults to the current [WorldScreen][com.unciv.ui.worldscreen.WorldScreen]
* @param restoreDefault A lambda to execute when "No" is chosen
*/
open class YesNoPopup (
open class YesNoPopup(
question: String,
action: ()->Unit,
screen: BaseScreen = UncivGame.Current.worldScreen,
restoreDefault: ()->Unit = {}
) : Popup(screen) {
stage: Stage,
restoreDefault: () -> Unit = {},
action: () -> Unit
) : Popup(stage) {
constructor(
question: String,
screen: BaseScreen,
restoreDefault: () -> Unit = {},
action: () -> Unit
) : this(question, screen.stage, restoreDefault, action)
/** The [Label][com.badlogic.gdx.scenes.scene2d.ui.Label] created for parameter `question` for optional layout tweaking */
private val promptLabel = question.toLabel()
@ -34,13 +42,12 @@ open class YesNoPopup (
}
/** Shortcut to open a [YesNoPopup] with the exit game question */
class ExitGamePopup(screen: BaseScreen, force: Boolean = false)
: YesNoPopup(
question = "Do you want to exit the game?",
action = { Gdx.app.exit() },
screen = screen,
restoreDefault = { screen.game.musicController.resume() }
) {
class ExitGamePopup(screen: BaseScreen, force: Boolean = false) : YesNoPopup(
question = "Do you want to exit the game?",
screen = screen,
restoreDefault = { screen.game.musicController.resume() },
action = { Gdx.app.exit() }
) {
init {
screen.game.musicController.pause()
open(force)

View File

@ -6,9 +6,11 @@ import com.unciv.UncivGame
import com.unciv.logic.GameInfo
import com.unciv.ui.crashhandling.launchCrashHandling
import com.unciv.ui.crashhandling.postCrashHandlingRunnable
import com.unciv.ui.multiplayer.MultiplayerHelpers
import com.unciv.ui.popup.Popup
import com.unciv.ui.popup.ToastPopup
import com.unciv.ui.worldscreen.WorldScreen
import com.unciv.utils.Log
//todo reduce code duplication
@ -70,6 +72,7 @@ object QuickSave {
outOfMemory()
return@launchCrashHandling
} catch (ex: Exception) {
Log.error("Could not autoload game", ex)
postCrashHandlingRunnable {
loadingPopup.close()
ToastPopup("Cannot resume game!", screen)
@ -82,6 +85,13 @@ object QuickSave {
screen.game.onlineMultiplayer.loadGame(savedGame)
} catch (oom: OutOfMemoryError) {
outOfMemory()
} catch (ex: Exception) {
val message = MultiplayerHelpers.getLoadExceptionMessage(ex)
Log.error("Could not autoload game", ex)
postCrashHandlingRunnable {
loadingPopup.close()
ToastPopup(message, screen)
}
}
} else {
postCrashHandlingRunnable { /// ... and load it into the screen on main thread for GL context
@ -89,6 +99,10 @@ object QuickSave {
screen.game.loadGame(savedGame)
} catch (oom: OutOfMemoryError) {
outOfMemory()
} catch (ex: Exception) {
Log.error("Could not autoload game", ex)
loadingPopup.close()
ToastPopup("Cannot resume game!", screen)
}
}
}

View File

@ -33,7 +33,7 @@ class SaveGameScreen(val gameInfo: GameInfo) : LoadOrSaveScreen("Current saves")
rightSideButton.setText("Save game".tr())
val saveAction = {
if (game.gameSaver.getSave(gameNameTextField.text).exists())
YesNoPopup("Overwrite existing file?", { saveGame() }, this).open()
YesNoPopup("Overwrite existing file?", this) { saveGame() }.open()
else saveGame()
}
rightSideButton.onClick(saveAction)

View File

@ -172,7 +172,7 @@ class TileGroupIcons(val tileGroup: TileGroup) {
val shouldDisplayResource =
if (tileGroup.showEntireMap) showResourcesAndImprovements
else showResourcesAndImprovements
&& tileGroup.tileInfo.hasViewableResource(UncivGame.Current.worldScreen.viewingCiv)
&& tileGroup.tileInfo.hasViewableResource(UncivGame.Current.worldScreen!!.viewingCiv)
tileGroup.resourceImage!!.isVisible = shouldDisplayResource
}
}

View File

@ -73,7 +73,7 @@ class DiplomacyScreen(
private val rightSideTable = Table()
private val closeButton = Constants.close.toTextButton()
private fun isNotPlayersTurn() = !UncivGame.Current.worldScreen.canChangeState
private fun isNotPlayersTurn() = !UncivGame.Current.worldScreen!!.canChangeState
init {
onBackButtonClicked { UncivGame.Current.resetToWorldScreen() }
@ -205,7 +205,7 @@ class DiplomacyScreen(
wrapper.addTooltip(name, 18f)
wrapper.onClick {
val pedia = CivilopediaScreen(
UncivGame.Current.gameInfo.ruleSet,
UncivGame.Current.gameInfo!!.ruleSet,
this,
link = "Resource/$name"
)
@ -389,11 +389,11 @@ class DiplomacyScreen(
private fun getRevokeProtectionButton(otherCiv: CivilizationInfo): TextButton {
val revokeProtectionButton = "Revoke Protection".toTextButton()
revokeProtectionButton.onClick {
YesNoPopup("Revoke protection for [${otherCiv.civName}]?", {
YesNoPopup("Revoke protection for [${otherCiv.civName}]?", this) {
otherCiv.removeProtectorCiv(viewingCiv)
updateLeftSideTable(otherCiv)
updateRightSide(otherCiv)
}, this).open()
}.open()
}
if (isNotPlayersTurn() || !otherCiv.otherCivCanWithdrawProtection(viewingCiv)) revokeProtectionButton.disable()
return revokeProtectionButton
@ -402,11 +402,11 @@ class DiplomacyScreen(
private fun getPledgeToProtectButton(otherCiv: CivilizationInfo): TextButton {
val protectionButton = "Pledge to protect".toTextButton()
protectionButton.onClick {
YesNoPopup("Declare Protection of [${otherCiv.civName}]?", {
YesNoPopup("Declare Protection of [${otherCiv.civName}]?", this) {
otherCiv.addProtectorCiv(viewingCiv)
updateLeftSideTable(otherCiv)
updateRightSide(otherCiv)
}, this).open()
}.open()
}
if (isNotPlayersTurn() || !otherCiv.otherCivCanPledgeProtection(viewingCiv)) protectionButton.disable()
return protectionButton
@ -418,7 +418,7 @@ class DiplomacyScreen(
): TextButton {
val peaceButton = "Negotiate Peace".toTextButton()
peaceButton.onClick {
YesNoPopup("Peace with [${otherCiv.civName}]?", {
YesNoPopup("Peace with [${otherCiv.civName}]?", this) {
val tradeLogic = TradeLogic(viewingCiv, otherCiv)
tradeLogic.currentTrade.ourOffers.add(
TradeOffer(
@ -435,7 +435,7 @@ class DiplomacyScreen(
tradeLogic.acceptTrade()
updateLeftSideTable(otherCiv)
updateRightSide(otherCiv)
}, this).open()
}.open()
}
val cityStatesAlly = otherCiv.getAllyCiv()
val atWarWithItsAlly = viewingCiv.getKnownCivs()
@ -761,11 +761,11 @@ class DiplomacyScreen(
): TextButton {
val denounceButton = "Denounce ([30] turns)".toTextButton()
denounceButton.onClick {
YesNoPopup("Denounce [${otherCiv.civName}]?", {
YesNoPopup("Denounce [${otherCiv.civName}]?", this) {
diplomacyManager.denounce()
updateLeftSideTable(otherCiv)
setRightSideFlavorText(otherCiv, "We will remember this.", "Very well.")
}, this).open()
}.open()
}
if (isNotPlayersTurn()) denounceButton.disable()
return denounceButton
@ -956,12 +956,12 @@ class DiplomacyScreen(
declareWarButton.setText(declareWarButton.text.toString() + " ($turnsToPeaceTreaty${Fonts.turn})")
}
declareWarButton.onClick {
YesNoPopup("Declare war on [${otherCiv.civName}]?", {
YesNoPopup("Declare war on [${otherCiv.civName}]?", this) {
diplomacyManager.declareWar()
setRightSideFlavorText(otherCiv, otherCiv.nation.attacked, "Very well.")
updateLeftSideTable(otherCiv)
UncivGame.Current.musicController.chooseTrack(otherCiv.civName, MusicMood.War, MusicTrackChooserFlags.setSpecific)
}, this).open()
}.open()
}
if (isNotPlayersTurn()) declareWarButton.disable()
return declareWarButton
@ -996,8 +996,11 @@ class DiplomacyScreen(
private fun getGoToOnMapButton(civilization: CivilizationInfo): TextButton {
val goToOnMapButton = "Go to on map".toTextButton()
goToOnMapButton.onClick {
UncivGame.Current.resetToWorldScreen()
UncivGame.Current.worldScreen.mapHolder.setCenterPosition(civilization.getCapital()!!.location, selectUnit = false)
val worldScreen = UncivGame.Current.worldScreen
if (worldScreen != null) {
UncivGame.Current.resetToWorldScreen()
worldScreen.mapHolder.setCenterPosition(civilization.getCapital()!!.location, selectUnit = false)
}
}
return goToOnMapButton
}

View File

@ -92,7 +92,7 @@ class OffersListScroll(
Luxury_Resource, Strategic_Resource ->
ImageGetter.getResourceImage(offer.name, 30f)
WarDeclaration ->
ImageGetter.getNationIndicator(UncivGame.Current.gameInfo.ruleSet.nations[offer.name]!!, 30f)
ImageGetter.getNationIndicator(UncivGame.Current.gameInfo!!.ruleSet.nations[offer.name]!!, 30f)
else -> null
}
val tradeButton = IconTextButton(tradeLabel, tradeIcon).apply {

View File

@ -7,16 +7,22 @@ import com.badlogic.gdx.graphics.GL20
import com.badlogic.gdx.graphics.g2d.BitmapFont
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.scenes.scene2d.Stage
import com.badlogic.gdx.scenes.scene2d.ui.*
import com.badlogic.gdx.scenes.scene2d.ui.CheckBox
import com.badlogic.gdx.scenes.scene2d.ui.Label
import com.badlogic.gdx.scenes.scene2d.ui.SelectBox
import com.badlogic.gdx.scenes.scene2d.ui.Skin
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
import com.badlogic.gdx.scenes.scene2d.ui.TextField
import com.badlogic.gdx.scenes.scene2d.utils.Drawable
import com.badlogic.gdx.utils.viewport.ExtendViewport
import com.unciv.UncivGame
import com.unciv.models.Tutorial
import com.unciv.ui.UncivStage
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.options.OptionsPopup
import com.unciv.ui.popup.hasOpenPopups
import com.unciv.ui.tutorials.TutorialController
import com.unciv.ui.options.OptionsPopup
import com.unciv.ui.utils.extensions.isNarrowerThan4to3
abstract class BaseScreen : Screen {
@ -122,7 +128,7 @@ abstract class BaseScreen : Screen {
fun isCrampedPortrait() = isPortrait() &&
game.settings.resolution.split("x").map { it.toInt() }.last() <= 700
/** @return `true` if the screen is narrower than 4:3 landscape */
fun isNarrowerThan4to3() = stage.viewport.screenHeight * 4 > stage.viewport.screenWidth * 3
fun isNarrowerThan4to3() = stage.isNarrowerThan4to3()
fun openOptionsPopup(startingPage: Int = OptionsPopup.defaultPage, onClose: () -> Unit = {}) {
OptionsPopup(this, startingPage, onClose).open(force = true)

View File

@ -289,7 +289,7 @@ open class TabbedPager(
//region Initialization
init {
val screen = (if (UncivGame.isCurrentInitialized()) UncivGame.Current.screen else null) as? BaseScreen
val screen = (if (UncivGame.isCurrentInitialized()) UncivGame.Current.screen else null)
val (screenWidth, screenHeight) = (screen?.stage?.run { width to height }) ?: (Float.MAX_VALUE to Float.MAX_VALUE)
dimW = DimensionMeasurement.from(minimumWidth, maximumWidth, screenWidth)
dimH = DimensionMeasurement.from(minimumHeight, maximumHeight, screenHeight)
@ -601,7 +601,7 @@ open class TabbedPager(
if (!UncivGame.isCurrentInitialized() || askPasswordLock || deferredSecretPages.isEmpty()) return
askPasswordLock = true // race condition: Popup closes _first_, then deferredSecretPages is emptied -> parent shows and calls us again
PassPopup(UncivGame.Current.screen as BaseScreen, {
PassPopup(UncivGame.Current.screen!!, {
addDeferredSecrets()
}, {
deferredSecretPages.clear()

View File

@ -229,3 +229,6 @@ fun WidgetGroup.packIfNeeded(): WidgetGroup {
if (needsLayout()) pack()
return this
}
/** @return `true` if the screen is narrower than 4:3 landscape */
fun Stage.isNarrowerThan4to3() = viewport.screenHeight * 4 > viewport.screenWidth * 3

View File

@ -3,25 +3,24 @@ package com.unciv.ui.worldscreen
import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.Constants
import com.unciv.logic.GameInfo
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.ui.crashhandling.postCrashHandlingRunnable
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.utils.BaseScreen
import com.unciv.ui.utils.extensions.onClick
import com.unciv.ui.utils.extensions.toLabel
class PlayerReadyScreen(gameInfo: GameInfo, currentPlayerCiv: CivilizationInfo) : BaseScreen(){
class PlayerReadyScreen(worldScreen: WorldScreen) : BaseScreen() {
init {
val table= Table()
table.touchable= Touchable.enabled
table.background= ImageGetter.getBackground(currentPlayerCiv.nation.getOuterColor())
val table = Table()
table.touchable = Touchable.enabled
val curCiv = worldScreen.viewingCiv
table.background = ImageGetter.getBackground(curCiv.nation.getOuterColor())
table.add("[$currentPlayerCiv] ready?".toLabel(currentPlayerCiv.nation.getInnerColor(), Constants.headingFontSize))
table.add("[$curCiv] ready?".toLabel(curCiv.nation.getInnerColor(), Constants.headingFontSize))
table.onClick {
postCrashHandlingRunnable { // To avoid ANRs on Android when the creation of the worldscreen takes more than 500ms
game.resetToWorldScreen(WorldScreen(gameInfo, currentPlayerCiv))
game.setScreen(worldScreen)
}
}
table.setFillParent(true)

View File

@ -216,7 +216,7 @@ class WorldMapHolder(
}
private fun onTileRightClicked(unit: MapUnit, tile: TileInfo) {
if (UncivGame.Current.gameInfo.currentPlayerCiv.isSpectator()) {
if (UncivGame.Current.gameInfo!!.currentPlayerCiv.isSpectator()) {
return
}
removeUnitActionOverlay()

View File

@ -336,7 +336,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas
try {
debug("loadLatestMultiplayerState current game: gameId: %s, turn: %s, curCiv: %s",
game.worldScreen.gameInfo.gameId, game.worldScreen.gameInfo.turns, game.worldScreen.gameInfo.currentPlayer)
gameInfo.gameId, gameInfo.turns, gameInfo.currentPlayer)
val latestGame = game.onlineMultiplayer.downloadGame(gameInfo.gameId)
debug("loadLatestMultiplayerState downloaded game: gameId: %s, turn: %s, curCiv: %s",
latestGame.gameId, latestGame.turns, latestGame.currentPlayer)
@ -345,8 +345,8 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas
}
postCrashHandlingRunnable {
loadingGamePopup.close()
if (game.gameInfo.gameId == gameInfo.gameId) { // game could've been changed during download
createNewWorldScreen(latestGame)
if (game.gameInfo!!.gameId == gameInfo.gameId) { // game could've been changed during download
game.setScreen(createNewWorldScreen(latestGame))
}
}
} catch (ex: Throwable) {
@ -602,7 +602,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas
}
private fun createNewWorldScreen(gameInfo: GameInfo, resize:Boolean=false) {
private fun createNewWorldScreen(gameInfo: GameInfo, resize:Boolean=false): WorldScreen {
game.gameInfo = gameInfo
val newWorldScreen = WorldScreen(gameInfo, gameInfo.getPlayerToViewAs())
@ -621,7 +621,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas
newWorldScreen.selectedCiv = gameInfo.getCivilization(selectedCiv.civName)
newWorldScreen.fogOfWar = fogOfWar
game.resetToWorldScreen(newWorldScreen)
return newWorldScreen
}
fun nextTurn() {
@ -669,16 +669,15 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas
// 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
postCrashHandlingRunnable {
val newWorldScreen = createNewWorldScreen(gameInfoClone)
if (gameInfoClone.currentPlayerCiv.civName != viewingCiv.civName
&& !gameInfoClone.gameParameters.isOnlineMultiplayer)
game.setScreen(PlayerReadyScreen(gameInfoClone, gameInfoClone.getCurrentPlayerCivilization()))
else {
createNewWorldScreen(gameInfoClone)
&& !gameInfoClone.gameParameters.isOnlineMultiplayer) {
game.setScreen(PlayerReadyScreen(newWorldScreen))
} else {
game.setScreen(newWorldScreen)
}
if (shouldAutoSave) {
val newWorldScreen = this@WorldScreen.game.worldScreen
newWorldScreen.waitingForAutosave = true
newWorldScreen.shouldUpdate = true
game.gameSaver.autoSave(gameInfoClone) {
@ -821,7 +820,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas
nextTurn()
}
if (game.settings.confirmNextTurn) {
YesNoPopup("Confirm next turn", action, this).open()
YesNoPopup("Confirm next turn", this, action = action).open()
} else {
action()
}

View File

@ -26,7 +26,7 @@ class TileInfoTable(private val viewingCiv :CivilizationInfo) : Table(BaseScreen
if (tile != null && (UncivGame.Current.viewEntireMapForDebug || viewingCiv.exploredTiles.contains(tile.position)) ) {
add(getStatsTable(tile))
add( MarkupRenderer.render(tile.toMarkup(viewingCiv), padding = 0f, iconDisplay = IconDisplay.None) {
UncivGame.Current.setScreen(CivilopediaScreen(viewingCiv.gameInfo.ruleSet, UncivGame.Current.worldScreen, link = it))
UncivGame.Current.setScreen(CivilopediaScreen(viewingCiv.gameInfo.ruleSet, UncivGame.Current.worldScreen!!, link = it))
} ).pad(5f).row()
if (UncivGame.Current.viewEntireMapForDebug)
add(tile.position.run { "(${x.toInt()},${y.toInt()})" }.toLabel()).colspan(2).pad(5f)

View File

@ -42,7 +42,7 @@ class MapOverlayToggleButton(
/** Toggle overlay. Called on click. */
fun toggle() {
setter(!getter())
UncivGame.Current.worldScreen.shouldUpdate = true
UncivGame.Current.worldScreen!!.shouldUpdate = true
// Setting worldScreen.shouldUpdate implicitly causes this.update() to be called by the WorldScreen on the next update.
}

View File

@ -127,7 +127,7 @@ object UnitActions {
val disbandText = if (unit.currentTile.getOwner() == unit.civInfo)
"Disband this unit for [${unit.baseUnit.getDisbandGold(unit.civInfo)}] gold?".tr()
else "Do you really want to disband this unit?".tr()
YesNoPopup(disbandText, { unit.disband(); worldScreen.shouldUpdate = true }).open()
YesNoPopup(disbandText, UncivGame.Current.worldScreen!!) { unit.disband(); worldScreen.shouldUpdate = true }.open()
}
}.takeIf { unit.currentMovement > 0 })
}
@ -187,7 +187,7 @@ object UnitActions {
if (tile.ruleset.tileImprovements.containsKey("City center"))
tile.improvement = "City center"
unit.destroy()
UncivGame.Current.worldScreen.shouldUpdate = true
UncivGame.Current.worldScreen!!.shouldUpdate = true
}
if (unit.civInfo.playerType == PlayerType.AI)
@ -204,7 +204,7 @@ object UnitActions {
else {
// ask if we would be breaking a promise
val text = "Do you want to break your promise to [$leaders]?"
YesNoPopup(text, foundAction, UncivGame.Current.worldScreen).open(force = true)
YesNoPopup(text, UncivGame.Current.worldScreen!!, action = foundAction).open(force = true)
}
}
)
@ -275,7 +275,7 @@ object UnitActions {
else actionList += UnitAction(type = UnitActionType.Pillage) {
if (!worldScreen.hasOpenPopups()) {
val pillageText = "Are you sure you want to pillage this [${unit.currentTile.improvement}]?"
YesNoPopup(pillageText, { (pillageAction.action)(); worldScreen.shouldUpdate = true }).open()
YesNoPopup(pillageText, UncivGame.Current.worldScreen!!) { (pillageAction.action)(); worldScreen.shouldUpdate = true }.open()
}
}
}
@ -853,7 +853,7 @@ object UnitActions {
unit.destroy() // City states dont get GPs
else
unit.gift(recipient)
UncivGame.Current.worldScreen.shouldUpdate = true
UncivGame.Current.worldScreen!!.shouldUpdate = true
}
return UnitAction(UnitActionType.GiftUnit, action = giftAction)

View File

@ -39,7 +39,7 @@ class UnitActionsTable(val worldScreen: WorldScreen) : Table() {
actionButton.pack()
val action = {
unitAction.action?.invoke()
UncivGame.Current.worldScreen.shouldUpdate = true
UncivGame.Current.worldScreen!!.shouldUpdate = true
}
if (unitAction.action == null) actionButton.disable()
else {

View File

@ -87,6 +87,7 @@ internal object DesktopLauncher {
try {
updateRpc(game)
} catch (ex: Exception) {
debug("Exception while updating Discord Rich Presence", ex)
}
}
} catch (ex: Throwable) {
@ -98,10 +99,15 @@ internal object DesktopLauncher {
private fun updateRpc(game: UncivGame) {
if (!game.isInitialized) return
val presence = DiscordRichPresence()
val currentPlayerCiv = game.gameInfo.getCurrentPlayerCivilization()
presence.details = "${currentPlayerCiv.nation.leaderName} of ${currentPlayerCiv.nation.name}"
presence.largeImageKey = "logo" // The actual image is uploaded to the discord app / applications webpage
presence.largeImageText = "Turn" + " " + currentPlayerCiv.gameInfo.turns
val gameInfo = game.gameInfo
if (gameInfo != null) {
val currentPlayerCiv = gameInfo.getCurrentPlayerCivilization()
presence.details = "${currentPlayerCiv.nation.leaderName} of ${currentPlayerCiv.nation.name}"
presence.largeImageText = "Turn" + " " + currentPlayerCiv.gameInfo.turns
}
DiscordRPC.INSTANCE.Discord_UpdatePresence(presence)
}
}