-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
GameSettings.kt
388 lines (335 loc) · 15.8 KB
/
GameSettings.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
package com.unciv.models.metadata
import com.badlogic.gdx.Application.ApplicationType
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.utils.Base64Coder
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.multiplayer.FriendList
import com.unciv.models.UncivSound
import com.unciv.ui.components.fonts.FontFamilyData
import com.unciv.ui.components.fonts.Fonts
import com.unciv.ui.components.input.KeyboardBindings
import com.unciv.ui.screens.overviewscreen.EmpireOverviewCategories
import com.unciv.ui.screens.worldscreen.NotificationsScroll
import com.unciv.utils.Display
import com.unciv.utils.ScreenOrientation
import java.text.Collator
import java.time.Duration
import java.util.Locale
import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty0
class GameSettings {
/** Allows panning the map by moving the pointer to the screen edges */
var mapAutoScroll: Boolean = false
/** How fast the map pans using keyboard or with [mapAutoScroll] and mouse */
var mapPanningSpeed: Float = 6f
var showWorkedTiles: Boolean = false
var showResourcesAndImprovements: Boolean = true
var showTileYields: Boolean = false
var showUnitMovements: Boolean = false
var showSettlersSuggestedCityLocations: Boolean = true
var checkForDueUnits: Boolean = true
var autoUnitCycle: Boolean = true
var singleTapMove: Boolean = false
var longTapMove: Boolean = true
var language: String = Constants.english
@Transient
var locale: Locale? = null
var screenSize: ScreenSize = ScreenSize.Small
var screenMode: Int = 0
var tutorialsShown = HashSet<String>()
var tutorialTasksCompleted = HashSet<String>()
var soundEffectsVolume = 0.5f
var citySoundsVolume = 0.5f
var musicVolume = 0.5f
var voicesVolume = 0.5f
var pauseBetweenTracks = 10
var turnsBetweenAutosaves = 1
var tileSet: String = Constants.defaultTileset
var unitSet: String? = Constants.defaultUnitset
var skin: String = Constants.defaultSkin
var showTutorials: Boolean = true
var autoAssignCityProduction: Boolean = true
/** This set of construction names has two effects:
* * Matching constructions are no longer candidates for [autoAssignCityProduction]
* * Matching constructions are offered in a separate 'Disabled' category in CityScreen
*/
var disabledAutoAssignConstructions = HashSet<String>()
var autoBuildingRoads: Boolean = true
var automatedWorkersReplaceImprovements = true
var automatedUnitsMoveOnTurnStart: Boolean = false
var automatedUnitsCanUpgrade: Boolean = false
var automatedUnitsChoosePromotions: Boolean = false
var citiesAutoBombardAtEndOfTurn: Boolean = false
var showMinimap: Boolean = true
var minimapSize: Int = 6 // default corresponds to 15% screen space
var unitIconOpacity = 1f // default corresponds to fully opaque
val showPixelUnits: Boolean get() = unitSet != null
var showPixelImprovements: Boolean = true
var continuousRendering = false
var orderTradeOffersByAmount = true
var confirmNextTurn = false
var windowState = WindowState()
var isFreshlyCreated = false
var visualMods = HashSet<String>()
var useDemographics: Boolean = false
var showZoomButtons: Boolean = false
var forbidPopupClickBehindToClose: Boolean = false
var notificationsLogMaxTurns = 5
var showAutosaves: Boolean = false
var androidCutout: Boolean = false
var androidHideSystemUi = true
var multiplayer = GameSettingsMultiplayer()
var autoPlay = GameSettingsAutoPlay()
// This is a string not an enum so if tabs change it won't screw up the json serialization
//TODO remove line in a future update
var lastOverviewPage = EmpireOverviewCategories.Cities.name
/** Holds EmpireOverviewScreen per-page persistable states */
val overview = OverviewPersistableData()
/** Orientation for mobile platforms */
var displayOrientation = ScreenOrientation.Landscape
/** Saves the last successful new game's setup */
var lastGameSetup: GameSetupInfo? = null
var fontFamilyData: FontFamilyData = FontFamilyData.default
var fontSizeMultiplier: Float = 1f
var enableEasterEggs: Boolean = true
/** Maximum zoom-out of the map - performance heavy */
var maxWorldZoomOut = 2f
var keyBindings = KeyboardBindings()
/** NotificationScroll on Word Screen visibility control - mapped to [NotificationsScroll.UserSetting] enum */
// Defaulting this to "" - and implement the fallback only in NotificationsScroll leads to Options popup and actual effect being in disagreement!
var notificationScroll: String = NotificationsScroll.UserSetting.default().name
/** If on, selected notifications are drawn enlarged with wider padding */
var enlargeSelectedNotification = true
/** Whether the Nation Picker shows icons only or the horizontal "civBlocks" with leader/nation name */
var nationPickerListMode = NationPickerListMode.List
/** Size of automatic display of UnitSet art in Civilopedia - 0 to disable */
var pediaUnitArtSize = 0f
/** Don't close developer console after a successful command */
var keepConsoleOpen = false
/** Persist the history of successful developer console commands */
val consoleCommandHistory = ArrayList<String>()
/** used to migrate from older versions of the settings */
var version: Int? = null
init {
// 26 = Android Oreo. Versions below may display permanent icon in notification bar.
if (Gdx.app?.type == ApplicationType.Android && Gdx.app.version < 26) {
multiplayer.turnCheckerPersistentNotificationEnabled = false
}
}
//region <Methods>
fun save() {
if (Gdx.app == null) return // Simulation mode from ConsoleLauncher
refreshWindowSize()
UncivGame.Current.files.setGeneralSettings(this)
}
fun refreshWindowSize() {
if (isFreshlyCreated || Gdx.app.type != ApplicationType.Desktop) return
if (!Display.hasUserSelectableSize(screenMode)) return
windowState = WindowState.current()
}
fun addCompletedTutorialTask(tutorialTask: String): Boolean {
if (!tutorialTasksCompleted.add(tutorialTask)) return false
UncivGame.Current.isTutorialTaskCollapsed = false
save()
return true
}
fun updateLocaleFromLanguage() {
val bannedCharacters = listOf(' ', '_', '-', '(', ')') // Things not to have in enum names
val languageName = language.filterNot { it in bannedCharacters }
locale = try {
val code = LocaleCode.valueOf(languageName)
Locale(code.language, code.country)
} catch (_: Exception) {
Locale.getDefault()
}
}
fun getFontSize(): Int {
return (Fonts.ORIGINAL_FONT_SIZE * fontSizeMultiplier).toInt()
}
private fun getCurrentLocale(): Locale {
if (locale == null)
updateLocaleFromLanguage()
return locale!!
}
fun getCollatorFromLocale(): Collator {
return Collator.getInstance(getCurrentLocale())
}
//endregion
//region <Nested classes>
/**
* Knowledge on Window "state", limited.
* - Size: Saved
* - Iconified, Maximized: Not saved
* - Position / Multimonitor display choice: Not saved
*
* Note: Useful on desktop only, on Android we do not explicitly support `Activity.isInMultiWindowMode` returning true.
* (On Android Display.hasUserSelectableSize will return false, and AndroidLauncher & co ignore it)
*
* Open to future enhancement - but:
* retrieving a valid position from our upstream libraries while the window is maximized or iconified has proven tricky so far.
*/
data class WindowState(val width: Int = 900, val height: Int = 600) {
constructor(bounds: java.awt.Rectangle) : this(bounds.width, bounds.height)
companion object {
/** Our choice of minimum window width */
const val minimumWidth = 120
/** Our choice of minimum window height */
const val minimumHeight = 80
fun current() = WindowState(Gdx.graphics.width, Gdx.graphics.height)
}
/**
* Constrains the dimensions of `this` [WindowState] to be within [minimumWidth] x [minimumHeight] to [maximumWidth] x [maximumHeight].
* @param maximumWidth defaults to unlimited
* @param maximumHeight defaults to unlimited
* @return `this` unchanged if it is within valid limits, otherwise a new WindowState that is.
*/
fun coerceIn(maximumWidth: Int = Int.MAX_VALUE, maximumHeight: Int = Int.MAX_VALUE): WindowState {
if (width in minimumWidth..maximumWidth && height in minimumHeight..maximumHeight)
return this
return WindowState(
width.coerceIn(minimumWidth, maximumWidth),
height.coerceIn(minimumHeight, maximumHeight)
)
}
/**
* Constrains the dimensions of `this` [WindowState] to be within [minimumWidth] x [minimumHeight] to `maximumWidth` x `maximumHeight`.
* @param maximumWindowBounds provides maximum sizes
* @return `this` unchanged if it is within valid limits, otherwise a new WindowState that is.
* @see coerceIn
*/
fun coerceIn(maximumWindowBounds: java.awt.Rectangle) =
coerceIn(maximumWindowBounds.width, maximumWindowBounds.height)
}
enum class ScreenSize(
@Suppress("unused") // Actual width determined by screen aspect ratio, this as comment only
val virtualWidth: Float,
val virtualHeight: Float
) {
Tiny(750f,500f),
Small(900f,600f),
Medium(1050f,700f),
Large(1200f,800f),
Huge(1500f,1000f)
}
enum class NationPickerListMode { Icons, List }
/** Map Unciv language key to Java locale, for the purpose of getting a Collator for sorting.
* - Effect depends on the Java libraries and may not always conform to expectations.
* If in doubt, debug and see what Locale instance you get and compare its properties with `Locale.getDefault()`.
* (`Collator.getInstance(LocaleCode.*.run { Locale(language, country) }) to Collator.getInstance())`, drill to both `rules`, compare hashes - if equal and other properties equal, then Java doesn't know your Language))
* @property name same as translation file name with ' ', '_', '-', '(', ')' removed
* @property language ISO 639-1 code for the language
* @property country ISO 3166 code for the nation this is predominantly spoken in
* @property trueLanguage If set, used instead of language to trick Java into supplying a close-enough collator (a no-match would otherwise give us the default collator, not a collator for a partial match)
*/
enum class LocaleCode(val language: String, val country: String, val trueLanguage: String? = null) {
Afrikaans("af", "ZA"),
Arabic("ar", "IQ"),
Belarusian("be", "BY"),
Bosnian("bs", "BA"),
BrazilianPortuguese("pt", "BR"),
Bulgarian("bg", "BG"),
Catalan("ca", "ES"),
Croatian("hr", "HR"),
Czech("cs", "CZ"),
Danish("da", "DK"),
Dutch("nl", "NL"),
English("en", "US"),
Estonian("et", "EE"),
Finnish("fi", "FI"),
French("fr", "FR"),
German("de", "DE"),
Greek("el", "GR"),
Hindi("hi", "IN"),
Hungarian("hu", "HU"),
Indonesian("in", "ID"),
Italian("it", "IT"),
Japanese("ja", "JP"),
Korean("ko", "KR"),
Latin("la", "IT"),
Latvian("lv", "LV"),
Lithuanian("lt", "LT"),
Malay("ms", "MY"),
Norwegian("no", "NO"),
NorwegianNynorsk("nn", "NO"),
PersianPinglishDIN("fa", "IR"), // These might just fall back to default
PersianPinglishUN("fa", "IR"),
Polish("pl", "PL"),
Portuguese("pt", "PT"),
Romanian("ro", "RO"),
Russian("ru", "RU"),
Rusyn("uk", "UA", "rus"), // No specific locale for rus exists, so use closest for collator
Serbian("sr", "RS"),
SimplifiedChinese("zh", "CN"),
Slovak("sk", "SK"),
Spanish("es", "ES"),
Swedish("sv", "SE"),
Thai("th", "TH"),
TraditionalChinese("zh", "TW"),
Turkish("tr", "TR"),
Ukrainian("uk", "UA"),
Vietnamese("vi", "VN"),
Zulu("zu", "ZA")
}
//endregion
//region Multiplayer-specific
class GameSettingsMultiplayer {
var userId = ""
var passwords = mutableMapOf<String, String>()
@Suppress("unused") // @GGuenni knows what he intended with this field
var userName: String = ""
var server = Constants.uncivXyzServer
var friendList: MutableList<FriendList.Friend> = mutableListOf()
var turnCheckerEnabled = true
var turnCheckerPersistentNotificationEnabled = true
var turnCheckerDelay: Duration = Duration.ofMinutes(5)
var statusButtonInSinglePlayer = false
var currentGameRefreshDelay: Duration = Duration.ofSeconds(10)
var allGameRefreshDelay: Duration = Duration.ofMinutes(5)
var currentGameTurnNotificationSound: UncivSound = UncivSound.Silent
var otherGameTurnNotificationSound: UncivSound = UncivSound.Silent
var hideDropboxWarning = false
fun getAuthHeader(): String {
val serverPassword = passwords[server] ?: ""
val preEncodedAuthValue = "$userId:$serverPassword"
return "Basic ${Base64Coder.encodeString(preEncodedAuthValue)}"
}
}
class GameSettingsAutoPlay {
var showAutoPlayButton: Boolean = false
var autoPlayUntilEnd: Boolean = false
var autoPlayMaxTurns = 10
var fullAutoPlayAI: Boolean = true
var autoPlayMilitary: Boolean = true
var autoPlayCivilian: Boolean = true
var autoPlayEconomy: Boolean = true
var autoPlayTechnology: Boolean = true
var autoPlayPolicies: Boolean = true
var autoPlayReligion: Boolean = true
var autoPlayDiplomacy: Boolean = true
}
@Suppress("SuspiciousCallableReferenceInLambda") // By @Azzurite, safe as long as that warning below is followed
enum class GameSetting(
val kClass: KClass<*>,
private val propertyGetter: (GameSettings) -> KMutableProperty0<*>
) {
// Uncomment these once they are refactored to send events on change
// MULTIPLAYER_USER_ID(String::class, { it.multiplayer::userId }),
// MULTIPLAYER_SERVER(String::class, { it.multiplayer::server }),
// MULTIPLAYER_STATUSBUTTON_IN_SINGLEPLAYER(Boolean::class, { it.multiplayer::statusButtonInSinglePlayer }),
// MULTIPLAYER_TURN_CHECKER_ENABLED(Boolean::class, { it.multiplayer::turnCheckerEnabled }),
// MULTIPLAYER_TURN_CHECKER_PERSISTENT_NOTIFICATION_ENABLED(Boolean::class, { it.multiplayer::turnCheckerPersistentNotificationEnabled }),
// MULTIPLAYER_HIDE_DROPBOX_WARNING(Boolean::class, { it.multiplayer::hideDropboxWarning }),
MULTIPLAYER_TURN_CHECKER_DELAY(Duration::class, { it.multiplayer::turnCheckerDelay }),
MULTIPLAYER_CURRENT_GAME_REFRESH_DELAY(Duration::class, { it.multiplayer::currentGameRefreshDelay }),
MULTIPLAYER_ALL_GAME_REFRESH_DELAY(Duration::class, { it.multiplayer::allGameRefreshDelay }),
MULTIPLAYER_CURRENT_GAME_TURN_NOTIFICATION_SOUND(UncivSound::class, { it.multiplayer::currentGameTurnNotificationSound }),
MULTIPLAYER_OTHER_GAME_TURN_NOTIFICATION_SOUND(UncivSound::class, { it.multiplayer::otherGameTurnNotificationSound });
/** **Warning:** It is the obligation of the caller to select the same type [T] that the [kClass] of this property has */
fun <T> getProperty(settings: GameSettings): KMutableProperty0<T> {
@Suppress("UNCHECKED_CAST")
return propertyGetter(settings) as KMutableProperty0<T>
}
}
//endregion
}