mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-04 07:17:50 +07:00
Cleaning: platform specifics and UncivGame (#8773)
* Cleanup: platform specifics + UncivGame * Fix tests * Fix requests not clearing --------- Co-authored-by: vegeta1k95 <vfylfhby>
This commit is contained in:
@ -18,7 +18,7 @@ import com.unciv.ui.components.Fonts
|
||||
import java.util.*
|
||||
import kotlin.math.abs
|
||||
|
||||
class FontAndroid : FontImplementation {
|
||||
class AndroidFont : FontImplementation {
|
||||
|
||||
private val fontList by lazy {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) emptySet()
|
35
android/src/com/unciv/app/AndroidGame.kt
Normal file
35
android/src/com/unciv/app/AndroidGame.kt
Normal file
@ -0,0 +1,35 @@
|
||||
package com.unciv.app
|
||||
|
||||
import android.os.Build
|
||||
import com.unciv.UncivGame
|
||||
|
||||
class AndroidGame(private val activity: AndroidLauncher) : UncivGame() {
|
||||
|
||||
/*
|
||||
Sources for Info about current orientation in case need:
|
||||
val windowManager = (activity.getSystemService(Context.WINDOW_SERVICE) as WindowManager)
|
||||
val displayRotation = windowManager.defaultDisplay.rotation
|
||||
val currentOrientation = activity.resources.configuration.orientation
|
||||
const val ORIENTATION_UNDEFINED = 0
|
||||
const val ORIENTATION_PORTRAIT = 1
|
||||
const val ORIENTATION_LANDSCAPE = 2
|
||||
*/
|
||||
|
||||
override fun hasDisplayCutout(): Boolean {
|
||||
return when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R ->
|
||||
activity.display?.cutout != null
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ->
|
||||
@Suppress("DEPRECATION")
|
||||
activity.windowManager.defaultDisplay.cutout != null
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ->
|
||||
activity.window.decorView.rootWindowInsets.displayCutout != null
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun allowPortrait(allow: Boolean) {
|
||||
activity.allowPortrait(allow)
|
||||
}
|
||||
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
package com.unciv.app
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.graphics.Rect
|
||||
import android.hardware.display.DisplayManager
|
||||
import android.net.Uri
|
||||
import android.opengl.GLSurfaceView
|
||||
import android.os.Build
|
||||
@ -11,7 +11,7 @@ import android.view.Surface
|
||||
import android.view.SurfaceHolder
|
||||
import android.view.View
|
||||
import android.view.ViewTreeObserver
|
||||
import androidx.annotation.RequiresApi
|
||||
import android.view.WindowManager
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.work.WorkManager
|
||||
import com.badlogic.gdx.Gdx
|
||||
@ -20,9 +20,9 @@ import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration
|
||||
import com.badlogic.gdx.backends.android.AndroidGraphics
|
||||
import com.badlogic.gdx.math.Rectangle
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.UncivGameParameters
|
||||
import com.unciv.logic.files.UncivFiles
|
||||
import com.unciv.logic.event.EventBus
|
||||
import com.unciv.ui.components.Fonts
|
||||
import com.unciv.ui.screens.basescreen.UncivStage
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
import com.unciv.utils.Log
|
||||
@ -30,37 +30,36 @@ import com.unciv.utils.concurrency.Concurrency
|
||||
import java.io.File
|
||||
|
||||
open class AndroidLauncher : AndroidApplication() {
|
||||
private var customFileLocationHelper: CustomFileLocationHelperAndroid? = null
|
||||
|
||||
private var game: UncivGame? = null
|
||||
private var deepLinkedMultiplayerGame: String? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// Setup Android logging
|
||||
Log.backend = AndroidLogBackend()
|
||||
customFileLocationHelper = CustomFileLocationHelperAndroid(this)
|
||||
|
||||
// Setup Android fonts
|
||||
Fonts.fontImplementation = AndroidFont()
|
||||
|
||||
// Setup Android custom saver-loader
|
||||
UncivFiles.saverLoader = AndroidSaverLoader(this)
|
||||
UncivFiles.preferExternalStorage = true
|
||||
|
||||
// Create notification channels for Multiplayer notificator
|
||||
MultiplayerTurnCheckWorker.createNotificationChannels(applicationContext)
|
||||
|
||||
copyMods()
|
||||
|
||||
val config = AndroidApplicationConfiguration().apply {
|
||||
useImmersiveMode = true
|
||||
}
|
||||
|
||||
val config = AndroidApplicationConfiguration().apply { useImmersiveMode = true }
|
||||
val settings = UncivFiles.getSettingsForPlatformLaunchers(filesDir.path)
|
||||
|
||||
// Manage orientation lock and display cutout
|
||||
val platformSpecificHelper = PlatformSpecificHelpersAndroid(this)
|
||||
platformSpecificHelper.allowPortrait(settings.allowAndroidPortrait)
|
||||
// Setup orientation lock and display cutout
|
||||
allowPortrait(settings.allowAndroidPortrait)
|
||||
setDisplayCutout(settings.androidCutout)
|
||||
|
||||
platformSpecificHelper.toggleDisplayCutout(settings.androidCutout)
|
||||
|
||||
val androidParameters = UncivGameParameters(
|
||||
crashReportSysInfo = CrashReportSysInfoAndroid,
|
||||
fontImplementation = FontAndroid(),
|
||||
customFileLocationHelper = customFileLocationHelper,
|
||||
platformSpecificHelper = platformSpecificHelper
|
||||
)
|
||||
|
||||
game = UncivGame(androidParameters)
|
||||
game = AndroidGame(this)
|
||||
initialize(game, config)
|
||||
|
||||
setDeepLinkedGame(intent)
|
||||
@ -73,6 +72,23 @@ open class AndroidLauncher : AndroidApplication() {
|
||||
addScreenRefreshRateListener(glView)
|
||||
}
|
||||
|
||||
fun allowPortrait(allow: Boolean) {
|
||||
val orientation = when {
|
||||
allow -> ActivityInfo.SCREEN_ORIENTATION_USER
|
||||
else -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
|
||||
}
|
||||
// Comparison ensures ActivityTaskManager.getService().setRequestedOrientation isn't called unless necessary
|
||||
if (requestedOrientation != orientation) requestedOrientation = orientation
|
||||
}
|
||||
|
||||
private fun setDisplayCutout(cutout: Boolean) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) return
|
||||
window.attributes.layoutInDisplayCutoutMode = when {
|
||||
cutout -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
||||
else -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
|
||||
}
|
||||
}
|
||||
|
||||
/** Request the best available device frame rate for
|
||||
* the game, as soon as OpenGL surface is created */
|
||||
private fun addScreenRefreshRateListener(surfaceView: GLSurfaceView) {
|
||||
@ -196,7 +212,8 @@ open class AndroidLauncher : AndroidApplication() {
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
customFileLocationHelper?.onActivityResult(requestCode, data)
|
||||
val saverLoader = UncivFiles.saverLoader as AndroidSaverLoader
|
||||
saverLoader.onActivityResult(requestCode, data)
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,13 @@ class AndroidLogBackend : LogBackend {
|
||||
override fun isRelease(): Boolean {
|
||||
return !BuildConfig.DEBUG
|
||||
}
|
||||
|
||||
override fun getSystemInfo(): String {
|
||||
return """
|
||||
Device Model: ${Build.MODEL}
|
||||
API Level: ${Build.VERSION.SDK_INT}
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
private fun toAndroidTag(tag: Tag): String {
|
||||
|
113
android/src/com/unciv/app/AndroidSaverLoader.kt
Normal file
113
android/src/com/unciv/app/AndroidSaverLoader.kt
Normal file
@ -0,0 +1,113 @@
|
||||
package com.unciv.app
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.DocumentsContract
|
||||
import android.provider.OpenableColumns
|
||||
import com.unciv.logic.files.PlatformSaverLoader
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
private class Request(
|
||||
val onFileChosen: (Uri) -> Unit
|
||||
)
|
||||
|
||||
class AndroidSaverLoader(private val activity: Activity) : PlatformSaverLoader {
|
||||
|
||||
private val contentResolver = activity.contentResolver
|
||||
private val requests = HashMap<Int, Request>()
|
||||
private var requestCode = 100
|
||||
|
||||
override fun saveGame(
|
||||
data: String,
|
||||
suggestedLocation: String,
|
||||
onSaved: (location: String) -> Unit,
|
||||
onError: (ex: Exception) -> Unit
|
||||
) {
|
||||
|
||||
// When we loaded, we returned a "content://" URI as file location.
|
||||
val suggestedUri = Uri.parse(suggestedLocation)
|
||||
val fileName = getFilename(suggestedUri, suggestedLocation)
|
||||
|
||||
val onFileChosen = {uri: Uri ->
|
||||
var stream: OutputStream? = null
|
||||
try {
|
||||
stream = contentResolver.openOutputStream(uri, "rwt")
|
||||
stream!!.writer().use { it.write(data) }
|
||||
onSaved(uri.toString())
|
||||
} catch (ex: Exception) {
|
||||
onError(ex)
|
||||
} finally {
|
||||
stream?.close()
|
||||
}
|
||||
}
|
||||
|
||||
requests[requestCode] = Request(onFileChosen)
|
||||
openSaveFileChooser(fileName, suggestedUri, requestCode)
|
||||
requestCode += 1
|
||||
}
|
||||
|
||||
override fun loadGame(
|
||||
onLoaded: (data: String, location: String) -> Unit,
|
||||
onError: (ex: Exception) -> Unit) {
|
||||
|
||||
val onFileChosen = {uri: Uri ->
|
||||
var stream: InputStream? = null
|
||||
try {
|
||||
stream = contentResolver.openInputStream(uri)
|
||||
val text = stream!!.reader().use { it.readText() }
|
||||
onLoaded(text, uri.toString())
|
||||
} catch (ex: Exception) {
|
||||
onError(ex)
|
||||
} finally {
|
||||
stream?.close()
|
||||
}
|
||||
}
|
||||
|
||||
requests[requestCode] = Request(onFileChosen)
|
||||
openLoadFileChooser(requestCode)
|
||||
requestCode += 1
|
||||
}
|
||||
|
||||
fun onActivityResult(requestCode: Int, data: Intent?) {
|
||||
val uri: Uri = data?.data ?: return
|
||||
val request = requests.remove(requestCode) ?: return
|
||||
request.onFileChosen(uri)
|
||||
}
|
||||
|
||||
private fun openSaveFileChooser(fileName: String, uri: Uri, requestCode: Int) {
|
||||
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||
intent.type = "application/json"
|
||||
intent.putExtra(Intent.EXTRA_TITLE, fileName)
|
||||
if (uri.scheme == "content")
|
||||
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri)
|
||||
activity.startActivityForResult(intent, requestCode)
|
||||
}
|
||||
|
||||
private fun openLoadFileChooser(requestCode: Int) {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
intent.type = "*/*"
|
||||
/* It is theoretically possible to use an initial URI here,
|
||||
however, the only Android URIs we have are obtained from here, so, no dice */
|
||||
activity.startActivityForResult(intent, requestCode)
|
||||
}
|
||||
|
||||
private fun getFilename(uri: Uri, suggestedLocation: String): String {
|
||||
|
||||
if (uri.scheme != "content")
|
||||
return suggestedLocation
|
||||
|
||||
try {
|
||||
contentResolver.query(uri, null, null, null, null).use {
|
||||
if (it?.moveToFirst() == true)
|
||||
return it.getString(it.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
|
||||
else
|
||||
return ""
|
||||
}
|
||||
} catch(ex: Exception) {
|
||||
return suggestedLocation.split("2F").last() // I have no idea why but the content path ends with this before the filename
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package com.unciv.app
|
||||
|
||||
import android.os.Build
|
||||
import com.unciv.ui.crashhandling.CrashReportSysInfo
|
||||
|
||||
object CrashReportSysInfoAndroid: CrashReportSysInfo {
|
||||
|
||||
override fun getInfo(): String =
|
||||
"""
|
||||
Device Model: ${Build.MODEL}
|
||||
API Level: ${Build.VERSION.SDK_INT}
|
||||
""".trimIndent()
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
package com.unciv.app
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.DocumentsContract
|
||||
import android.provider.OpenableColumns
|
||||
import androidx.annotation.GuardedBy
|
||||
import com.unciv.logic.files.CustomFileLocationHelper
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
class CustomFileLocationHelperAndroid(private val activity: Activity) : CustomFileLocationHelper() {
|
||||
|
||||
@GuardedBy("this")
|
||||
private val callbacks = mutableListOf<ActivityCallback>()
|
||||
@GuardedBy("this")
|
||||
private var curActivityRequestCode = 100
|
||||
|
||||
override fun createOutputStream(suggestedLocation: String, callback: (String?, OutputStream?, Exception?) -> Unit) {
|
||||
val requestCode = createActivityCallback(callback) { activity.contentResolver.openOutputStream(it, "rwt") }
|
||||
|
||||
// When we loaded, we returned a "content://" URI as file location.
|
||||
val uri = Uri.parse(suggestedLocation)
|
||||
val fileName = if (uri.scheme == "content") {
|
||||
try {
|
||||
val cursor = activity.contentResolver.query(uri, null, null, null, null)
|
||||
cursor.use {
|
||||
// we should have a direct URI to a file, so first is enough
|
||||
if (it?.moveToFirst() == true) {
|
||||
it.getString(it.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
|
||||
} else ""
|
||||
}
|
||||
}
|
||||
catch(ex:Exception) {
|
||||
suggestedLocation.split("2F").last() // I have no idea why but the content path ends with this before the filename
|
||||
}
|
||||
} else {
|
||||
// if we didn't load, this is some file name entered by the user
|
||||
suggestedLocation
|
||||
}
|
||||
|
||||
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
type = "application/json"
|
||||
putExtra(Intent.EXTRA_TITLE, fileName)
|
||||
if (uri.scheme == "content") {
|
||||
putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri)
|
||||
}
|
||||
activity.startActivityForResult(this, requestCode)
|
||||
}
|
||||
}
|
||||
|
||||
override fun createInputStream(callback: (String?, InputStream?, Exception?) -> Unit) {
|
||||
val callbackIndex = createActivityCallback(callback, activity.contentResolver::openInputStream)
|
||||
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
type = "*/*"
|
||||
// It is theoretically possible to use an initial URI here, however, the only Android URIs we have are obtained from here, so, no dice
|
||||
activity.startActivityForResult(this, callbackIndex)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> createActivityCallback(callback: (String?, T?, Exception?) -> Unit,
|
||||
createValue: (Uri) -> T): Int {
|
||||
synchronized(this) {
|
||||
val requestCode = curActivityRequestCode++
|
||||
val activityCallback = ActivityCallback(requestCode) { uri ->
|
||||
if (uri == null) {
|
||||
callback(null, null, null)
|
||||
return@ActivityCallback
|
||||
}
|
||||
|
||||
try {
|
||||
val outputStream = createValue(uri)
|
||||
callback(uri.toString(), outputStream, null)
|
||||
} catch (ex: Exception) {
|
||||
callback(null, null, ex)
|
||||
}
|
||||
}
|
||||
callbacks.add(activityCallback)
|
||||
return requestCode
|
||||
}
|
||||
}
|
||||
|
||||
fun onActivityResult(requestCode: Int, data: Intent?) {
|
||||
val activityCallback = synchronized(this) {
|
||||
val index = callbacks.indexOfFirst { it.requestCode == requestCode }
|
||||
if (index == -1) return
|
||||
callbacks.removeAt(index)
|
||||
}
|
||||
activityCallback.callback(data?.data)
|
||||
}
|
||||
}
|
||||
|
||||
private class ActivityCallback(
|
||||
val requestCode: Int,
|
||||
val callback: (Uri?) -> Unit
|
||||
)
|
@ -269,7 +269,7 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
||||
val gdxFiles = DefaultAndroidFiles(applicationContext.assets, ContextWrapper(applicationContext), true)
|
||||
// GDX's AndroidFileHandle uses Gdx.files internally, so we need to set that to our new instance
|
||||
Gdx.files = gdxFiles
|
||||
files = UncivFiles(gdxFiles, null, true)
|
||||
files = UncivFiles(gdxFiles)
|
||||
}
|
||||
|
||||
override fun doWork(): Result = runBlocking {
|
||||
|
@ -1,68 +0,0 @@
|
||||
package com.unciv.app
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.os.Build
|
||||
import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
|
||||
import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.TextField
|
||||
import com.unciv.ui.components.GeneralPlatformSpecificHelpers
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
/** See also interface [GeneralPlatformSpecificHelpers].
|
||||
*
|
||||
* The Android implementation (currently the only one) effectively ends up doing
|
||||
* [Activity.setRequestedOrientation]
|
||||
*/
|
||||
class PlatformSpecificHelpersAndroid(private val activity: Activity) : GeneralPlatformSpecificHelpers {
|
||||
|
||||
/*
|
||||
Sources for Info about current orientation in case need:
|
||||
val windowManager = (activity.getSystemService(Context.WINDOW_SERVICE) as WindowManager)
|
||||
val displayRotation = windowManager.defaultDisplay.rotation
|
||||
val currentOrientation = activity.resources.configuration.orientation
|
||||
const val ORIENTATION_UNDEFINED = 0
|
||||
const val ORIENTATION_PORTRAIT = 1
|
||||
const val ORIENTATION_LANDSCAPE = 2
|
||||
*/
|
||||
|
||||
override fun allowPortrait(allow: Boolean) {
|
||||
val orientation = when {
|
||||
allow -> ActivityInfo.SCREEN_ORIENTATION_USER
|
||||
else -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
|
||||
}
|
||||
// Comparison ensures ActivityTaskManager.getService().setRequestedOrientation isn't called unless necessary
|
||||
if (activity.requestedOrientation != orientation) activity.requestedOrientation = orientation
|
||||
}
|
||||
|
||||
override fun hasDisplayCutout() = when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R ->
|
||||
activity.display?.cutout != null
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ->
|
||||
@Suppress("DEPRECATION")
|
||||
activity.windowManager.defaultDisplay.cutout != null
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ->
|
||||
activity.window.decorView.rootWindowInsets.displayCutout != null
|
||||
else -> false
|
||||
}
|
||||
|
||||
override fun toggleDisplayCutout(androidCutout: Boolean) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) return
|
||||
val layoutParams = activity.window.attributes
|
||||
if (androidCutout) {
|
||||
layoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
||||
} else {
|
||||
layoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On Android, local is some android-internal data directory which may or may not be accessible by the user.
|
||||
* External is probably on an SD-card or similar which is always accessible by the user.
|
||||
*/
|
||||
override fun shouldPreferExternalStorage(): Boolean = true
|
||||
|
||||
override fun addImprovements(textField: TextField): TextField {
|
||||
return TextfieldImprovements.add(textField)
|
||||
}
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
package com.unciv.app
|
||||
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor
|
||||
import com.badlogic.gdx.scenes.scene2d.InputEvent
|
||||
import com.badlogic.gdx.scenes.scene2d.InputListener
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.TextField
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.FocusListener
|
||||
import com.unciv.logic.event.EventBus
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.screens.basescreen.UncivStage
|
||||
import com.unciv.ui.popups.Popup
|
||||
import com.unciv.ui.components.KeyCharAndCode
|
||||
import com.unciv.ui.components.extensions.getAscendant
|
||||
import com.unciv.ui.components.scrollAscendantToTextField
|
||||
import com.unciv.utils.concurrency.Concurrency
|
||||
import com.unciv.utils.concurrency.withGLContext
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
object TextfieldImprovements {
|
||||
private val hideKeyboard = { Gdx.input.setOnscreenKeyboardVisible(false) }
|
||||
fun add(textField: TextField): TextField {
|
||||
textField.addListener(object : InputListener() {
|
||||
private val events = EventBus.EventReceiver()
|
||||
init {
|
||||
events.receive(UncivStage.VisibleAreaChanged::class) {
|
||||
if (textField.stage == null || !textField.hasKeyboardFocus()) return@receive
|
||||
Concurrency.run {
|
||||
// If anything resizes, it also does so with this event. So we need to wait for that to finish to update the scroll position.
|
||||
delay(100)
|
||||
withGLContext {
|
||||
if (textField.stage == null) return@withGLContext
|
||||
|
||||
if (textField.scrollAscendantToTextField()) {
|
||||
val scrollPane = textField.getAscendant { it is ScrollPane } as ScrollPane?
|
||||
// when screen dimensions change, we don't want an animation for scrolling, just show, just show the textfield immediately
|
||||
scrollPane?.updateVisualScroll()
|
||||
} else {
|
||||
// We can't scroll the text field into view, so we need to show a popup
|
||||
TextfieldPopup(textField).open()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int): Boolean {
|
||||
addPopupCloseListener(textField)
|
||||
return false
|
||||
}
|
||||
})
|
||||
textField.addListener(object : FocusListener() {
|
||||
override fun keyboardFocusChanged(event: FocusEvent?, actor: Actor?, focused: Boolean) {
|
||||
if (focused) {
|
||||
addPopupCloseListener(textField)
|
||||
Gdx.input.setOnscreenKeyboardVisible(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return textField
|
||||
}
|
||||
|
||||
private fun addPopupCloseListener(textField: TextField) {
|
||||
val popup = textField.getAscendant { it is Popup } as Popup?
|
||||
if (popup != null && !popup.closeListeners.contains(hideKeyboard)) {
|
||||
popup.closeListeners.add(hideKeyboard)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TextfieldPopup(
|
||||
textField: TextField
|
||||
) : Popup(textField.stage) {
|
||||
val popupTextfield = clone(textField)
|
||||
init {
|
||||
addGoodSizedLabel(popupTextfield.messageText)
|
||||
.colspan(2)
|
||||
.row()
|
||||
|
||||
add(popupTextfield)
|
||||
.width(stageToShowOn.width / 2)
|
||||
.colspan(2)
|
||||
.row()
|
||||
|
||||
addCloseButton("Cancel")
|
||||
.left()
|
||||
addOKButton { textField.text = popupTextfield.text }
|
||||
.right()
|
||||
.row()
|
||||
|
||||
showListeners.add {
|
||||
stageToShowOn.keyboardFocus = popupTextfield
|
||||
}
|
||||
closeListeners.add {
|
||||
stageToShowOn.keyboardFocus = null
|
||||
Gdx.input.setOnscreenKeyboardVisible(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun clone(textField: TextField): TextField {
|
||||
@Suppress("UNCIV_RAW_TEXTFIELD") // we are copying the existing text field
|
||||
val copy = TextField(textField.text, textField.style)
|
||||
copy.textFieldFilter = textField.textFieldFilter
|
||||
copy.messageText = textField.messageText
|
||||
copy.setSelection(textField.selectionStart, textField.selection.length)
|
||||
copy.cursorPosition = textField.cursorPosition
|
||||
copy.alignment = textField.alignment
|
||||
copy.isPasswordMode = textField.isPasswordMode
|
||||
copy.onscreenKeyboard = textField.onscreenKeyboard
|
||||
return copy
|
||||
}
|
||||
}
|
@ -25,7 +25,7 @@ import com.unciv.ui.audio.MusicController
|
||||
import com.unciv.ui.audio.MusicMood
|
||||
import com.unciv.ui.audio.MusicTrackChooserFlags
|
||||
import com.unciv.ui.audio.SoundPlayer
|
||||
import com.unciv.ui.components.FontImplementation
|
||||
import com.unciv.ui.components.Fonts
|
||||
import com.unciv.ui.components.extensions.center
|
||||
import com.unciv.ui.crashhandling.CrashScreen
|
||||
import com.unciv.ui.crashhandling.wrapCrashHandlingUnit
|
||||
@ -41,7 +41,9 @@ import com.unciv.ui.screens.worldscreen.PlayerReadyScreen
|
||||
import com.unciv.ui.screens.worldscreen.WorldMapHolder
|
||||
import com.unciv.ui.screens.worldscreen.WorldScreen
|
||||
import com.unciv.ui.screens.worldscreen.unit.UnitTable
|
||||
import com.unciv.utils.DebugUtils
|
||||
import com.unciv.utils.Log
|
||||
import com.unciv.utils.PlatformSpecific
|
||||
import com.unciv.utils.concurrency.Concurrency
|
||||
import com.unciv.utils.concurrency.launchOnGLThread
|
||||
import com.unciv.utils.concurrency.withGLContext
|
||||
@ -51,13 +53,10 @@ import kotlinx.coroutines.CancellationException
|
||||
import java.io.PrintWriter
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayDeque
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
object GUI {
|
||||
|
||||
fun isDebugMapVisible(): Boolean {
|
||||
return UncivGame.Current.viewEntireMapForDebug
|
||||
}
|
||||
|
||||
fun setUpdateWorldOnNextRender() {
|
||||
UncivGame.Current.worldScreen?.shouldUpdate = true
|
||||
}
|
||||
@ -74,10 +73,6 @@ object GUI {
|
||||
return UncivGame.Current.settings
|
||||
}
|
||||
|
||||
fun getFontImpl(): FontImplementation {
|
||||
return UncivGame.Current.fontImplementation!!
|
||||
}
|
||||
|
||||
fun isWorldLoaded(): Boolean {
|
||||
return UncivGame.Current.worldScreen != null
|
||||
}
|
||||
@ -116,19 +111,11 @@ object GUI {
|
||||
|
||||
}
|
||||
|
||||
class UncivGame(parameters: UncivGameParameters) : Game() {
|
||||
constructor() : this(UncivGameParameters())
|
||||
|
||||
val crashReportSysInfo = parameters.crashReportSysInfo
|
||||
val cancelDiscordEvent = parameters.cancelDiscordEvent
|
||||
var fontImplementation = parameters.fontImplementation
|
||||
val consoleMode = parameters.consoleMode
|
||||
private val customSaveLocationHelper = parameters.customFileLocationHelper
|
||||
val platformSpecificHelper = parameters.platformSpecificHelper
|
||||
private val audioExceptionHelper = parameters.audioExceptionHelper
|
||||
open class UncivGame(val isConsoleMode: Boolean = false) : Game(), PlatformSpecific {
|
||||
|
||||
var deepLinkedMultiplayerGame: String? = null
|
||||
var gameInfo: GameInfo? = null
|
||||
|
||||
lateinit var settings: GameSettings
|
||||
lateinit var musicController: MusicController
|
||||
lateinit var onlineMultiplayer: OnlineMultiplayer
|
||||
@ -136,20 +123,6 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
|
||||
|
||||
var isTutorialTaskCollapsed = false
|
||||
|
||||
/**
|
||||
* 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 */
|
||||
var 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.
|
||||
*/
|
||||
var simulateUntilTurnForDebug: Int = 0
|
||||
|
||||
var worldScreen: WorldScreen? = null
|
||||
private set
|
||||
|
||||
@ -167,10 +140,10 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
|
||||
isInitialized = false // this could be on reload, therefore we need to keep setting this to false
|
||||
Gdx.input.setCatchKey(Input.Keys.BACK, true)
|
||||
if (Gdx.app.type != Application.ApplicationType.Desktop) {
|
||||
viewEntireMapForDebug = false
|
||||
DebugUtils.VISIBLE_MAP = false
|
||||
}
|
||||
Current = this
|
||||
files = UncivFiles(Gdx.files, customSaveLocationHelper, platformSpecificHelper?.shouldPreferExternalStorage() == true)
|
||||
files = UncivFiles(Gdx.files)
|
||||
|
||||
// If this takes too long players, especially with older phones, get ANR problems.
|
||||
// Whatever needs graphics needs to be done on the main thread,
|
||||
@ -190,10 +163,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
|
||||
GameSounds.init()
|
||||
|
||||
musicController = MusicController() // early, but at this point does only copy volume from settings
|
||||
audioExceptionHelper?.installHooks(
|
||||
musicController.getAudioLoopCallback(),
|
||||
musicController.getAudioExceptionHandler()
|
||||
)
|
||||
installAudioHooks()
|
||||
|
||||
onlineMultiplayer = OnlineMultiplayer()
|
||||
|
||||
@ -230,7 +200,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
|
||||
|
||||
// Loading available fonts can take a long time on Android phones.
|
||||
// Therefore we initialize the lazy parameters in the font implementation, while we're in another thread, to avoid ANRs on main thread
|
||||
fontImplementation?.setFontFamily(settings.fontFamilyData, settings.getFontSize())
|
||||
Fonts.fontImplementation.setFontFamily(settings.fontFamilyData, settings.getFontSize())
|
||||
|
||||
// This stuff needs to run on the main thread because it needs the GL context
|
||||
launchOnGLThread {
|
||||
@ -474,8 +444,6 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
|
||||
|
||||
override fun dispose() {
|
||||
Gdx.input.inputProcessor = null // don't allow ANRs when shutting down, that's silly
|
||||
|
||||
cancelDiscordEvent?.invoke()
|
||||
SoundPlayer.clearCache()
|
||||
if (::musicController.isInitialized) musicController.gracefulShutdown() // Do allow fade-out
|
||||
|
||||
@ -498,7 +466,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
|
||||
// On desktop this should only be this one and "DestroyJavaVM"
|
||||
logRunningThreads()
|
||||
|
||||
System.exit(0)
|
||||
exitProcess(0)
|
||||
}
|
||||
|
||||
private fun logRunningThreads() {
|
||||
@ -523,7 +491,6 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
|
||||
} catch (ex: Exception) {
|
||||
// ignore
|
||||
}
|
||||
if (platformSpecificHelper?.handleUncaughtThrowable(ex) == true) return
|
||||
Gdx.app.postRunnable {
|
||||
setAsRootScreen(CrashScreen(ex))
|
||||
}
|
||||
|
@ -1,16 +0,0 @@
|
||||
package com.unciv
|
||||
|
||||
import com.unciv.logic.files.CustomFileLocationHelper
|
||||
import com.unciv.ui.crashhandling.CrashReportSysInfo
|
||||
import com.unciv.ui.components.AudioExceptionHelper
|
||||
import com.unciv.ui.components.GeneralPlatformSpecificHelpers
|
||||
import com.unciv.ui.components.FontImplementation
|
||||
|
||||
class UncivGameParameters(val crashReportSysInfo: CrashReportSysInfo? = null,
|
||||
val cancelDiscordEvent: (() -> Unit)? = null,
|
||||
val fontImplementation: FontImplementation? = null,
|
||||
val consoleMode: Boolean = false,
|
||||
val customFileLocationHelper: CustomFileLocationHelper? = null,
|
||||
val platformSpecificHelper: GeneralPlatformSpecificHelpers? = null,
|
||||
val audioExceptionHelper: AudioExceptionHelper? = null
|
||||
)
|
@ -32,6 +32,7 @@ import com.unciv.models.ruleset.nation.Difficulty
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.ui.audio.MusicMood
|
||||
import com.unciv.ui.audio.MusicTrackChooserFlags
|
||||
import com.unciv.utils.DebugUtils
|
||||
import com.unciv.utils.debug
|
||||
import java.util.*
|
||||
|
||||
@ -253,7 +254,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
|
||||
//region State changing functions
|
||||
|
||||
// Do we automatically simulate until N turn?
|
||||
fun isSimulation(): Boolean = turns < UncivGame.Current.simulateUntilTurnForDebug
|
||||
fun isSimulation(): Boolean = turns < DebugUtils.SIMULATE_UNTIL_TURN
|
||||
|| turns < simulateMaxTurns && simulateUntilWin
|
||||
|
||||
fun nextTurn() {
|
||||
@ -266,7 +267,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
|
||||
playerIndex = (playerIndex + 1) % civilizations.size
|
||||
if (playerIndex == 0) {
|
||||
turns++
|
||||
if (UncivGame.Current.simulateUntilTurnForDebug != 0)
|
||||
if (DebugUtils.SIMULATE_UNTIL_TURN != 0)
|
||||
debug("Starting simulation of turn %s", turns)
|
||||
}
|
||||
player = civilizations[playerIndex]
|
||||
@ -311,8 +312,8 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
|
||||
setNextPlayer()
|
||||
}
|
||||
|
||||
if (turns == UncivGame.Current.simulateUntilTurnForDebug)
|
||||
UncivGame.Current.simulateUntilTurnForDebug = 0
|
||||
if (turns == DebugUtils.SIMULATE_UNTIL_TURN)
|
||||
DebugUtils.SIMULATE_UNTIL_TURN = 0
|
||||
|
||||
// We found human player, so we are making him current
|
||||
currentTurnStartTime = System.currentTimeMillis()
|
||||
|
@ -17,6 +17,7 @@ import com.unciv.models.stats.Stat
|
||||
import com.unciv.models.stats.StatMap
|
||||
import com.unciv.models.stats.Stats
|
||||
import com.unciv.ui.components.extensions.toPercent
|
||||
import com.unciv.utils.DebugUtils
|
||||
import kotlin.math.min
|
||||
|
||||
|
||||
@ -464,7 +465,7 @@ class CityStats(val city: City) {
|
||||
|
||||
newStatsBonusTree.add(getStatsPercentBonusesFromUniquesBySource(currentConstruction))
|
||||
|
||||
if (UncivGame.Current.superchargedForDebug) {
|
||||
if (DebugUtils.SUPERCHARGED) {
|
||||
val stats = Stats()
|
||||
for (stat in Stat.values()) stats[stat] = 10000f
|
||||
newStatsBonusTree.addStats(stats, "Supercharged")
|
||||
|
@ -2,7 +2,6 @@ package com.unciv.logic.civilization.transients
|
||||
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.unciv.Constants
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.city.City
|
||||
import com.unciv.logic.civilization.Civilization
|
||||
import com.unciv.logic.civilization.NotificationCategory
|
||||
@ -19,6 +18,7 @@ import com.unciv.models.ruleset.unique.UniqueTarget
|
||||
import com.unciv.models.ruleset.unique.UniqueTriggerActivation
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.models.ruleset.unit.BaseUnit
|
||||
import com.unciv.utils.DebugUtils
|
||||
|
||||
/** CivInfo class was getting too crowded */
|
||||
class CivInfoTransientCache(val civInfo: Civilization) {
|
||||
@ -142,7 +142,7 @@ class CivInfoTransientCache(val civInfo: Civilization) {
|
||||
val newViewableTiles = HashSet<Tile>()
|
||||
|
||||
// while spectating all map is visible
|
||||
if (civInfo.isSpectator() || UncivGame.Current.viewEntireMapForDebug) {
|
||||
if (civInfo.isSpectator() || DebugUtils.VISIBLE_MAP) {
|
||||
val allTiles = civInfo.gameInfo.tileMap.values.toSet()
|
||||
civInfo.viewableTiles = allTiles
|
||||
civInfo.viewableInvisibleUnitsTiles = allTiles
|
||||
|
@ -1,95 +0,0 @@
|
||||
package com.unciv.logic.files
|
||||
|
||||
import com.unciv.logic.files.UncivFiles.CustomLoadResult
|
||||
import com.unciv.logic.files.UncivFiles.CustomSaveResult
|
||||
import com.unciv.utils.concurrency.Concurrency
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
/**
|
||||
* Contract for platform-specific helper classes to handle saving and loading games to and from
|
||||
* arbitrary external locations.
|
||||
*
|
||||
* Implementation note: If a game is loaded with [loadGame] and the same game is saved with [saveGame],
|
||||
* the suggestedLocation in [saveGame] will be the location returned by [loadGame].
|
||||
*/
|
||||
abstract class CustomFileLocationHelper {
|
||||
/**
|
||||
* Saves a game asynchronously to a location selected by the user.
|
||||
*
|
||||
* Prefills their UI with a [suggestedLocation].
|
||||
*
|
||||
* Calls the [saveCompleteCallback] on the main thread with the save location on success or the [Exception] on error or null in both on cancel.
|
||||
*/
|
||||
fun saveGame(
|
||||
gameData: String,
|
||||
suggestedLocation: String,
|
||||
saveCompleteCallback: (CustomSaveResult) -> Unit = {}
|
||||
) {
|
||||
createOutputStream(suggestedLocation) { location, outputStream, exception ->
|
||||
if (outputStream == null) {
|
||||
callSaveCallback(saveCompleteCallback, exception = exception)
|
||||
return@createOutputStream
|
||||
}
|
||||
|
||||
try {
|
||||
outputStream.writer().use { it.write(gameData) }
|
||||
callSaveCallback(saveCompleteCallback, location)
|
||||
} catch (ex: Exception) {
|
||||
callSaveCallback(saveCompleteCallback, exception = ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a game asynchronously from a location selected by the user.
|
||||
*
|
||||
* Calls the [loadCompleteCallback] on the main thread.
|
||||
*/
|
||||
fun loadGame(loadCompleteCallback: (CustomLoadResult<String>) -> Unit) {
|
||||
createInputStream { location, inputStream, exception ->
|
||||
if (inputStream == null) {
|
||||
callLoadCallback(loadCompleteCallback, exception = exception)
|
||||
return@createInputStream
|
||||
}
|
||||
|
||||
try {
|
||||
val gameData = inputStream.reader().use { it.readText() }
|
||||
callLoadCallback(loadCompleteCallback, location, gameData)
|
||||
} catch (ex: Exception) {
|
||||
callLoadCallback(loadCompleteCallback, exception = ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [callback] should be called with the actual selected location and an OutputStream to the location, or an exception if something failed.
|
||||
*/
|
||||
protected abstract fun createOutputStream(suggestedLocation: String, callback: (String?, OutputStream?, Exception?) -> Unit)
|
||||
|
||||
/**
|
||||
* [callback] should be called with the actual selected location and an InputStream to read the location, or an exception if something failed.
|
||||
*/
|
||||
protected abstract fun createInputStream(callback: (String?, InputStream?, Exception?) -> Unit)
|
||||
}
|
||||
|
||||
private fun callLoadCallback(loadCompleteCallback: (CustomLoadResult<String>) -> Unit,
|
||||
location: String? = null,
|
||||
gameData: String? = null,
|
||||
exception: Exception? = null) {
|
||||
val result = if (location != null && gameData != null && exception == null) {
|
||||
CustomLoadResult(location to gameData)
|
||||
} else {
|
||||
CustomLoadResult(null, exception)
|
||||
}
|
||||
Concurrency.runOnGLThread {
|
||||
loadCompleteCallback(result)
|
||||
}
|
||||
}
|
||||
private fun callSaveCallback(saveCompleteCallback: (CustomSaveResult) -> Unit,
|
||||
location: String? = null,
|
||||
exception: Exception? = null) {
|
||||
Concurrency.runOnGLThread {
|
||||
saveCompleteCallback(CustomSaveResult(location, exception))
|
||||
}
|
||||
}
|
23
core/src/com/unciv/logic/files/PlatformSaverLoader.kt
Normal file
23
core/src/com/unciv/logic/files/PlatformSaverLoader.kt
Normal file
@ -0,0 +1,23 @@
|
||||
package com.unciv.logic.files
|
||||
|
||||
/**
|
||||
* Contract for platform-specific helper classes to handle saving and loading games to and from
|
||||
* arbitrary external locations.
|
||||
*
|
||||
* Implementation note: If a game is loaded with [loadGame] and the same game is saved with [saveGame],
|
||||
* the suggestedLocation in [saveGame] will be the location returned by [loadGame].
|
||||
*/
|
||||
interface PlatformSaverLoader {
|
||||
|
||||
fun saveGame(
|
||||
data: String, // Data to save
|
||||
suggestedLocation: String, // Proposed location
|
||||
onSaved: (location: String) -> Unit = {}, // On-save-complete callback
|
||||
onError: (ex: Exception) -> Unit = {} // On-save-error callback
|
||||
)
|
||||
|
||||
fun loadGame(
|
||||
onLoaded: (data: String, location: String) -> Unit, // On-load-complete callback
|
||||
onError: (Exception) -> Unit = {} // On-load-error callback
|
||||
)
|
||||
}
|
@ -30,13 +30,11 @@ class UncivFiles(
|
||||
* This is necessary because the Android turn check background worker does not hold any reference to the actual [com.badlogic.gdx.Application],
|
||||
* which is normally responsible for keeping the [Gdx] static variables from being garbage collected.
|
||||
*/
|
||||
private val files: Files,
|
||||
private val customFileLocationHelper: CustomFileLocationHelper? = null,
|
||||
private val preferExternalStorage: Boolean = false
|
||||
private val files: Files
|
||||
) {
|
||||
init {
|
||||
debug("Creating UncivFiles, localStoragePath: %s, externalStoragePath: %s, preferExternalStorage: %s",
|
||||
files.localStoragePath, files.externalStoragePath, preferExternalStorage)
|
||||
debug("Creating UncivFiles, localStoragePath: %s, externalStoragePath: %s",
|
||||
files.localStoragePath, files.externalStoragePath)
|
||||
}
|
||||
//region Data
|
||||
|
||||
@ -112,8 +110,6 @@ class UncivFiles(
|
||||
return localFiles + externalFiles
|
||||
}
|
||||
|
||||
fun canLoadFromCustomSaveLocation() = customFileLocationHelper != null
|
||||
|
||||
/**
|
||||
* @return `true` if successful.
|
||||
* @throws SecurityException when delete access was denied
|
||||
@ -141,15 +137,6 @@ class UncivFiles(
|
||||
return file.delete()
|
||||
}
|
||||
|
||||
interface ChooseLocationResult {
|
||||
val location: String?
|
||||
val exception: Exception?
|
||||
|
||||
fun isCanceled(): Boolean = location == null && exception == null
|
||||
fun isError(): Boolean = exception != null
|
||||
fun isSuccessful(): Boolean = location != null
|
||||
}
|
||||
|
||||
//endregion
|
||||
//region Saving
|
||||
|
||||
@ -165,7 +152,8 @@ class UncivFiles(
|
||||
fun saveGame(game: GameInfo, file: FileHandle, saveCompletionCallback: (Exception?) -> Unit = { if (it != null) throw it }) {
|
||||
try {
|
||||
debug("Saving GameInfo %s to %s", game.gameId, file.path())
|
||||
file.writeString(gameInfoToString(game), false)
|
||||
val string = gameInfoToString(game)
|
||||
file.writeString(string, false)
|
||||
saveCompletionCallback(null)
|
||||
} catch (ex: Exception) {
|
||||
saveCompletionCallback(ex)
|
||||
@ -194,31 +182,35 @@ class UncivFiles(
|
||||
}
|
||||
}
|
||||
|
||||
class CustomSaveResult(
|
||||
override val location: String? = null,
|
||||
override val exception: Exception? = null
|
||||
) : ChooseLocationResult
|
||||
|
||||
/**
|
||||
* [gameName] is a suggested name for the file. If the file has already been saved to or loaded from a custom location,
|
||||
* this previous custom location will be used.
|
||||
*
|
||||
* Calls the [saveCompleteCallback] on the main thread with the save location on success, an [Exception] on error, or both null on cancel.
|
||||
* Calls the [onSaved] on the main thread on success.
|
||||
* Calls the [onError] on the main thread with an [Exception] on error.
|
||||
*/
|
||||
fun saveGameToCustomLocation(game: GameInfo, gameName: String, saveCompletionCallback: (CustomSaveResult) -> Unit) {
|
||||
fun saveGameToCustomLocation(
|
||||
game: GameInfo,
|
||||
gameName: String,
|
||||
onSaved: () -> Unit,
|
||||
onError: (Exception) -> Unit) {
|
||||
val saveLocation = game.customSaveLocation ?: Gdx.files.local(gameName).path()
|
||||
val gameData = try {
|
||||
gameInfoToString(game)
|
||||
|
||||
try {
|
||||
val data = gameInfoToString(game)
|
||||
debug("Saving GameInfo %s to custom location %s", game.gameId, saveLocation)
|
||||
saverLoader.saveGame(data, saveLocation,
|
||||
{ location ->
|
||||
game.customSaveLocation = location
|
||||
Concurrency.runOnGLThread { onSaved() }
|
||||
},
|
||||
{
|
||||
Concurrency.runOnGLThread { onError(it) }
|
||||
}
|
||||
)
|
||||
|
||||
} catch (ex: Exception) {
|
||||
Concurrency.runOnGLThread { saveCompletionCallback(CustomSaveResult(exception = ex)) }
|
||||
return
|
||||
}
|
||||
debug("Saving GameInfo %s to custom location %s", game.gameId, saveLocation)
|
||||
customFileLocationHelper!!.saveGame(gameData, saveLocation) {
|
||||
if (it.isSuccessful()) {
|
||||
game.customSaveLocation = it.location
|
||||
}
|
||||
saveCompletionCallback(it)
|
||||
Concurrency.runOnGLThread { onError(ex) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,11 +232,7 @@ class UncivFiles(
|
||||
loadGamePreviewFromFile(getMultiplayerSave(gameName))
|
||||
|
||||
fun loadGamePreviewFromFile(gameFile: FileHandle): GameInfoPreview {
|
||||
val preview = json().fromJson(GameInfoPreview::class.java, gameFile)
|
||||
if (preview == null) {
|
||||
throw emptyFile(gameFile)
|
||||
}
|
||||
return preview
|
||||
return json().fromJson(GameInfoPreview::class.java, gameFile) ?: throw emptyFile(gameFile)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -256,36 +244,29 @@ class UncivFiles(
|
||||
return SerializationException("The file for the game ${gameFile.name()} is empty")
|
||||
}
|
||||
|
||||
class CustomLoadResult<T>(
|
||||
private val locationAndGameData: Pair<String, T>? = null,
|
||||
override val exception: Exception? = null
|
||||
) : ChooseLocationResult {
|
||||
override val location: String? get() = locationAndGameData?.first
|
||||
val gameData: T? get() = locationAndGameData?.second
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the [loadCompleteCallback] on the main thread with the [GameInfo] on success or the [Exception] on error or null in both on cancel.
|
||||
*
|
||||
* The exception may be [IncompatibleGameInfoVersionException] if the [gameData] was created by a version of this game that is incompatible with the current one.
|
||||
* Calls the [onLoaded] on the main thread with the [GameInfo] on success.
|
||||
* Calls the [onError] on the main thread with the [Exception] on error
|
||||
* The exception may be [IncompatibleGameInfoVersionException] if the [GameInfo] was created by a version of this game that is incompatible with the current one.
|
||||
*/
|
||||
fun loadGameFromCustomLocation(loadCompletionCallback: (CustomLoadResult<GameInfo>) -> Unit) {
|
||||
customFileLocationHelper!!.loadGame { result ->
|
||||
val location = result.location
|
||||
val gameData = result.gameData
|
||||
if (location == null || gameData == null) {
|
||||
loadCompletionCallback(CustomLoadResult(exception = result.exception))
|
||||
return@loadGame
|
||||
fun loadGameFromCustomLocation(
|
||||
onLoaded: (GameInfo) -> Unit,
|
||||
onError: (Exception) -> Unit
|
||||
) {
|
||||
saverLoader.loadGame(
|
||||
{ data, location ->
|
||||
try {
|
||||
val game = gameInfoFromString(data)
|
||||
game.customSaveLocation = location
|
||||
Concurrency.runOnGLThread { onLoaded(game) }
|
||||
} catch (ex: Exception) {
|
||||
Concurrency.runOnGLThread { onError(ex) }
|
||||
}
|
||||
},
|
||||
{
|
||||
Concurrency.runOnGLThread { onError(it) }
|
||||
}
|
||||
|
||||
try {
|
||||
val gameInfo = gameInfoFromString(gameData)
|
||||
gameInfo.customSaveLocation = location
|
||||
loadCompletionCallback(CustomLoadResult(location to gameInfo))
|
||||
} catch (ex: Exception) {
|
||||
loadCompletionCallback(CustomLoadResult(exception = ex))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -293,7 +274,7 @@ class UncivFiles(
|
||||
//region Settings
|
||||
|
||||
private fun getGeneralSettingsFile(): FileHandle {
|
||||
return if (UncivGame.Current.consoleMode) FileHandle(SETTINGS_FILE_NAME)
|
||||
return if (UncivGame.Current.isConsoleMode) FileHandle(SETTINGS_FILE_NAME)
|
||||
else files.local(SETTINGS_FILE_NAME)
|
||||
}
|
||||
|
||||
@ -325,6 +306,17 @@ class UncivFiles(
|
||||
|
||||
var saveZipped = false
|
||||
|
||||
/**
|
||||
* If the GDX [com.badlogic.gdx.Files.getExternalStoragePath] should be preferred for this platform,
|
||||
* otherwise uses [com.badlogic.gdx.Files.getLocalStoragePath]
|
||||
*/
|
||||
var preferExternalStorage = false
|
||||
|
||||
/**
|
||||
* Platform dependent saver-loader to custom system locations
|
||||
*/
|
||||
lateinit var saverLoader: PlatformSaverLoader
|
||||
|
||||
/** Specialized function to access settings before Gdx is initialized.
|
||||
*
|
||||
* @param base Path to the directory where the file should be - if not set, the OS current directory is used (which is "/" on Android)
|
||||
@ -333,11 +325,7 @@ class UncivFiles(
|
||||
// FileHandle is Gdx, but the class and JsonParser are not dependent on app initialization
|
||||
// In fact, at this point Gdx.app or Gdx.files are null but this still works.
|
||||
val file = FileHandle(base + File.separator + SETTINGS_FILE_NAME)
|
||||
return if (file.exists())
|
||||
json().fromJsonFile(
|
||||
GameSettings::class.java,
|
||||
file
|
||||
)
|
||||
return if (file.exists()) json().fromJsonFile(GameSettings::class.java, file)
|
||||
else GameSettings().apply { isFreshlyCreated = true }
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.unciv.Constants
|
||||
import com.unciv.GUI
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.IsPartOfGameInfoSerialization
|
||||
import com.unciv.logic.city.City
|
||||
import com.unciv.logic.civilization.Civilization
|
||||
@ -22,6 +21,7 @@ import com.unciv.models.ruleset.unique.StateForConditionals
|
||||
import com.unciv.models.ruleset.unique.Unique
|
||||
import com.unciv.models.ruleset.unique.UniqueMap
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.utils.DebugUtils
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.min
|
||||
import kotlin.random.Random
|
||||
@ -235,13 +235,13 @@ open class Tile : IsPartOfGameInfoSerialization {
|
||||
else ruleset.terrains[naturalWonder!!]!!
|
||||
|
||||
fun isVisible(player: Civilization): Boolean {
|
||||
if (UncivGame.Current.viewEntireMapForDebug)
|
||||
if (DebugUtils.VISIBLE_MAP)
|
||||
return true
|
||||
return player.viewableTiles.contains(this)
|
||||
}
|
||||
|
||||
fun isExplored(player: Civilization): Boolean {
|
||||
if (UncivGame.Current.viewEntireMapForDebug || player.isSpectator())
|
||||
if (DebugUtils.VISIBLE_MAP || player.isSpectator())
|
||||
return true
|
||||
return exploredBy.contains(player.civName)
|
||||
}
|
||||
|
@ -1,19 +1,19 @@
|
||||
package com.unciv.logic.map.tile
|
||||
|
||||
import com.unciv.Constants
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.civilization.Civilization
|
||||
import com.unciv.models.ruleset.tile.ResourceType
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.screens.civilopediascreen.FormattedLine
|
||||
import com.unciv.ui.components.Fonts
|
||||
import com.unciv.utils.DebugUtils
|
||||
|
||||
object TileDescription {
|
||||
|
||||
/** Get info on a selected tile, used on WorldScreen (right side above minimap), CityScreen or MapEditorViewTab. */
|
||||
fun toMarkup(tile: Tile, viewingCiv: Civilization?): ArrayList<FormattedLine> {
|
||||
val lineList = ArrayList<FormattedLine>()
|
||||
val isViewableToPlayer = viewingCiv == null || UncivGame.Current.viewEntireMapForDebug
|
||||
val isViewableToPlayer = viewingCiv == null || DebugUtils.VISIBLE_MAP
|
||||
|| viewingCiv.viewableTiles.contains(tile)
|
||||
|
||||
if (tile.isCityCenter()) {
|
||||
@ -21,7 +21,7 @@ object TileDescription {
|
||||
var cityString = city.name.tr()
|
||||
if (isViewableToPlayer) cityString += " (${city.health})"
|
||||
lineList += FormattedLine(cityString)
|
||||
if (UncivGame.Current.viewEntireMapForDebug || city.civ == viewingCiv)
|
||||
if (DebugUtils.VISIBLE_MAP || city.civ == viewingCiv)
|
||||
lineList += city.cityConstructions.getProductionMarkup(tile.ruleset)
|
||||
}
|
||||
|
||||
|
@ -177,6 +177,7 @@ object Fonts {
|
||||
const val ORIGINAL_FONT_SIZE = 50f
|
||||
const val DEFAULT_FONT_FAMILY = ""
|
||||
|
||||
lateinit var fontImplementation: FontImplementation
|
||||
lateinit var font: BitmapFont
|
||||
|
||||
/** This resets all cached font data in object Fonts.
|
||||
@ -184,15 +185,12 @@ object Fonts {
|
||||
*/
|
||||
fun resetFont() {
|
||||
val settings = GUI.getSettings()
|
||||
val fontImpl = GUI.getFontImpl()
|
||||
fontImpl.setFontFamily(settings.fontFamilyData, settings.getFontSize())
|
||||
font = fontImpl.getBitmapFont()
|
||||
fontImplementation.setFontFamily(settings.fontFamilyData, settings.getFontSize())
|
||||
font = fontImplementation.getBitmapFont()
|
||||
}
|
||||
|
||||
/** Reduce the font list returned by platform-specific code to font families (plain variant if possible) */
|
||||
fun getSystemFonts(): Sequence<FontFamilyData> {
|
||||
val fontImplementation = UncivGame.Current.fontImplementation
|
||||
?: return emptySequence()
|
||||
return fontImplementation.getSystemFonts()
|
||||
.sortedWith(compareBy(UncivGame.Current.settings.getCollatorFromLocale()) { it.localName })
|
||||
}
|
||||
|
@ -1,42 +0,0 @@
|
||||
package com.unciv.ui.components
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.TextField
|
||||
import com.unciv.models.metadata.GameSettings
|
||||
|
||||
/** Interface to support various platform-specific tools */
|
||||
interface GeneralPlatformSpecificHelpers {
|
||||
/** Pass a Boolean setting as used in [allowAndroidPortrait][GameSettings.allowAndroidPortrait] to the OS.
|
||||
*
|
||||
* You can turn a mobile device on its side or upside down, and a mobile OS may or may not allow the
|
||||
* position changes to automatically result in App orientation changes. This is about limiting that feature.
|
||||
* @param allow `true`: allow all orientations (follows sensor as limited by OS settings)
|
||||
* `false`: allow only landscape orientations (both if supported, otherwise default landscape only)
|
||||
*/
|
||||
fun allowPortrait(allow: Boolean) {}
|
||||
|
||||
fun hasDisplayCutout(): Boolean { return false }
|
||||
fun toggleDisplayCutout(androidCutout: Boolean) {}
|
||||
|
||||
/**
|
||||
* Notifies the user that it's their turn while the game is running
|
||||
*/
|
||||
fun notifyTurnStarted() {}
|
||||
|
||||
/**
|
||||
* If the GDX [com.badlogic.gdx.Files.getExternalStoragePath] should be preferred for this platform,
|
||||
* otherwise uses [com.badlogic.gdx.Files.getLocalStoragePath]
|
||||
*/
|
||||
fun shouldPreferExternalStorage(): Boolean
|
||||
|
||||
/**
|
||||
* Handle an uncaught throwable.
|
||||
* @return true if the throwable was handled.
|
||||
*/
|
||||
fun handleUncaughtThrowable(ex: Throwable): Boolean = false
|
||||
|
||||
/**
|
||||
* Adds platform-specific improvements to the given text field, making it nicer to interact with on this platform.
|
||||
*/
|
||||
fun addImprovements(textField: TextField): TextField = textField
|
||||
|
||||
}
|
@ -1,11 +1,16 @@
|
||||
package com.unciv.ui.components
|
||||
|
||||
import com.badlogic.gdx.Application
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor
|
||||
import com.badlogic.gdx.scenes.scene2d.InputEvent
|
||||
import com.badlogic.gdx.scenes.scene2d.InputListener
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.TextField
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.FocusListener
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.event.EventBus
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.screens.basescreen.UncivStage
|
||||
import com.unciv.ui.components.extensions.getAscendant
|
||||
@ -13,7 +18,11 @@ import com.unciv.ui.components.extensions.getOverlap
|
||||
import com.unciv.ui.components.extensions.right
|
||||
import com.unciv.ui.components.extensions.stageBoundingBox
|
||||
import com.unciv.ui.components.extensions.top
|
||||
import com.unciv.ui.popups.Popup
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
import com.unciv.utils.concurrency.Concurrency
|
||||
import com.unciv.utils.concurrency.withGLContext
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
object UncivTextField {
|
||||
/**
|
||||
@ -33,7 +42,9 @@ object UncivTextField {
|
||||
}
|
||||
}
|
||||
})
|
||||
UncivGame.Current.platformSpecificHelper?.addImprovements(textField)
|
||||
|
||||
if (Gdx.app.type == Application.ApplicationType.Android)
|
||||
TextfieldImprovements.add(textField)
|
||||
return textField
|
||||
}
|
||||
}
|
||||
@ -96,3 +107,97 @@ fun TextField.scrollAscendantToTextField(): Boolean {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
object TextfieldImprovements {
|
||||
private val hideKeyboard = { Gdx.input.setOnscreenKeyboardVisible(false) }
|
||||
fun add(textField: TextField): TextField {
|
||||
textField.addListener(object : InputListener() {
|
||||
private val events = EventBus.EventReceiver()
|
||||
init {
|
||||
events.receive(UncivStage.VisibleAreaChanged::class) {
|
||||
if (textField.stage == null || !textField.hasKeyboardFocus()) return@receive
|
||||
Concurrency.run {
|
||||
// If anything resizes, it also does so with this event. So we need to wait for that to finish to update the scroll position.
|
||||
delay(100)
|
||||
withGLContext {
|
||||
if (textField.stage == null) return@withGLContext
|
||||
|
||||
if (textField.scrollAscendantToTextField()) {
|
||||
val scrollPane = textField.getAscendant { it is ScrollPane } as ScrollPane?
|
||||
// when screen dimensions change, we don't want an animation for scrolling, just show, just show the textfield immediately
|
||||
scrollPane?.updateVisualScroll()
|
||||
} else {
|
||||
// We can't scroll the text field into view, so we need to show a popup
|
||||
TextfieldPopup(textField).open()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int): Boolean {
|
||||
addPopupCloseListener(textField)
|
||||
return false
|
||||
}
|
||||
})
|
||||
textField.addListener(object : FocusListener() {
|
||||
override fun keyboardFocusChanged(event: FocusEvent?, actor: Actor?, focused: Boolean) {
|
||||
if (focused) {
|
||||
addPopupCloseListener(textField)
|
||||
Gdx.input.setOnscreenKeyboardVisible(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return textField
|
||||
}
|
||||
|
||||
private fun addPopupCloseListener(textField: TextField) {
|
||||
val popup = textField.getAscendant { it is Popup } as Popup?
|
||||
if (popup != null && !popup.closeListeners.contains(hideKeyboard)) {
|
||||
popup.closeListeners.add(hideKeyboard)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TextfieldPopup(
|
||||
textField: TextField
|
||||
) : Popup(textField.stage) {
|
||||
val popupTextfield = clone(textField)
|
||||
init {
|
||||
addGoodSizedLabel(popupTextfield.messageText)
|
||||
.colspan(2)
|
||||
.row()
|
||||
|
||||
add(popupTextfield)
|
||||
.width(stageToShowOn.width / 2)
|
||||
.colspan(2)
|
||||
.row()
|
||||
|
||||
addCloseButton("Cancel")
|
||||
.left()
|
||||
addOKButton { textField.text = popupTextfield.text }
|
||||
.right()
|
||||
.row()
|
||||
|
||||
showListeners.add {
|
||||
stageToShowOn.keyboardFocus = popupTextfield
|
||||
}
|
||||
closeListeners.add {
|
||||
stageToShowOn.keyboardFocus = null
|
||||
Gdx.input.setOnscreenKeyboardVisible(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun clone(textField: TextField): TextField {
|
||||
@Suppress("UNCIV_RAW_TEXTFIELD") // we are copying the existing text field
|
||||
val copy = TextField(textField.text, textField.style)
|
||||
copy.textFieldFilter = textField.textFieldFilter
|
||||
copy.messageText = textField.messageText
|
||||
copy.setSelection(textField.selectionStart, textField.selection.length)
|
||||
copy.cursorPosition = textField.cursorPosition
|
||||
copy.alignment = textField.alignment
|
||||
copy.isPasswordMode = textField.isPasswordMode
|
||||
copy.onscreenKeyboard = textField.onscreenKeyboard
|
||||
return copy
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
import com.unciv.ui.screens.cityscreen.CityReligionInfoTable
|
||||
import com.unciv.ui.screens.cityscreen.CityScreen
|
||||
import com.unciv.ui.screens.diplomacyscreen.DiplomacyScreen
|
||||
import com.unciv.utils.DebugUtils
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
@ -228,7 +229,7 @@ private class CityTable(city: City, forPopup: Boolean = false) : BorderedTable(
|
||||
pad(0f)
|
||||
defaults().pad(0f)
|
||||
|
||||
val isShowDetailedInfo = UncivGame.Current.viewEntireMapForDebug
|
||||
val isShowDetailedInfo = DebugUtils.VISIBLE_MAP
|
||||
|| city.civ == viewingCiv
|
||||
|| viewingCiv.isSpectator()
|
||||
|
||||
@ -532,7 +533,7 @@ class CityButton(val city: City, private val tileGroup: TileGroup): Table(BaseSc
|
||||
if (isButtonMoved) {
|
||||
// second tap on the button will go to the city screen
|
||||
// if this city belongs to you and you are not iterating though the air units
|
||||
if (GUI.isDebugMapVisible() || viewingPlayer.isSpectator()
|
||||
if (DebugUtils.VISIBLE_MAP || viewingPlayer.isSpectator()
|
||||
|| (belongsToViewingCiv() && !tileGroup.tile.airUnits.contains(unitTable.selectedUnit))) {
|
||||
GUI.pushScreen(CityScreen(city))
|
||||
} else if (viewingPlayer.knows(city.civ)) {
|
||||
|
@ -2,7 +2,6 @@ package com.unciv.ui.components.tilegroups
|
||||
|
||||
import com.badlogic.gdx.graphics.g2d.Batch
|
||||
import com.badlogic.gdx.scenes.scene2d.Group
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.civilization.Civilization
|
||||
import com.unciv.logic.map.tile.Tile
|
||||
import com.unciv.ui.components.tilegroups.layers.TileLayerBorders
|
||||
@ -13,6 +12,7 @@ import com.unciv.ui.components.tilegroups.layers.TileLayerOverlay
|
||||
import com.unciv.ui.components.tilegroups.layers.TileLayerTerrain
|
||||
import com.unciv.ui.components.tilegroups.layers.TileLayerUnitArt
|
||||
import com.unciv.ui.components.tilegroups.layers.TileLayerUnitFlag
|
||||
import com.unciv.utils.DebugUtils
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sqrt
|
||||
|
||||
@ -40,7 +40,7 @@ open class TileGroup(
|
||||
val hexagonImageOrigin = Pair(hexagonImageWidth / 2f, sqrt((hexagonImageWidth / 2f).pow(2) - (hexagonImageWidth / 4f).pow(2)))
|
||||
val hexagonImagePosition = Pair(-hexagonImageOrigin.first / 3f, -hexagonImageOrigin.second / 4f)
|
||||
|
||||
var isForceVisible = UncivGame.Current.viewEntireMapForDebug
|
||||
var isForceVisible = DebugUtils.VISIBLE_MAP
|
||||
var isForMapEditorIcon = false
|
||||
|
||||
@Suppress("LeakingThis") val layerTerrain = TileLayerTerrain(this, groupSize)
|
||||
|
@ -4,11 +4,11 @@ import com.badlogic.gdx.graphics.g2d.Batch
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor
|
||||
import com.badlogic.gdx.scenes.scene2d.Touchable
|
||||
import com.badlogic.gdx.utils.Align
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.civilization.Civilization
|
||||
import com.unciv.ui.components.tilegroups.CityButton
|
||||
import com.unciv.ui.components.tilegroups.TileGroup
|
||||
import com.unciv.ui.components.tilegroups.WorldTileGroup
|
||||
import com.unciv.utils.DebugUtils
|
||||
|
||||
class TileLayerCityButton(tileGroup: TileGroup, size: Float) : TileLayer(tileGroup, size) {
|
||||
|
||||
@ -60,7 +60,7 @@ class TileLayerCityButton(tileGroup: TileGroup, size: Float) : TileLayer(tileGro
|
||||
return
|
||||
|
||||
val tileIsViewable = isViewable(viewingCiv)
|
||||
val shouldShow = UncivGame.Current.viewEntireMapForDebug
|
||||
val shouldShow = DebugUtils.VISIBLE_MAP
|
||||
|
||||
// Create (if not yet) and update city button
|
||||
if (city != null && tileGroup.tile.isCityCenter()) {
|
||||
|
@ -1,5 +0,0 @@
|
||||
package com.unciv.ui.crashhandling
|
||||
|
||||
interface CrashReportSysInfo {
|
||||
fun getInfo(): String
|
||||
}
|
@ -19,6 +19,7 @@ import com.unciv.ui.components.extensions.toLabel
|
||||
import com.unciv.ui.images.IconTextButton
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.popups.ToastPopup
|
||||
import com.unciv.utils.Log
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
|
||||
@ -88,7 +89,7 @@ class CrashScreen(val exception: Throwable): BaseScreen() {
|
||||
|
||||
--------------------------------
|
||||
|
||||
${UncivGame.Current.crashReportSysInfo?.getInfo().toString().prependIndentToOnlyNewLines(baseIndent)}
|
||||
${Log.getSystemInfo().prependIndentToOnlyNewLines(baseIndent)}
|
||||
|
||||
--------------------------------
|
||||
|
||||
|
@ -54,17 +54,18 @@ fun advancedTab(
|
||||
|
||||
addAutosaveTurnsSelectBox(this, settings)
|
||||
|
||||
if (UncivGame.Current.platformSpecificHelper?.hasDisplayCutout() == true)
|
||||
optionsPopup.addCheckbox(this, "Enable display cutout (requires restart)", settings.androidCutout, false) { settings.androidCutout = it }
|
||||
if (UncivGame.Current.hasDisplayCutout())
|
||||
optionsPopup.addCheckbox(this, "Enable display cutout (requires restart)", settings.androidCutout) {
|
||||
settings.androidCutout = it
|
||||
}
|
||||
|
||||
addMaxZoomSlider(this, settings)
|
||||
|
||||
val helper = UncivGame.Current.platformSpecificHelper
|
||||
if (helper != null && Gdx.app.type == Application.ApplicationType.Android) {
|
||||
if (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
|
||||
helper.allowPortrait(it)
|
||||
UncivGame.Current.allowPortrait(it)
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,7 +238,7 @@ private fun addTranslationGeneration(table: Table, optionsPopup: OptionsPopup) {
|
||||
Concurrency.run("GenerateScreenshot") {
|
||||
val extraImagesLocation = "../../extraImages"
|
||||
// I'm not sure why we need to advance the y by 2 for every screenshot... but that's the only way it remains centered
|
||||
generateScreenshots(arrayListOf(
|
||||
generateScreenshots(optionsPopup.settings, arrayListOf(
|
||||
ScreenshotConfig(630, 500, ScreenSize.Medium, "$extraImagesLocation/itch.io image.png", Vector2(-2f, 2f),false),
|
||||
ScreenshotConfig(1280, 640, ScreenSize.Medium, "$extraImagesLocation/GithubPreviewImage.png", Vector2(-2f, 4f)),
|
||||
ScreenshotConfig(1024, 500, ScreenSize.Medium, "$extraImagesLocation/Feature graphic - Google Play.png",Vector2(-2f, 6f)),
|
||||
@ -251,12 +252,12 @@ private fun addTranslationGeneration(table: Table, optionsPopup: OptionsPopup) {
|
||||
|
||||
data class ScreenshotConfig(val width: Int, val height: Int, val screenSize: ScreenSize, var fileLocation:String, var centerTile:Vector2, var attackCity:Boolean=true)
|
||||
|
||||
private fun CoroutineScope.generateScreenshots(configs:ArrayList<ScreenshotConfig>) {
|
||||
private fun CoroutineScope.generateScreenshots(settings: GameSettings, configs:ArrayList<ScreenshotConfig>) {
|
||||
val currentConfig = configs.first()
|
||||
launchOnGLThread {
|
||||
val screenshotGame =
|
||||
UncivGame.Current.files.loadGameByName("ScreenshotGenerationGame")
|
||||
UncivGame.Current.settings.screenSize = currentConfig.screenSize
|
||||
settings.screenSize = currentConfig.screenSize
|
||||
val newScreen = UncivGame.Current.loadGame(screenshotGame)
|
||||
newScreen.stage.viewport.update(currentConfig.width, currentConfig.height, true)
|
||||
|
||||
@ -290,7 +291,7 @@ private fun CoroutineScope.generateScreenshots(configs:ArrayList<ScreenshotConfi
|
||||
)
|
||||
pixmap.dispose()
|
||||
val newConfigs = configs.withoutItem(currentConfig)
|
||||
if (newConfigs.isNotEmpty()) generateScreenshots(newConfigs)
|
||||
if (newConfigs.isNotEmpty()) generateScreenshots(settings, newConfigs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import com.unciv.ui.components.extensions.onClick
|
||||
import com.unciv.ui.components.extensions.toCheckBox
|
||||
import com.unciv.ui.components.extensions.toLabel
|
||||
import com.unciv.ui.components.extensions.toTextButton
|
||||
import com.unciv.utils.DebugUtils
|
||||
|
||||
fun debugTab() = Table(BaseScreen.skin).apply {
|
||||
pad(10f)
|
||||
@ -22,7 +23,7 @@ fun debugTab() = Table(BaseScreen.skin).apply {
|
||||
|
||||
if (GUI.isWorldLoaded()) {
|
||||
val simulateButton = "Simulate until turn:".toTextButton()
|
||||
val simulateTextField = UncivTextField.create("Turn", game.simulateUntilTurnForDebug.toString())
|
||||
val simulateTextField = UncivTextField.create("Turn", DebugUtils.SIMULATE_UNTIL_TURN.toString())
|
||||
val invalidInputLabel = "This is not a valid integer!".toLabel().also { it.isVisible = false }
|
||||
simulateButton.onClick {
|
||||
val simulateUntilTurns = simulateTextField.text.toIntOrNull()
|
||||
@ -30,7 +31,7 @@ fun debugTab() = Table(BaseScreen.skin).apply {
|
||||
invalidInputLabel.isVisible = true
|
||||
return@onClick
|
||||
}
|
||||
game.simulateUntilTurnForDebug = simulateUntilTurns
|
||||
DebugUtils.SIMULATE_UNTIL_TURN = simulateUntilTurns
|
||||
invalidInputLabel.isVisible = false
|
||||
GUI.getWorldScreen().nextTurn()
|
||||
}
|
||||
@ -39,11 +40,11 @@ fun debugTab() = Table(BaseScreen.skin).apply {
|
||||
add(invalidInputLabel).colspan(2).row()
|
||||
}
|
||||
|
||||
add("Supercharged".toCheckBox(game.superchargedForDebug) {
|
||||
game.superchargedForDebug = it
|
||||
add("Supercharged".toCheckBox(DebugUtils.SUPERCHARGED) {
|
||||
DebugUtils.SUPERCHARGED = it
|
||||
}).colspan(2).row()
|
||||
add("View entire map".toCheckBox(game.viewEntireMapForDebug) {
|
||||
game.viewEntireMapForDebug = it
|
||||
add("View entire map".toCheckBox(DebugUtils.VISIBLE_MAP) {
|
||||
DebugUtils.VISIBLE_MAP = it
|
||||
}).colspan(2).row()
|
||||
val curGameInfo = game.gameInfo
|
||||
if (curGameInfo != null) {
|
||||
|
@ -44,6 +44,8 @@ class OptionsPopup(
|
||||
private val selectPage: Int = defaultPage,
|
||||
private val onClose: () -> Unit = {}
|
||||
) : Popup(screen.stage, /** [TabbedPager] handles scrolling */ scrollable = false ) {
|
||||
|
||||
val game = screen.game
|
||||
val settings = screen.game.settings
|
||||
val tabs: TabbedPager
|
||||
val selectBoxMinWidth: Float
|
||||
@ -120,7 +122,7 @@ class OptionsPopup(
|
||||
|
||||
addCloseButton {
|
||||
screen.game.musicController.onChange(null)
|
||||
screen.game.platformSpecificHelper?.allowPortrait(settings.allowAndroidPortrait)
|
||||
screen.game.allowPortrait(settings.allowAndroidPortrait)
|
||||
onClose()
|
||||
}.padBottom(10f)
|
||||
|
||||
@ -205,7 +207,7 @@ open class SettingsSelect<T : Any>(
|
||||
) {
|
||||
private val settingsProperty: KMutableProperty0<T> = setting.getProperty(settings)
|
||||
private val label = createLabel(labelText)
|
||||
protected val refreshSelectBox = createSelectBox(items.toGdxArray(), settings)
|
||||
private val refreshSelectBox = createSelectBox(items.toGdxArray(), settings)
|
||||
val items by refreshSelectBox::items
|
||||
|
||||
private fun createLabel(labelText: String): Label {
|
||||
|
@ -17,6 +17,7 @@ import com.unciv.ui.components.extensions.toLabel
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.screens.civilopediascreen.CivilopediaCategories
|
||||
import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen
|
||||
import com.unciv.utils.DebugUtils
|
||||
|
||||
class WonderOverviewTab(
|
||||
viewingPlayer: Civilization,
|
||||
@ -115,7 +116,7 @@ class WonderInfo {
|
||||
val city: City?,
|
||||
val location: Tile?
|
||||
) {
|
||||
val viewEntireMapForDebug = UncivGame.Current.viewEntireMapForDebug
|
||||
val viewEntireMapForDebug = DebugUtils.VISIBLE_MAP
|
||||
|
||||
fun getImage() = if (status == WonderStatus.Unknown && !viewEntireMapForDebug) null
|
||||
else category.getImage?.invoke(name, if (category == CivilopediaCategories.Terrain) 50f else 45f)
|
||||
|
@ -159,22 +159,22 @@ class LoadGameScreen : LoadOrSaveScreen() {
|
||||
}
|
||||
|
||||
private fun Table.addLoadFromCustomLocationButton() {
|
||||
if (!game.files.canLoadFromCustomSaveLocation()) return
|
||||
val loadFromCustomLocation = loadFromCustomLocation.toTextButton()
|
||||
loadFromCustomLocation.onClick {
|
||||
errorLabel.isVisible = false
|
||||
loadFromCustomLocation.setText(Constants.loading.tr())
|
||||
loadFromCustomLocation.disable()
|
||||
Concurrency.run(Companion.loadFromCustomLocation) {
|
||||
game.files.loadGameFromCustomLocation { result ->
|
||||
if (result.isError()) {
|
||||
handleLoadGameException(result.exception!!, "Could not load game from custom location!")
|
||||
} else if (result.isSuccessful()) {
|
||||
Concurrency.run {
|
||||
game.loadGame(result.gameData!!, true)
|
||||
}
|
||||
game.files.loadGameFromCustomLocation(
|
||||
{
|
||||
Concurrency.run { game.loadGame(it, true) }
|
||||
loadFromCustomLocation.enable()
|
||||
},
|
||||
{
|
||||
handleLoadGameException(it, "Could not load game from custom location!")
|
||||
loadFromCustomLocation.enable()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
add(loadFromCustomLocation).row()
|
||||
|
@ -82,7 +82,6 @@ class SaveGameScreen(val gameInfo: GameInfo) : LoadOrSaveScreen("Current saves")
|
||||
}
|
||||
|
||||
private fun Table.addSaveToCustomLocation() {
|
||||
if (!game.files.canLoadFromCustomSaveLocation()) return
|
||||
val saveToCustomLocation = "Save to custom location".toTextButton()
|
||||
val errorLabel = "".toLabel(Color.RED)
|
||||
saveToCustomLocation.onClick {
|
||||
@ -90,15 +89,18 @@ class SaveGameScreen(val gameInfo: GameInfo) : LoadOrSaveScreen("Current saves")
|
||||
saveToCustomLocation.setText("Saving...".tr())
|
||||
saveToCustomLocation.disable()
|
||||
Concurrency.runOnNonDaemonThreadPool("Save to custom location") {
|
||||
game.files.saveGameToCustomLocation(gameInfo, gameNameTextField.text) { result ->
|
||||
if (result.isError()) {
|
||||
errorLabel.setText("Could not save game to custom location!".tr())
|
||||
result.exception?.printStackTrace()
|
||||
} else if (result.isSuccessful()) {
|
||||
|
||||
game.files.saveGameToCustomLocation(gameInfo, gameNameTextField.text,
|
||||
{
|
||||
game.popScreen()
|
||||
saveToCustomLocation.enable()
|
||||
},
|
||||
{
|
||||
errorLabel.setText("Could not save game to custom location!".tr())
|
||||
it.printStackTrace()
|
||||
saveToCustomLocation.enable()
|
||||
}
|
||||
saveToCustomLocation.enable()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
add(saveToCustomLocation).row()
|
||||
|
@ -350,7 +350,7 @@ class WorldScreen(
|
||||
debug("loadLatestMultiplayerState downloaded game: gameId: %s, turn: %s, curCiv: %s",
|
||||
latestGame.gameId, latestGame.turns, latestGame.currentPlayer)
|
||||
if (viewingCiv.civName == latestGame.currentPlayer || viewingCiv.civName == Constants.spectator) {
|
||||
game.platformSpecificHelper?.notifyTurnStarted()
|
||||
game.notifyTurnStarted()
|
||||
}
|
||||
launchOnGLThread {
|
||||
loadingGamePopup.close()
|
||||
|
@ -31,6 +31,7 @@ import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
import com.unciv.ui.screens.worldscreen.WorldScreen
|
||||
import com.unciv.ui.screens.worldscreen.bottombar.BattleTableHelpers.flashWoundedCombatants
|
||||
import com.unciv.ui.screens.worldscreen.bottombar.BattleTableHelpers.getHealthBar
|
||||
import com.unciv.utils.DebugUtils
|
||||
import kotlin.math.max
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@ -102,15 +103,12 @@ class BattleTable(val worldScreen: WorldScreen): Table() {
|
||||
if (defender == null || (!includeFriendly && defender.getCivInfo() == attackerCiv))
|
||||
return null // no enemy combatant in tile
|
||||
|
||||
val canSeeDefender =
|
||||
if (UncivGame.Current.viewEntireMapForDebug) true
|
||||
else {
|
||||
when {
|
||||
defender.isInvisible(attackerCiv) -> attackerCiv.viewableInvisibleUnitsTiles.contains(selectedTile)
|
||||
defender.isCity() -> attackerCiv.hasExplored(selectedTile)
|
||||
else -> attackerCiv.viewableTiles.contains(selectedTile)
|
||||
}
|
||||
}
|
||||
val canSeeDefender = when {
|
||||
DebugUtils.VISIBLE_MAP -> true
|
||||
defender.isInvisible(attackerCiv) -> attackerCiv.viewableInvisibleUnitsTiles.contains(selectedTile)
|
||||
defender.isCity() -> attackerCiv.hasExplored(selectedTile)
|
||||
else -> attackerCiv.viewableTiles.contains(selectedTile)
|
||||
}
|
||||
|
||||
if (!canSeeDefender) return null
|
||||
|
||||
|
@ -15,6 +15,7 @@ import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
import com.unciv.ui.components.extensions.addBorderAllowOpacity
|
||||
import com.unciv.ui.components.extensions.darken
|
||||
import com.unciv.ui.components.extensions.toLabel
|
||||
import com.unciv.utils.DebugUtils
|
||||
|
||||
class TileInfoTable(private val viewingCiv :Civilization) : Table(BaseScreen.skin) {
|
||||
init {
|
||||
@ -27,12 +28,12 @@ class TileInfoTable(private val viewingCiv :Civilization) : Table(BaseScreen.ski
|
||||
internal fun updateTileTable(tile: Tile?) {
|
||||
clearChildren()
|
||||
|
||||
if (tile != null && (UncivGame.Current.viewEntireMapForDebug || viewingCiv.hasExplored(tile)) ) {
|
||||
if (tile != null && (DebugUtils.VISIBLE_MAP || viewingCiv.hasExplored(tile)) ) {
|
||||
add(getStatsTable(tile))
|
||||
add(MarkupRenderer.render(TileDescription.toMarkup(tile, viewingCiv), padding = 0f, iconDisplay = IconDisplay.None) {
|
||||
UncivGame.Current.pushScreen(CivilopediaScreen(viewingCiv.gameInfo.ruleset, link = it))
|
||||
} ).pad(5f).row()
|
||||
if (UncivGame.Current.viewEntireMapForDebug)
|
||||
if (DebugUtils.VISIBLE_MAP)
|
||||
add(tile.position.run { "(${x.toInt()},${y.toInt()})" }.toLabel()).colspan(2).pad(5f)
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ import com.badlogic.gdx.scenes.scene2d.Actor
|
||||
import com.badlogic.gdx.scenes.scene2d.Group
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Image
|
||||
import com.badlogic.gdx.utils.Align
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.map.HexMath
|
||||
import com.unciv.logic.civilization.Civilization
|
||||
import com.unciv.logic.map.tile.Tile
|
||||
@ -13,6 +12,7 @@ import com.unciv.ui.images.IconCircleGroup
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.components.extensions.onClick
|
||||
import com.unciv.ui.components.extensions.surroundWithCircle
|
||||
import com.unciv.utils.DebugUtils
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.atan
|
||||
|
||||
@ -36,7 +36,7 @@ internal class MinimapTile(val tile: Tile, tileSize: Float, val onClick: () -> U
|
||||
}
|
||||
|
||||
fun updateColor(isTileUnrevealed: Boolean) {
|
||||
image.isVisible = UncivGame.Current.viewEntireMapForDebug || !isTileUnrevealed
|
||||
image.isVisible = DebugUtils.VISIBLE_MAP || !isTileUnrevealed
|
||||
if (!image.isVisible) return
|
||||
image.color = when {
|
||||
tile.isCityCenter() && !tile.isWater -> tile.getOwner()!!.nation.getInnerColor()
|
||||
|
20
core/src/com/unciv/utils/Debug.kt
Normal file
20
core/src/com/unciv/utils/Debug.kt
Normal file
@ -0,0 +1,20 @@
|
||||
package com.unciv.utils
|
||||
|
||||
object DebugUtils {
|
||||
|
||||
/**
|
||||
* This exists so that when debugging we can see the entire map.
|
||||
* Remember to turn this to false before commit and upload!
|
||||
*/
|
||||
var VISIBLE_MAP: Boolean = false
|
||||
|
||||
/** For when you need to test something in an advanced game and don't have time to faff around */
|
||||
var SUPERCHARGED: Boolean = 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.
|
||||
*/
|
||||
var SIMULATE_UNTIL_TURN: Int = 0
|
||||
|
||||
}
|
@ -143,6 +143,13 @@ object Log {
|
||||
fun error(tag: Tag, msg: String, throwable: Throwable) {
|
||||
doLog(backend::error, tag, buildThrowableMessage(msg, throwable))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get string information about operation system
|
||||
*/
|
||||
fun getSystemInfo(): String {
|
||||
return backend.getSystemInfo()
|
||||
}
|
||||
}
|
||||
|
||||
class Tag(val name: String)
|
||||
@ -153,6 +160,9 @@ interface LogBackend {
|
||||
|
||||
/** Do not log on release builds for performance reasons. */
|
||||
fun isRelease(): Boolean
|
||||
|
||||
/** Get string information about operation system */
|
||||
fun getSystemInfo(): String
|
||||
}
|
||||
|
||||
/** Only for tests, or temporary main() functions */
|
||||
@ -168,6 +178,10 @@ open class DefaultLogBackend : LogBackend {
|
||||
override fun isRelease(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun getSystemInfo(): String {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
/** Shortcut for [Log.debug] */
|
||||
|
17
core/src/com/unciv/utils/PlatformSpecific.kt
Normal file
17
core/src/com/unciv/utils/PlatformSpecific.kt
Normal file
@ -0,0 +1,17 @@
|
||||
package com.unciv.utils
|
||||
|
||||
interface PlatformSpecific {
|
||||
|
||||
/** Notifies player that his multiplayer turn started */
|
||||
fun notifyTurnStarted() {}
|
||||
|
||||
/** Install system audio hooks */
|
||||
fun installAudioHooks() {}
|
||||
|
||||
/** (Android) allow screen orientation switch */
|
||||
fun allowPortrait(allow: Boolean) {}
|
||||
|
||||
/** (Android) returns whether display has cutout */
|
||||
fun hasDisplayCutout(): Boolean { return false }
|
||||
|
||||
}
|
@ -2,7 +2,6 @@ package com.unciv.app.desktop
|
||||
|
||||
import com.unciv.Constants
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.UncivGameParameters
|
||||
import com.unciv.utils.Log
|
||||
import com.unciv.logic.GameStarter
|
||||
import com.unciv.logic.civilization.PlayerType
|
||||
@ -26,8 +25,7 @@ internal object ConsoleLauncher {
|
||||
fun main(arg: Array<String>) {
|
||||
Log.backend = DesktopLogBackend()
|
||||
|
||||
val consoleParameters = UncivGameParameters(consoleMode = true)
|
||||
val game = UncivGame(consoleParameters)
|
||||
val game = UncivGame(true)
|
||||
|
||||
UncivGame.Current = game
|
||||
UncivGame.Current.settings = GameSettings().apply {
|
||||
|
@ -1,100 +0,0 @@
|
||||
package com.unciv.app.desktop
|
||||
|
||||
import com.badlogic.gdx.files.FileHandle
|
||||
import com.unciv.ui.crashhandling.CrashReportSysInfo
|
||||
import java.nio.charset.Charset
|
||||
|
||||
class CrashReportSysInfoDesktop : CrashReportSysInfo {
|
||||
|
||||
override fun getInfo(): String {
|
||||
val builder = StringBuilder()
|
||||
|
||||
// Operating system
|
||||
val osName = System.getProperty("os.name") ?: "Unknown"
|
||||
val isWindows = osName.startsWith("Windows", ignoreCase = true)
|
||||
builder.append("OS: $osName")
|
||||
if (!isWindows) {
|
||||
val osInfo = listOfNotNull(System.getProperty("os.arch"), System.getProperty("os.version")).joinToString()
|
||||
if (osInfo.isNotEmpty()) builder.append(" ($osInfo)")
|
||||
}
|
||||
builder.appendLine()
|
||||
|
||||
// Specific release info
|
||||
val osRelease = if (isWindows) getWinVer() else getLinuxDistro()
|
||||
if (osRelease.isNotEmpty())
|
||||
builder.appendLine("\t$osRelease")
|
||||
|
||||
// Java runtime version
|
||||
val javaVendor: String? = System.getProperty("java.vendor")
|
||||
if (javaVendor != null) {
|
||||
val javaVersion: String = System.getProperty("java.vendor.version") ?: System.getProperty("java.vm.version") ?: ""
|
||||
builder.appendLine("Java: $javaVendor $javaVersion")
|
||||
}
|
||||
|
||||
// Java VM memory limit as set by -Xmx
|
||||
val maxMemory = try {
|
||||
Runtime.getRuntime().maxMemory() / 1024 / 1024
|
||||
} catch (ex: Throwable) { -1L }
|
||||
if (maxMemory > 0) {
|
||||
builder.append('\t')
|
||||
builder.appendLine("Max Memory: $maxMemory MB")
|
||||
}
|
||||
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Suppress("SpellCheckingInspection")
|
||||
private val winVerCommand = """
|
||||
cmd /c
|
||||
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" /v ProductName &&
|
||||
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" /v ReleaseId &&
|
||||
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" /v CurrentBuild &&
|
||||
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" /v DisplayVersion
|
||||
""".trimIndent().replace('\n', ' ')
|
||||
|
||||
/** Kludge to get the important Windows version info (no easier way than the registry AFAIK)
|
||||
* using a subprocess running reg query. Other methods would involve nasty reflection
|
||||
* to break java.util.prefs.Preferences out of its Sandbox, or JNA requiring new bindings.
|
||||
*/
|
||||
fun getWinVer(): String {
|
||||
val entries: Map<String,String> = try {
|
||||
val process = Runtime.getRuntime().exec(winVerCommand)
|
||||
process.waitFor()
|
||||
val output = process.inputStream.readAllBytes().toString(Charset.defaultCharset())
|
||||
|
||||
val goodLines = output.split('\n').mapNotNull {
|
||||
it.removeSuffix("\r").run {
|
||||
if (startsWith(" ") || startsWith("\t")) trim() else null
|
||||
}
|
||||
}
|
||||
|
||||
goodLines.map { it.split("REG_SZ") }
|
||||
.filter { it.size == 2 }
|
||||
.associate { it[0].trim() to it[1].trim() }
|
||||
} catch (ex: Throwable) { mapOf() }
|
||||
|
||||
if ("ProductName" !in entries) return ""
|
||||
|
||||
return entries["ProductName"]!! +
|
||||
((entries["DisplayVersion"] ?: entries["ReleaseId"])?.run { " Version $this" } ?: "") +
|
||||
(entries["CurrentBuild"]?.run { " (Build $this)" } ?: "")
|
||||
}
|
||||
|
||||
/** Get linux Distribution out of the /etc/os-release file (ini-style)
|
||||
* Should be safely silent on systems not supporting that file.
|
||||
*/
|
||||
fun getLinuxDistro(): String {
|
||||
val osRelease: Map<String,String> = try {
|
||||
FileHandle("/etc/os-release")
|
||||
.readString()
|
||||
.split('\n')
|
||||
.map { it.split('=') }
|
||||
.filter { it.size == 2 }
|
||||
.associate { it[0] to it[1].removeSuffix("\"").removePrefix("\"") }
|
||||
} catch (ex: Throwable) { mapOf() }
|
||||
if ("NAME" !in osRelease) return ""
|
||||
return osRelease["PRETTY_NAME"] ?: "${osRelease["NAME"]} ${osRelease["VERSION"]}"
|
||||
}
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ import java.awt.image.BufferedImage
|
||||
import java.util.*
|
||||
|
||||
|
||||
class FontDesktop : FontImplementation {
|
||||
class DesktopFont : FontImplementation {
|
||||
|
||||
private lateinit var font: Font
|
||||
private lateinit var metric: FontMetrics
|
52
desktop/src/com/unciv/app/desktop/DesktopGame.kt
Normal file
52
desktop/src/com/unciv/app/desktop/DesktopGame.kt
Normal file
@ -0,0 +1,52 @@
|
||||
package com.unciv.app.desktop
|
||||
|
||||
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration
|
||||
import com.unciv.UncivGame
|
||||
|
||||
class DesktopGame(config: Lwjgl3ApplicationConfiguration) : UncivGame() {
|
||||
|
||||
private val audio = HardenGdxAudio()
|
||||
private var discordUpdater = DiscordUpdater()
|
||||
private val turnNotifier = MultiplayerTurnNotifierDesktop()
|
||||
|
||||
init {
|
||||
config.setWindowListener(turnNotifier)
|
||||
|
||||
discordUpdater.setOnUpdate {
|
||||
|
||||
if (!isInitialized)
|
||||
return@setOnUpdate null
|
||||
|
||||
val info = DiscordGameInfo()
|
||||
val game = gameInfo
|
||||
|
||||
if (game != null) {
|
||||
info.gameTurn = game.turns
|
||||
info.gameLeader = game.getCurrentPlayerCivilization().nation.leaderName
|
||||
info.gameNation = game.getCurrentPlayerCivilization().nation.name
|
||||
}
|
||||
|
||||
return@setOnUpdate info
|
||||
|
||||
}
|
||||
|
||||
discordUpdater.startUpdates()
|
||||
}
|
||||
|
||||
override fun installAudioHooks() {
|
||||
audio.installHooks(
|
||||
musicController.getAudioLoopCallback(),
|
||||
musicController.getAudioExceptionHandler()
|
||||
)
|
||||
}
|
||||
|
||||
override fun notifyTurnStarted() {
|
||||
turnNotifier.turnStarted()
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
discordUpdater.stopUpdates()
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
}
|
@ -1,33 +1,33 @@
|
||||
package com.unciv.app.desktop
|
||||
|
||||
import club.minnced.discord.rpc.DiscordEventHandlers
|
||||
import club.minnced.discord.rpc.DiscordRPC
|
||||
import club.minnced.discord.rpc.DiscordRichPresence
|
||||
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application
|
||||
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration
|
||||
import com.badlogic.gdx.files.FileHandle
|
||||
import com.badlogic.gdx.graphics.glutils.HdpiMode
|
||||
import com.sun.jna.Native
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.UncivGameParameters
|
||||
import com.unciv.json.json
|
||||
import com.unciv.logic.files.SETTINGS_FILE_NAME
|
||||
import com.unciv.logic.files.UncivFiles
|
||||
import com.unciv.models.metadata.ScreenSize
|
||||
import com.unciv.models.metadata.WindowState
|
||||
import com.unciv.ui.components.Fonts
|
||||
import com.unciv.utils.Log
|
||||
import com.unciv.utils.debug
|
||||
import java.awt.GraphicsEnvironment
|
||||
import java.util.*
|
||||
import kotlin.concurrent.timer
|
||||
|
||||
|
||||
internal object DesktopLauncher {
|
||||
private var discordTimer: Timer? = null
|
||||
|
||||
@JvmStatic
|
||||
fun main(arg: Array<String>) {
|
||||
|
||||
// Setup Desktop logging
|
||||
Log.backend = DesktopLogBackend()
|
||||
|
||||
// Setup Desktop font
|
||||
Fonts.fontImplementation = DesktopFont()
|
||||
|
||||
// Setup Desktop saver-loader
|
||||
UncivFiles.saverLoader = DesktopSaverLoader()
|
||||
UncivFiles.preferExternalStorage = false
|
||||
|
||||
// Solves a rendering problem in specific GPUs and drivers.
|
||||
// For more info see https://github.com/yairm210/Unciv/pull/3202 and https://github.com/LWJGL/lwjgl/issues/119
|
||||
System.setProperty("org.lwjgl.opengl.Display.allowSoftwareOpenGL", "true")
|
||||
@ -69,60 +69,7 @@ internal object DesktopLauncher {
|
||||
UiElementDocsWriter().write()
|
||||
}
|
||||
|
||||
val platformSpecificHelper = PlatformSpecificHelpersDesktop(config)
|
||||
val desktopParameters = UncivGameParameters(
|
||||
cancelDiscordEvent = { discordTimer?.cancel() },
|
||||
fontImplementation = FontDesktop(),
|
||||
customFileLocationHelper = CustomFileLocationHelperDesktop(),
|
||||
crashReportSysInfo = CrashReportSysInfoDesktop(),
|
||||
platformSpecificHelper = platformSpecificHelper,
|
||||
audioExceptionHelper = HardenGdxAudio()
|
||||
)
|
||||
|
||||
val game = UncivGame(desktopParameters)
|
||||
|
||||
tryActivateDiscord(game)
|
||||
val game = DesktopGame(config)
|
||||
Lwjgl3Application(game, config)
|
||||
}
|
||||
|
||||
private fun tryActivateDiscord(game: UncivGame) {
|
||||
try {
|
||||
/*
|
||||
We try to load the Discord library manually before the instance initializes.
|
||||
This is because if there's a crash when the instance initializes on a similar line,
|
||||
it's not within the bounds of the try/catch and thus the app will crash.
|
||||
*/
|
||||
Native.load("discord-rpc", DiscordRPC::class.java)
|
||||
val handlers = DiscordEventHandlers()
|
||||
DiscordRPC.INSTANCE.Discord_Initialize("647066573147996161", handlers, true, null)
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(Thread { DiscordRPC.INSTANCE.Discord_Shutdown() })
|
||||
|
||||
discordTimer = timer(name = "Discord", daemon = true, period = 1000) {
|
||||
try {
|
||||
updateRpc(game)
|
||||
} catch (ex: Exception) {
|
||||
debug("Exception while updating Discord Rich Presence", ex)
|
||||
}
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
// This needs to be a Throwable because if we can't find the discord_rpc library, we'll get a UnsatisfiedLinkError, which is NOT an exception.
|
||||
debug("Could not initialize Discord")
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateRpc(game: UncivGame) {
|
||||
if (!game.isInitialized) return
|
||||
val presence = DiscordRichPresence()
|
||||
presence.largeImageKey = "logo" // The actual image is uploaded to the discord app / applications webpage
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -7,10 +7,14 @@ class DesktopLogBackend : DefaultLogBackend() {
|
||||
|
||||
// -ea (enable assertions) or kotlin debugging property as marker for a debug run.
|
||||
// Can easily be added to IntelliJ/Android Studio launch configuration template for all launches.
|
||||
private val release = !ManagementFactory.getRuntimeMXBean().getInputArguments().contains("-ea")
|
||||
private val release = !ManagementFactory.getRuntimeMXBean().inputArguments.contains("-ea")
|
||||
&& System.getProperty("kotlinx.coroutines.debug") == null
|
||||
|
||||
override fun isRelease(): Boolean {
|
||||
return release
|
||||
}
|
||||
|
||||
override fun getSystemInfo(): String {
|
||||
return SystemUtils.getSystemInfo()
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
package com.unciv.app.desktop
|
||||
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.unciv.logic.files.CustomFileLocationHelper
|
||||
import com.unciv.logic.files.PlatformSaverLoader
|
||||
import com.unciv.utils.Log
|
||||
import java.awt.Component
|
||||
import java.awt.EventQueue
|
||||
import java.awt.event.WindowEvent
|
||||
@ -11,17 +12,45 @@ import java.io.OutputStream
|
||||
import javax.swing.JFileChooser
|
||||
import javax.swing.JFrame
|
||||
|
||||
class CustomFileLocationHelperDesktop : CustomFileLocationHelper() {
|
||||
class DesktopSaverLoader : PlatformSaverLoader {
|
||||
|
||||
override fun saveGame(
|
||||
data: String,
|
||||
suggestedLocation: String,
|
||||
onSaved: (location: String) -> Unit,
|
||||
onError: (ex: Exception) -> Unit
|
||||
) {
|
||||
val onFileChosen = { stream: OutputStream, location: String ->
|
||||
try {
|
||||
stream.writer().use { it.write(data) }
|
||||
onSaved(location)
|
||||
} catch (ex: Exception) {
|
||||
onError(ex)
|
||||
}
|
||||
}
|
||||
|
||||
pickFile(onFileChosen, onError, JFileChooser::showSaveDialog, File::outputStream, suggestedLocation)
|
||||
|
||||
override fun createOutputStream(suggestedLocation: String, callback: (String?, OutputStream?, Exception?) -> Unit) {
|
||||
pickFile(callback, JFileChooser::showSaveDialog, File::outputStream, suggestedLocation)
|
||||
}
|
||||
|
||||
override fun createInputStream(callback: (String?, InputStream?, Exception?) -> Unit) {
|
||||
pickFile(callback, JFileChooser::showOpenDialog, File::inputStream)
|
||||
override fun loadGame(
|
||||
onLoaded: (data: String, location: String) -> Unit,
|
||||
onError: (ex: Exception) -> Unit
|
||||
) {
|
||||
val onFileChosen = { stream: InputStream, location: String ->
|
||||
try {
|
||||
val data = stream.reader().use { it.readText() }
|
||||
onLoaded(data, location)
|
||||
} catch (ex: Exception) {
|
||||
onError(ex)
|
||||
}
|
||||
}
|
||||
|
||||
pickFile(onFileChosen, onError, JFileChooser::showOpenDialog, File::inputStream)
|
||||
}
|
||||
|
||||
private fun <T> pickFile(callback: (String?, T?, Exception?) -> Unit,
|
||||
private fun <T> pickFile(onSuccess: (T, String) -> Unit,
|
||||
onError: (Exception) -> Unit,
|
||||
chooseAction: (JFileChooser, Component) -> Int,
|
||||
createValue: (File) -> T,
|
||||
suggestedLocation: String? = null) {
|
||||
@ -47,13 +76,13 @@ class CustomFileLocationHelperDesktop : CustomFileLocationHelper() {
|
||||
frame.dispose()
|
||||
|
||||
if (result == JFileChooser.CANCEL_OPTION) {
|
||||
callback(null, null, null)
|
||||
return@invokeLater
|
||||
} else {
|
||||
val value = createValue(fileChooser.selectedFile)
|
||||
callback(fileChooser.selectedFile.absolutePath, value, null)
|
||||
onSuccess(value, fileChooser.selectedFile.absolutePath)
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
callback(null, null, ex)
|
||||
onError(ex)
|
||||
}
|
||||
}
|
||||
}
|
76
desktop/src/com/unciv/app/desktop/DiscordUpdater.kt
Normal file
76
desktop/src/com/unciv/app/desktop/DiscordUpdater.kt
Normal file
@ -0,0 +1,76 @@
|
||||
package com.unciv.app.desktop
|
||||
|
||||
import club.minnced.discord.rpc.DiscordEventHandlers
|
||||
import club.minnced.discord.rpc.DiscordRPC
|
||||
import club.minnced.discord.rpc.DiscordRichPresence
|
||||
import com.sun.jna.Native
|
||||
import com.unciv.utils.debug
|
||||
import java.util.*
|
||||
import kotlin.concurrent.timer
|
||||
|
||||
class DiscordGameInfo(
|
||||
var gameNation: String = "",
|
||||
var gameLeader: String = "",
|
||||
var gameTurn: Int = 0
|
||||
)
|
||||
|
||||
class DiscordUpdater {
|
||||
|
||||
private var onUpdate: (() -> DiscordGameInfo?)? = null
|
||||
private var updateTimer: Timer? = null
|
||||
|
||||
fun setOnUpdate(callback: () -> DiscordGameInfo?) {
|
||||
onUpdate = callback
|
||||
}
|
||||
|
||||
fun startUpdates() {
|
||||
try {
|
||||
/*
|
||||
We try to load the Discord library manually before the instance initializes.
|
||||
This is because if there's a crash when the instance initializes on a similar line,
|
||||
it's not within the bounds of the try/catch and thus the app will crash.
|
||||
*/
|
||||
Native.load("discord-rpc", DiscordRPC::class.java)
|
||||
val handlers = DiscordEventHandlers()
|
||||
DiscordRPC.INSTANCE.Discord_Initialize("647066573147996161", handlers, true, null)
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(Thread { DiscordRPC.INSTANCE.Discord_Shutdown() })
|
||||
|
||||
updateTimer = timer(name = "Discord", daemon = true, period = 1000) {
|
||||
try {
|
||||
updateRpc()
|
||||
} catch (ex: Exception) {
|
||||
debug("Exception while updating Discord Rich Presence", ex)
|
||||
}
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
// This needs to be a Throwable because if we can't find the discord_rpc library, we'll get a UnsatisfiedLinkError, which is NOT an exception.
|
||||
debug("Could not initialize Discord")
|
||||
}
|
||||
}
|
||||
|
||||
fun stopUpdates() {
|
||||
updateTimer?.cancel()
|
||||
}
|
||||
|
||||
private fun updateRpc() {
|
||||
|
||||
if (onUpdate == null)
|
||||
return
|
||||
|
||||
val info = onUpdate!!.invoke() ?: return
|
||||
|
||||
val presence = DiscordRichPresence()
|
||||
presence.largeImageKey = "logo" // The actual image is uploaded to the discord app / applications webpage
|
||||
|
||||
if (info.gameLeader.isNotEmpty() && info.gameNation.isNotEmpty()) {
|
||||
presence.details = "${info.gameLeader} of ${info.gameNation}"
|
||||
presence.details = "Turn ${info.gameTurn}"
|
||||
}
|
||||
|
||||
DiscordRPC.INSTANCE.Discord_UpdatePresence(presence)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package com.unciv.app.desktop
|
||||
|
||||
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration
|
||||
import com.unciv.ui.components.GeneralPlatformSpecificHelpers
|
||||
|
||||
class PlatformSpecificHelpersDesktop(config: Lwjgl3ApplicationConfiguration) : GeneralPlatformSpecificHelpers {
|
||||
val turnNotifier = MultiplayerTurnNotifierDesktop()
|
||||
init {
|
||||
config.setWindowListener(turnNotifier)
|
||||
}
|
||||
|
||||
override fun notifyTurnStarted() {
|
||||
turnNotifier.turnStarted()
|
||||
}
|
||||
|
||||
/** On desktop, external is likely some document folder, while local is the game directory. We'd like to keep everything in the game directory */
|
||||
override fun shouldPreferExternalStorage(): Boolean = false
|
||||
}
|
97
desktop/src/com/unciv/app/desktop/SystemUtils.kt
Normal file
97
desktop/src/com/unciv/app/desktop/SystemUtils.kt
Normal file
@ -0,0 +1,97 @@
|
||||
package com.unciv.app.desktop
|
||||
|
||||
import com.badlogic.gdx.files.FileHandle
|
||||
import java.nio.charset.Charset
|
||||
|
||||
object SystemUtils {
|
||||
|
||||
fun getSystemInfo(): String {
|
||||
val builder = StringBuilder()
|
||||
|
||||
// Operating system
|
||||
val osName = System.getProperty("os.name") ?: "Unknown"
|
||||
val isWindows = osName.startsWith("Windows", ignoreCase = true)
|
||||
builder.append("OS: $osName")
|
||||
if (!isWindows) {
|
||||
val osInfo = listOfNotNull(System.getProperty("os.arch"), System.getProperty("os.version")).joinToString()
|
||||
if (osInfo.isNotEmpty()) builder.append(" ($osInfo)")
|
||||
}
|
||||
builder.appendLine()
|
||||
|
||||
// Specific release info
|
||||
val osRelease = if (isWindows) getWinVer() else getLinuxDistro()
|
||||
if (osRelease.isNotEmpty())
|
||||
builder.appendLine("\t$osRelease")
|
||||
|
||||
// Java runtime version
|
||||
val javaVendor: String? = System.getProperty("java.vendor")
|
||||
if (javaVendor != null) {
|
||||
val javaVersion: String = System.getProperty("java.vendor.version") ?: System.getProperty("java.vm.version") ?: ""
|
||||
builder.appendLine("Java: $javaVendor $javaVersion")
|
||||
}
|
||||
|
||||
// Java VM memory limit as set by -Xmx
|
||||
val maxMemory = try {
|
||||
Runtime.getRuntime().maxMemory() / 1024 / 1024
|
||||
} catch (ex: Throwable) { -1L }
|
||||
if (maxMemory > 0) {
|
||||
builder.append('\t')
|
||||
builder.appendLine("Max Memory: $maxMemory MB")
|
||||
}
|
||||
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
/** Kludge to get the important Windows version info (no easier way than the registry AFAIK)
|
||||
* using a subprocess running reg query. Other methods would involve nasty reflection
|
||||
* to break java.util.prefs.Preferences out of its Sandbox, or JNA requiring new bindings.
|
||||
*/
|
||||
private fun getWinVer(): String {
|
||||
val winVerCommand = """
|
||||
cmd /c
|
||||
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" /v ProductName &&
|
||||
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" /v ReleaseId &&
|
||||
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" /v CurrentBuild &&
|
||||
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" /v DisplayVersion
|
||||
""".trimIndent().replace('\n', ' ')
|
||||
|
||||
val entries: Map<String,String> = try {
|
||||
val process = Runtime.getRuntime().exec(winVerCommand)
|
||||
process.waitFor()
|
||||
val output = process.inputStream.readAllBytes().toString(Charset.defaultCharset())
|
||||
|
||||
val goodLines = output.split('\n').mapNotNull {
|
||||
it.removeSuffix("\r").run {
|
||||
if (startsWith(" ") || startsWith("\t")) trim() else null
|
||||
}
|
||||
}
|
||||
|
||||
goodLines.map { it.split("REG_SZ") }
|
||||
.filter { it.size == 2 }
|
||||
.associate { it[0].trim() to it[1].trim() }
|
||||
} catch (ex: Throwable) { mapOf() }
|
||||
|
||||
if ("ProductName" !in entries) return ""
|
||||
|
||||
return entries["ProductName"]!! +
|
||||
((entries["DisplayVersion"] ?: entries["ReleaseId"])?.run { " Version $this" } ?: "") +
|
||||
(entries["CurrentBuild"]?.run { " (Build $this)" } ?: "")
|
||||
}
|
||||
|
||||
/** Get linux Distribution out of the /etc/os-release file (ini-style)
|
||||
* Should be safely silent on systems not supporting that file.
|
||||
*/
|
||||
private fun getLinuxDistro(): String {
|
||||
val osRelease: Map<String,String> = try {
|
||||
FileHandle("/etc/os-release")
|
||||
.readString()
|
||||
.split('\n')
|
||||
.map { it.split('=') }
|
||||
.filter { it.size == 2 }
|
||||
.associate { it[0] to it[1].removeSuffix("\"").removePrefix("\"") }
|
||||
} catch (ex: Throwable) { mapOf() }
|
||||
if ("NAME" !in osRelease) return ""
|
||||
return osRelease["PRETTY_NAME"] ?: "${osRelease["NAME"]} ${osRelease["VERSION"]}"
|
||||
}
|
||||
|
||||
}
|
@ -11,7 +11,6 @@ import com.badlogic.gdx.scenes.scene2d.Actor
|
||||
import com.badlogic.gdx.scenes.scene2d.InputEvent
|
||||
import com.badlogic.gdx.scenes.scene2d.InputListener
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.UncivGameParameters
|
||||
import com.unciv.logic.files.UncivFiles
|
||||
import com.unciv.logic.multiplayer.throttle
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
@ -61,10 +60,11 @@ object FasterUIDevelopment {
|
||||
}
|
||||
|
||||
class UIDevGame : Game() {
|
||||
val game = UncivGame(UncivGameParameters(
|
||||
fontImplementation = FontDesktop()
|
||||
))
|
||||
|
||||
private val game = UncivGame()
|
||||
|
||||
override fun create() {
|
||||
Fonts.fontImplementation = FontDesktop()
|
||||
UncivGame.Current = game
|
||||
UncivGame.Current.files = UncivFiles(Gdx.files)
|
||||
game.settings = UncivGame.Current.files.getGeneralSettings()
|
||||
|
@ -17,6 +17,7 @@ import com.unciv.models.stats.Stat
|
||||
import com.unciv.models.stats.Stats
|
||||
import com.unciv.models.translations.getPlaceholderParameters
|
||||
import com.unciv.models.translations.getPlaceholderText
|
||||
import com.unciv.utils.DebugUtils
|
||||
import com.unciv.utils.Log
|
||||
import com.unciv.utils.debug
|
||||
import org.junit.Assert
|
||||
@ -53,10 +54,10 @@ class BasicTests {
|
||||
fun gameIsNotRunWithDebugModes() {
|
||||
val game = UncivGame()
|
||||
Assert.assertTrue("This test will only pass if the game is not run with debug modes",
|
||||
!game.superchargedForDebug
|
||||
&& !game.viewEntireMapForDebug
|
||||
&& game.simulateUntilTurnForDebug <= 0
|
||||
&& !game.consoleMode
|
||||
!DebugUtils.SUPERCHARGED
|
||||
&& !DebugUtils.VISIBLE_MAP
|
||||
&& DebugUtils.SIMULATE_UNTIL_TURN <= 0
|
||||
&& !game.isConsoleMode
|
||||
)
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user