Unciv/core/src/com/unciv/UncivGame.kt

221 lines
8.5 KiB
Kotlin

package com.unciv
import com.badlogic.gdx.Application
import com.badlogic.gdx.Game
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.Input
import com.badlogic.gdx.audio.Music
import com.badlogic.gdx.scenes.scene2d.actions.Actions
import com.badlogic.gdx.utils.Align
import com.unciv.logic.GameInfo
import com.unciv.logic.GameSaver
import com.unciv.logic.civilization.PlayerType
import com.unciv.models.metadata.GameSettings
import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.tilesets.TileSetCache
import com.unciv.models.translations.Translations
import com.unciv.ui.LanguagePickerScreen
import com.unciv.ui.utils.*
import com.unciv.ui.worldscreen.PlayerReadyScreen
import com.unciv.ui.worldscreen.WorldScreen
import java.util.*
import kotlin.concurrent.thread
class UncivGame(parameters: UncivGameParameters) : Game() {
// we need this secondary constructor because Java code for iOS can't handle Kotlin lambda parameters
constructor(version: String) : this(UncivGameParameters(version, null))
val version = parameters.version
private val crashReportSender = parameters.crashReportSender
val cancelDiscordEvent = parameters.cancelDiscordEvent
val fontImplementation = parameters.fontImplementation
val consoleMode = parameters.consoleMode
val customSaveLocationHelper = parameters.customSaveLocationHelper
val limitOrientationsHelper = parameters.limitOrientationsHelper
lateinit var gameInfo: GameInfo
fun isGameInfoInitialized() = this::gameInfo.isInitialized
lateinit var settings: GameSettings
lateinit var crashController: CrashController
/**
* This exists so that when debugging we can see the entire map.
* Remember to turn this to false before commit and upload!
*/
var viewEntireMapForDebug = false
/** For when you need to test something in an advanced game and don't have time to faff around */
val superchargedForDebug = false
/** Simulate until this turn on the first "Next turn" button press.
* Does not update World View changes until finished.
* Set to 0 to disable.
*/
val simulateUntilTurnForDebug: Int = 0
/** Console log battles
*/
val alertBattle = false
lateinit var worldScreen: WorldScreen
var music: Music? = null
val musicLocation = "music/thatched-villagers.mp3"
var isInitialized = false
val translations = Translations()
override fun create() {
Gdx.input.setCatchKey(Input.Keys.BACK, true)
if (Gdx.app.type != Application.ApplicationType.Desktop) {
viewEntireMapForDebug = false
}
Current = this
GameSaver.customSaveLocationHelper = customSaveLocationHelper
// If this takes too long players, especially with older phones, get ANR problems.
// Whatever needs graphics needs to be done on the main thread,
// So it's basically a long set of deferred actions.
/** When we recreate the GL context for whatever reason (say - we moved to a split screen on Android),
* ALL objects that were related to the old context - need to be recreated.
* So far we have:
* - All textures (hence the texture atlas)
* - SpriteBatch (hence CameraStageBaseScreen uses a new SpriteBatch for each screen)
* - Skin (hence CameraStageBaseScreen.setSkin())
* - Font (hence Fonts.resetFont() inside setSkin())
*/
ImageGetter.resetAtlases()
settings = GameSaver.getGeneralSettings() // needed for the screen
ImageGetter.setNewRuleset(ImageGetter.ruleset) // This needs to come after the settings, since we may have default visual mods
if(settings.tileSet !in ImageGetter.getAvailableTilesets()) { // If one of the tilesets is no longer available, default back
settings.tileSet = "FantasyHex"
}
CameraStageBaseScreen.setSkin() // needs to come AFTER the Texture reset, since the buttons depend on it
Gdx.graphics.isContinuousRendering = settings.continuousRendering
screen = LoadingScreen()
thread(name = "LoadJSON") {
RulesetCache.loadRulesets(printOutput = true)
translations.tryReadTranslationForCurrentLanguage()
translations.loadPercentageCompleteOfLanguages()
TileSetCache.loadTileSetConfigs(printOutput = true)
if (settings.userId.isEmpty()) { // assign permanent user id
settings.userId = UUID.randomUUID().toString()
settings.save()
}
// This stuff needs to run on the main thread because it needs the GL context
Gdx.app.postRunnable {
ImageGetter.ruleset = RulesetCache.getBaseRuleset() // so that we can enter the map editor without having to load a game first
thread(name="Music") { startMusic() }
if (settings.isFreshlyCreated) {
setScreen(LanguagePickerScreen())
} else { setScreen(MainMenuScreen()) }
isInitialized = true
}
}
crashController = CrashController.Impl(crashReportSender)
}
fun loadGame(gameInfo: GameInfo) {
this.gameInfo = gameInfo
ImageGetter.setNewRuleset(gameInfo.ruleSet)
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()))
else {
worldScreen = WorldScreen(gameInfo, gameInfo.getPlayerToViewAs())
setWorldScreen()
}
}
fun startMusic() {
if (settings.musicVolume < 0.01) return
val musicFile = Gdx.files.local(musicLocation)
if (musicFile.exists()) {
music = Gdx.audio.newMusic(musicFile)
music!!.isLooping = true
music!!.volume = 0.4f * settings.musicVolume
music!!.play()
}
}
fun setScreen(screen: CameraStageBaseScreen) {
Gdx.input.inputProcessor = screen.stage
super.setScreen(screen)
}
fun setWorldScreen() {
if (screen != null && screen != worldScreen) screen.dispose()
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()
}
// This is ALWAYS called after create() on Android - google "Android life cycle"
override fun resume() {
super.resume()
if (!isInitialized) return // The stuff from Create() is still happening, so the main screen will load eventually
}
override fun pause() {
if (isGameInfoInitialized()) GameSaver.autoSave(this.gameInfo)
super.pause()
}
override fun resize(width: Int, height: Int) {
screen.resize(width, height)
}
override fun dispose() {
cancelDiscordEvent?.invoke()
Sounds.clearCache()
// Log still running threads (on desktop that should be only this one and "DestroyJavaVM")
val numThreads = Thread.activeCount()
val threadList = Array(numThreads) { _ -> Thread() }
Thread.enumerate(threadList)
if (isGameInfoInitialized()){
val autoSaveThread = threadList.firstOrNull { it.name == "Autosave" }
if (autoSaveThread != null && autoSaveThread.isAlive) {
// 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
autoSaveThread.join()
} else
GameSaver.autoSaveSingleThreaded(gameInfo) // NO new thread
settings.save()
}
threadList.filter { it !== Thread.currentThread() && it.name != "DestroyJavaVM"}.forEach {
println (" Thread ${it.name} still running in UncivGame.dispose().")
}
}
companion object {
lateinit var Current: UncivGame
fun isCurrentInitialized() = this::Current.isInitialized
}
}
class LoadingScreen:CameraStageBaseScreen() {
init {
val happinessImage = ImageGetter.getImage("StatIcons/Happiness")
happinessImage.center(stage)
happinessImage.setOrigin(Align.center)
happinessImage.addAction(Actions.sequence(
Actions.delay(1f),
Actions.rotateBy(360f, 0.5f)))
stage.addActor(happinessImage)
}
}