mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-04 15:27:50 +07:00
Add simple logging solution (#6952)
* Add simple logging solution * Fix Android compilation For some reason I stashed this and didn't unstash. * Add better logging explanation
This commit is contained in:
@ -11,6 +11,7 @@ import com.unciv.UncivGame
|
||||
import com.unciv.UncivGameParameters
|
||||
import com.unciv.logic.GameSaver
|
||||
import com.unciv.ui.utils.Fonts
|
||||
import com.unciv.utils.Log
|
||||
import java.io.File
|
||||
|
||||
open class AndroidLauncher : AndroidApplication() {
|
||||
@ -19,6 +20,7 @@ open class AndroidLauncher : AndroidApplication() {
|
||||
private var deepLinkedMultiplayerGame: String? = null
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Log.backend = AndroidLogBackend()
|
||||
customSaveLocationHelper = CustomSaveLocationHelperAndroid(this)
|
||||
MultiplayerTurnCheckWorker.createNotificationChannels(applicationContext)
|
||||
|
||||
|
35
android/src/com/unciv/app/AndroidLogBackend.kt
Normal file
35
android/src/com/unciv/app/AndroidLogBackend.kt
Normal file
@ -0,0 +1,35 @@
|
||||
package com.unciv.app
|
||||
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import com.unciv.utils.LogBackend
|
||||
import com.unciv.utils.Tag
|
||||
|
||||
private const val TAG_MAX_LENGTH = 23
|
||||
|
||||
class AndroidLogBackend : LogBackend {
|
||||
|
||||
override fun debug(tag: Tag, curThreadName: String, msg: String) {
|
||||
Log.d(toAndroidTag(tag), "[$curThreadName] $msg")
|
||||
}
|
||||
|
||||
override fun error(tag: Tag, curThreadName: String, msg: String) {
|
||||
Log.e(toAndroidTag(tag), "[$curThreadName] $msg")
|
||||
}
|
||||
|
||||
override fun isRelease(): Boolean {
|
||||
return !BuildConfig.DEBUG
|
||||
}
|
||||
}
|
||||
|
||||
private fun toAndroidTag(tag: Tag): String {
|
||||
// This allows easy filtering of logcat by tag "Unciv"
|
||||
val withUncivPrefix = if (tag.name.contains("unciv", true)) tag.name else "Unciv ${tag.name}"
|
||||
|
||||
// Limit was removed in Nougat
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N || tag.name.length <= TAG_MAX_LENGTH) {
|
||||
withUncivPrefix
|
||||
} else {
|
||||
withUncivPrefix.substring(0, TAG_MAX_LENGTH)
|
||||
}
|
||||
}
|
@ -28,6 +28,7 @@ import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.multiplayer.LoadDeepLinkScreen
|
||||
import com.unciv.ui.multiplayer.MultiplayerHelpers
|
||||
import com.unciv.ui.popup.Popup
|
||||
import com.unciv.utils.debug
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.*
|
||||
|
||||
@ -66,10 +67,6 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
|
||||
*/
|
||||
var simulateUntilTurnForDebug: Int = 0
|
||||
|
||||
/** Console log battles
|
||||
*/
|
||||
val alertBattle = false
|
||||
|
||||
lateinit var worldScreen: WorldScreen
|
||||
private set
|
||||
fun getWorldScreenOrNull() = if (this::worldScreen.isInitialized) worldScreen else null
|
||||
@ -124,10 +121,10 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
|
||||
Gdx.graphics.isContinuousRendering = settings.continuousRendering
|
||||
|
||||
launchCrashHandling("LoadJSON") {
|
||||
RulesetCache.loadRulesets(printOutput = true)
|
||||
RulesetCache.loadRulesets()
|
||||
translations.tryReadTranslationForCurrentLanguage()
|
||||
translations.loadPercentageCompleteOfLanguages()
|
||||
TileSetCache.loadTileSetConfigs(printOutput = true)
|
||||
TileSetCache.loadTileSetConfigs()
|
||||
|
||||
if (settings.multiplayer.userId.isEmpty()) { // assign permanent user id
|
||||
settings.multiplayer.userId = UUID.randomUUID().toString()
|
||||
@ -178,12 +175,18 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
|
||||
*/
|
||||
fun resetToWorldScreen(newWorldScreen: WorldScreen? = null) {
|
||||
if (newWorldScreen != null) {
|
||||
debug("Reset to new WorldScreen, gameId: %s, turn: %s, curCiv: %s",
|
||||
newWorldScreen.gameInfo.gameId, newWorldScreen.gameInfo.turns, newWorldScreen.gameInfo.currentPlayer)
|
||||
val oldWorldScreen = getWorldScreenOrNull()
|
||||
worldScreen = newWorldScreen
|
||||
// setScreen disposes the current screen, but the old world screen is not the current screen, so need to dispose here
|
||||
if (screen != oldWorldScreen) {
|
||||
oldWorldScreen?.dispose()
|
||||
}
|
||||
} else {
|
||||
val oldWorldScreen = getWorldScreenOrNull()!!
|
||||
debug("Reset to old WorldScreen, gameId: %s, turn: %s, curCiv: %s",
|
||||
oldWorldScreen.gameInfo.gameId, oldWorldScreen.gameInfo.turns, oldWorldScreen.gameInfo.currentPlayer)
|
||||
}
|
||||
setScreen(worldScreen)
|
||||
worldScreen.shouldUpdate = true // This can set the screen to the policy picker or tech picker screen, so the input processor must come before
|
||||
@ -264,7 +267,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
|
||||
val threadList = Array(numThreads) { _ -> Thread() }
|
||||
Thread.enumerate(threadList)
|
||||
threadList.filter { it !== Thread.currentThread() && it.name != "DestroyJavaVM" }.forEach {
|
||||
println(" Thread ${it.name} still running in UncivGame.dispose().")
|
||||
debug("Thread %s still running in UncivGame.dispose().", it.name)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package com.unciv.logic
|
||||
|
||||
import com.unciv.Constants
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.utils.debug
|
||||
import com.unciv.logic.BackwardCompatibility.guaranteeUnitPromotions
|
||||
import com.unciv.logic.BackwardCompatibility.migrateBarbarianCamps
|
||||
import com.unciv.logic.BackwardCompatibility.migrateSeenImprovements
|
||||
@ -209,7 +210,7 @@ class GameInfo {
|
||||
if (currentPlayerIndex == 0) {
|
||||
turns++
|
||||
if (UncivGame.Current.simulateUntilTurnForDebug != 0)
|
||||
println("Starting simulation of turn $turns")
|
||||
debug("Starting simulation of turn %s", turns)
|
||||
}
|
||||
thisPlayer = civilizations[currentPlayerIndex]
|
||||
thisPlayer.startTurn()
|
||||
|
@ -13,6 +13,7 @@ import com.unciv.models.metadata.isMigrationNecessary
|
||||
import com.unciv.ui.crashhandling.launchCrashHandling
|
||||
import com.unciv.ui.crashhandling.postCrashHandlingRunnable
|
||||
import com.unciv.ui.saves.Gzip
|
||||
import com.unciv.utils.Log
|
||||
import kotlinx.coroutines.Job
|
||||
import java.io.File
|
||||
|
||||
@ -178,8 +179,7 @@ class GameSaver(
|
||||
// I'm not sure of the circumstances,
|
||||
// but some people were getting null settings, even though the file existed??? Very odd.
|
||||
// ...Json broken or otherwise unreadable is the only possible reason.
|
||||
println("Error reading settings file: ${ex.localizedMessage}")
|
||||
println(" cause: ${ex.cause}")
|
||||
Log.error("Error reading settings file", ex)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package com.unciv.logic
|
||||
|
||||
import com.unciv.Constants
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.utils.debug
|
||||
import com.unciv.logic.civilization.*
|
||||
import com.unciv.logic.map.TileInfo
|
||||
import com.unciv.logic.map.TileMap
|
||||
@ -20,12 +21,11 @@ import kotlin.collections.HashSet
|
||||
|
||||
object GameStarter {
|
||||
// temporary instrumentation while tuning/debugging
|
||||
private const val consoleOutput = false
|
||||
private const val consoleTimings = false
|
||||
|
||||
fun startNewGame(gameSetupInfo: GameSetupInfo): GameInfo {
|
||||
if (consoleOutput || consoleTimings)
|
||||
println("\nGameStarter run with parameters ${gameSetupInfo.gameParameters}, map ${gameSetupInfo.mapParameters}")
|
||||
if (consoleTimings)
|
||||
debug("\nGameStarter run with parameters %s, map %s", gameSetupInfo.gameParameters, gameSetupInfo.mapParameters)
|
||||
|
||||
val gameInfo = GameInfo()
|
||||
lateinit var tileMap: TileMap
|
||||
@ -133,7 +133,7 @@ object GameStarter {
|
||||
val startNanos = System.nanoTime()
|
||||
action()
|
||||
val delta = System.nanoTime() - startNanos
|
||||
println("GameStarter.$text took ${delta/1000000L}.${(delta/10000L).rem(100)}ms")
|
||||
debug("GameStarter.%s took %s.%sms", text, delta/1000000L, (delta/10000L).rem(100))
|
||||
}
|
||||
|
||||
private fun addPlayerIntros(gameInfo: GameInfo) {
|
||||
@ -333,7 +333,7 @@ object GameStarter {
|
||||
var unit = unitParam // We want to change it and this is the easiest way to do so
|
||||
if (unit == Constants.eraSpecificUnit) unit = eraUnitReplacement
|
||||
if (unit == Constants.settler && Constants.settler !in ruleSet.units) {
|
||||
val buildableSettlerLikeUnits =
|
||||
val buildableSettlerLikeUnits =
|
||||
settlerLikeUnits.filter {
|
||||
it.value.isBuildable(civ)
|
||||
&& it.value.isCivilian()
|
||||
@ -352,7 +352,7 @@ object GameStarter {
|
||||
return civ.getEquivalentUnit(unit).name
|
||||
}
|
||||
|
||||
// City states should only spawn with one settler regardless of difficulty, but this may be disabled in mods
|
||||
// City states should only spawn with one settler regardless of difficulty, but this may be disabled in mods
|
||||
if (civ.isCityState() && !ruleSet.modOptions.uniques.contains(ModOptionsConstants.allowCityStatesSpawnUnits)) {
|
||||
val startingSettlers = startingUnits.filter { settlerLikeUnits.contains(it) }
|
||||
|
||||
|
@ -14,11 +14,10 @@ import com.unciv.logic.map.TileInfo
|
||||
import com.unciv.models.ruleset.tile.Terrain
|
||||
import com.unciv.models.ruleset.tile.TileImprovement
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.utils.Log
|
||||
import com.unciv.utils.debug
|
||||
|
||||
private object WorkerAutomationConst {
|
||||
/** Controls detailed logging of decisions to the console -Turn off for release builds! */
|
||||
const val consoleOutput = false
|
||||
|
||||
/** BFS max size is determined by the aerial distance of two cities to connect, padded with this */
|
||||
// two tiles longer than the distance to the nearest connected city should be enough as the 'reach' of a BFS is increased by blocked tiles
|
||||
const val maxBfsReachPadding = 2
|
||||
@ -59,12 +58,12 @@ class WorkerAutomation(
|
||||
}.sortedBy {
|
||||
it.getCenterTile().aerialDistanceTo(civInfo.getCapital()!!.getCenterTile())
|
||||
}.toList()
|
||||
if (WorkerAutomationConst.consoleOutput) {
|
||||
println("WorkerAutomation citiesThatNeedConnecting for ${civInfo.civName} turn $cachedForTurn:")
|
||||
if (Log.shouldLog()) {
|
||||
debug("WorkerAutomation citiesThatNeedConnecting for ${civInfo.civName} turn $cachedForTurn:")
|
||||
if (result.isEmpty())
|
||||
println("\tempty")
|
||||
debug("\tempty")
|
||||
else result.forEach {
|
||||
println("\t${it.name}")
|
||||
debug("\t${it.name}")
|
||||
}
|
||||
}
|
||||
result
|
||||
@ -76,12 +75,12 @@ class WorkerAutomation(
|
||||
.filter { it.isCapital() || it.cityStats.isConnectedToCapital(bestRoadAvailable) }
|
||||
.map { it.getCenterTile() }
|
||||
.toList()
|
||||
if (WorkerAutomationConst.consoleOutput) {
|
||||
println("WorkerAutomation tilesOfConnectedCities for ${civInfo.civName} turn $cachedForTurn:")
|
||||
if (Log.shouldLog()) {
|
||||
debug("WorkerAutomation tilesOfConnectedCities for ${civInfo.civName} turn $cachedForTurn:")
|
||||
if (result.isEmpty())
|
||||
println("\tempty")
|
||||
debug("\tempty")
|
||||
else result.forEach {
|
||||
println("\t$it") // ${it.getCity()?.name} included in TileInfo toString()
|
||||
debug("\t$it") // ${it.getCity()?.name} included in TileInfo toString()
|
||||
}
|
||||
}
|
||||
result
|
||||
@ -135,8 +134,7 @@ class WorkerAutomation(
|
||||
}
|
||||
|
||||
if (tileToWork != currentTile) {
|
||||
if (WorkerAutomationConst.consoleOutput)
|
||||
println("WorkerAutomation: ${unit.label()} -> head towards $tileToWork")
|
||||
debug("WorkerAutomation: %s -> head towards %s", unit.label(), tileToWork)
|
||||
val reachedTile = unit.movement.headTowards(tileToWork)
|
||||
if (reachedTile != currentTile) unit.doAction() // otherwise, we get a situation where the worker is automated, so it tries to move but doesn't, then tries to automate, then move, etc, forever. Stack overflow exception!
|
||||
return
|
||||
@ -144,8 +142,7 @@ class WorkerAutomation(
|
||||
|
||||
if (currentTile.improvementInProgress == null && currentTile.isLand
|
||||
&& tileCanBeImproved(unit, currentTile)) {
|
||||
if (WorkerAutomationConst.consoleOutput)
|
||||
println("WorkerAutomation: ${unit.label()} -> start improving $currentTile")
|
||||
debug("WorkerAutomation: ${unit.label()} -> start improving $currentTile")
|
||||
return currentTile.startWorkingOnImprovement(chooseImprovement(unit, currentTile)!!, civInfo, unit)
|
||||
}
|
||||
|
||||
@ -164,15 +161,13 @@ class WorkerAutomation(
|
||||
.firstOrNull { unit.movement.canReach(it.getCenterTile()) } //goto most undeveloped city
|
||||
|
||||
if (mostUndevelopedCity != null && mostUndevelopedCity != currentTile.owningCity) {
|
||||
if (WorkerAutomationConst.consoleOutput)
|
||||
println("WorkerAutomation: ${unit.label()} -> head towards undeveloped city ${mostUndevelopedCity.name}")
|
||||
debug("WorkerAutomation: %s -> head towards undeveloped city %s", unit.label(), mostUndevelopedCity.name)
|
||||
val reachedTile = unit.movement.headTowards(mostUndevelopedCity.getCenterTile())
|
||||
if (reachedTile != currentTile) unit.doAction() // since we've moved, maybe we can do something here - automate
|
||||
return
|
||||
}
|
||||
|
||||
if (WorkerAutomationConst.consoleOutput)
|
||||
println("WorkerAutomation: ${unit.label()} -> nothing to do")
|
||||
debug("WorkerAutomation: %s -> nothing to do", unit.label())
|
||||
unit.civInfo.addNotification("${unit.shortDisplayName()} has no work to do.", currentTile.position, unit.name, "OtherIcons/Sleep")
|
||||
|
||||
// Idle CS units should wander so they don't obstruct players so much
|
||||
@ -236,15 +231,14 @@ class WorkerAutomation(
|
||||
val improvement = bestRoadAvailable.improvement(ruleSet)!!
|
||||
tileToConstructRoadOn.startWorkingOnImprovement(improvement, civInfo, unit)
|
||||
}
|
||||
if (WorkerAutomationConst.consoleOutput)
|
||||
println("WorkerAutomation: ${unit.label()} -> connect city ${bfs.startingPoint.getCity()?.name} to ${cityTile.getCity()!!.name} on $tileToConstructRoadOn")
|
||||
debug("WorkerAutomation: %s -> connect city %s to %s on %s",
|
||||
unit.label(), bfs.startingPoint.getCity()?.name, cityTile.getCity()!!.name, tileToConstructRoadOn)
|
||||
return true
|
||||
}
|
||||
if (bfs.hasEnded()) break
|
||||
bfs.nextStep()
|
||||
}
|
||||
if (WorkerAutomationConst.consoleOutput)
|
||||
println("WorkerAutomation: ${unit.label()} -> connect city ${bfs.startingPoint.getCity()?.name} failed at BFS size ${bfs.size()}")
|
||||
debug("WorkerAutomation: ${unit.label()} -> connect city ${bfs.startingPoint.getCity()?.name} failed at BFS size ${bfs.size()}")
|
||||
}
|
||||
|
||||
return false
|
||||
|
@ -3,6 +3,7 @@ package com.unciv.logic.battle
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.unciv.Constants
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.utils.debug
|
||||
import com.unciv.logic.city.CityInfo
|
||||
import com.unciv.logic.civilization.*
|
||||
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
|
||||
@ -75,10 +76,7 @@ object Battle {
|
||||
}
|
||||
|
||||
fun attack(attacker: ICombatant, defender: ICombatant) {
|
||||
if (UncivGame.Current.alertBattle) {
|
||||
println(attacker.getCivInfo().civName + " " + attacker.getName() + " attacked " +
|
||||
defender.getCivInfo().civName + " " + defender.getName())
|
||||
}
|
||||
debug("%s %s attacked %s %s", attacker.getCivInfo().civName, attacker.getName(), defender.getCivInfo().civName, defender.getName())
|
||||
val attackedTile = defender.getTile()
|
||||
if (attacker is MapUnitCombatant) {
|
||||
attacker.unit.attacksSinceTurnStart.add(Vector2(attackedTile.position))
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
import com.unciv.Constants
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.utils.debug
|
||||
import com.unciv.logic.battle.Battle
|
||||
import com.unciv.logic.civilization.CivilizationInfo
|
||||
import com.unciv.logic.civilization.NotificationIcon
|
||||
@ -32,7 +33,7 @@ class CityInfoConquestFunctions(val city: CityInfo){
|
||||
}
|
||||
|
||||
private fun destroyBuildingsOnCapture() {
|
||||
city.apply {
|
||||
city.apply {
|
||||
// Possibly remove other buildings
|
||||
for (building in cityConstructions.getBuiltBuildings()) {
|
||||
when {
|
||||
@ -49,7 +50,7 @@ class CityInfoConquestFunctions(val city: CityInfo){
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun removeBuildingsOnMoveToCiv(oldCiv: CivilizationInfo) {
|
||||
city.apply {
|
||||
// Remove all buildings provided for free to this city
|
||||
@ -60,7 +61,7 @@ class CityInfoConquestFunctions(val city: CityInfo){
|
||||
// Remove all buildings provided for free from here to other cities (e.g. CN Tower)
|
||||
for ((cityId, buildings) in cityConstructions.freeBuildingsProvidedFromThisCity) {
|
||||
val city = oldCiv.cities.firstOrNull { it.id == cityId } ?: continue
|
||||
println("Removing buildings $buildings from city ${city.name}")
|
||||
debug("Removing buildings %s from city %s", buildings, city.name)
|
||||
for (building in buildings) {
|
||||
city.cityConstructions.removeBuilding(building)
|
||||
}
|
||||
@ -88,9 +89,9 @@ class CityInfoConquestFunctions(val city: CityInfo){
|
||||
}
|
||||
}
|
||||
|
||||
/** Function for stuff that should happen on any capture, be it puppet, annex or liberate.
|
||||
/** Function for stuff that should happen on any capture, be it puppet, annex or liberate.
|
||||
* Stuff that should happen any time a city is moved between civs, so also when trading,
|
||||
* should go in `this.moveToCiv()`, which this function also calls.
|
||||
* should go in `this.moveToCiv()`, which this function also calls.
|
||||
*/
|
||||
private fun conquerCity(conqueringCiv: CivilizationInfo, conqueredCiv: CivilizationInfo, receivingCiv: CivilizationInfo) {
|
||||
val goldPlundered = getGoldForCapturingCity(conqueringCiv)
|
||||
@ -101,7 +102,7 @@ class CityInfoConquestFunctions(val city: CityInfo){
|
||||
val reconqueredCityWhileStillInResistance = previousOwner == conqueringCiv.civName && isInResistance()
|
||||
|
||||
destroyBuildingsOnCapture()
|
||||
|
||||
|
||||
this@CityInfoConquestFunctions.moveToCiv(receivingCiv)
|
||||
|
||||
Battle.destroyIfDefeated(conqueredCiv, conqueringCiv)
|
||||
@ -272,12 +273,12 @@ class CityInfoConquestFunctions(val city: CityInfo){
|
||||
|
||||
// Stop WLTKD if it's still going
|
||||
resetWLTKD()
|
||||
|
||||
|
||||
// Remove their free buildings from this city and remove free buildings provided by the city from their cities
|
||||
removeBuildingsOnMoveToCiv(oldCiv)
|
||||
|
||||
// Place palace for newCiv if this is the only city they have
|
||||
// This needs to happen _before_ free buildings are added, as somtimes these should
|
||||
// This needs to happen _before_ free buildings are added, as somtimes these should
|
||||
// only be placed in the capital, and then there needs to be a capital.
|
||||
if (newCivInfo.cities.size == 1) {
|
||||
newCivInfo.moveCapitalTo(this)
|
||||
@ -308,4 +309,4 @@ class CityInfoConquestFunctions(val city: CityInfo){
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -4,23 +4,32 @@ import com.unciv.Constants
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.HexMath
|
||||
import com.unciv.logic.civilization.CivilizationInfo
|
||||
import com.unciv.logic.map.*
|
||||
import com.unciv.logic.map.MapParameters
|
||||
import com.unciv.logic.map.MapShape
|
||||
import com.unciv.logic.map.MapType
|
||||
import com.unciv.logic.map.Perlin
|
||||
import com.unciv.logic.map.TileInfo
|
||||
import com.unciv.logic.map.TileMap
|
||||
import com.unciv.models.Counter
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.ruleset.tile.ResourceType
|
||||
import com.unciv.models.ruleset.tile.Terrain
|
||||
import com.unciv.models.ruleset.tile.TerrainType
|
||||
import com.unciv.models.ruleset.unique.Unique
|
||||
import kotlin.math.*
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.ui.mapeditor.MapGeneratorSteps
|
||||
import com.unciv.utils.Log
|
||||
import com.unciv.utils.debug
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.math.sign
|
||||
import kotlin.random.Random
|
||||
|
||||
|
||||
class MapGenerator(val ruleset: Ruleset) {
|
||||
companion object {
|
||||
// temporary instrumentation while tuning/debugging
|
||||
const val consoleOutput = false
|
||||
private const val consoleTimings = false
|
||||
}
|
||||
|
||||
@ -74,7 +83,7 @@ class MapGenerator(val ruleset: Ruleset) {
|
||||
return map
|
||||
}
|
||||
|
||||
if (consoleOutput || consoleTimings) println("\nMapGenerator run with parameters $mapParameters")
|
||||
if (consoleTimings) debug("\nMapGenerator run with parameters %s", mapParameters)
|
||||
runAndMeasure("MapLandmassGenerator") {
|
||||
MapLandmassGenerator(ruleset, randomness).generateLand(map)
|
||||
}
|
||||
@ -164,7 +173,7 @@ class MapGenerator(val ruleset: Ruleset) {
|
||||
val startNanos = System.nanoTime()
|
||||
action()
|
||||
val delta = System.nanoTime() - startNanos
|
||||
println("MapGenerator.$text took ${delta/1000000L}.${(delta/10000L).rem(100)}ms")
|
||||
debug("MapGenerator.%s took %s.%sms", text, delta/1000000L, (delta/10000L).rem(100))
|
||||
}
|
||||
|
||||
private fun convertTerrains(map: TileMap, ruleset: Ruleset) {
|
||||
@ -312,19 +321,19 @@ class MapGenerator(val ruleset: Ruleset) {
|
||||
private fun raiseMountainsAndHills(tileMap: TileMap) {
|
||||
val mountain = ruleset.terrains.values.firstOrNull { it.hasUnique(UniqueType.OccursInChains) }?.name
|
||||
val hill = ruleset.terrains.values.firstOrNull { it.hasUnique(UniqueType.OccursInGroups) }?.name
|
||||
val flat = ruleset.terrains.values.firstOrNull {
|
||||
!it.impassable && it.type == TerrainType.Land && !it.hasUnique(UniqueType.RoughTerrain)
|
||||
val flat = ruleset.terrains.values.firstOrNull {
|
||||
!it.impassable && it.type == TerrainType.Land && !it.hasUnique(UniqueType.RoughTerrain)
|
||||
}?.name
|
||||
|
||||
if (flat == null) {
|
||||
println("Ruleset seems to contain no flat terrain - can't generate heightmap")
|
||||
debug("Ruleset seems to contain no flat terrain - can't generate heightmap")
|
||||
return
|
||||
}
|
||||
|
||||
if (consoleOutput && mountain != null)
|
||||
println("Mountain-like generation for $mountain")
|
||||
if (consoleOutput && hill != null)
|
||||
println("Hill-like generation for $hill")
|
||||
if (mountain != null)
|
||||
debug("Mountain-like generation for %s", mountain)
|
||||
if (hill != null)
|
||||
debug("Hill-like generation for %s", mountain)
|
||||
|
||||
val elevationSeed = randomness.RNG.nextInt().toDouble()
|
||||
tileMap.setTransients(ruleset)
|
||||
@ -478,7 +487,7 @@ class MapGenerator(val ruleset: Ruleset) {
|
||||
temperature < 0.8 -> if (humidity < 0.5) Constants.plains else Constants.grassland
|
||||
temperature <= 1.0 -> if (humidity < 0.7) Constants.desert else Constants.plains
|
||||
else -> {
|
||||
println("applyHumidityAndTemperature: Invalid temperature $temperature")
|
||||
debug("applyHumidityAndTemperature: Invalid temperature %s", temperature)
|
||||
firstLandTerrain.name
|
||||
}
|
||||
}
|
||||
@ -492,7 +501,7 @@ class MapGenerator(val ruleset: Ruleset) {
|
||||
if (matchingTerrain != null) tile.baseTerrain = matchingTerrain.terrain.name
|
||||
else {
|
||||
tile.baseTerrain = firstLandTerrain.name
|
||||
println("applyHumidityAndTemperature: No terrain found for temperature: $temperature, humidity: $humidity")
|
||||
debug("applyHumidityAndTemperature: No terrain found for temperature: %s, humidity: %s", temperature, humidity)
|
||||
}
|
||||
tile.setTerrainTransients()
|
||||
}
|
||||
@ -542,7 +551,7 @@ class MapGenerator(val ruleset: Ruleset) {
|
||||
ruleset.terrains.values.asSequence()
|
||||
.filter { it.type == TerrainType.Water }
|
||||
.map { it.name }.toSet()
|
||||
val iceEquivalents: List<TerrainOccursRange> =
|
||||
val iceEquivalents: List<TerrainOccursRange> =
|
||||
ruleset.terrains.values.asSequence()
|
||||
.filter { terrain ->
|
||||
terrain.type == TerrainType.TerrainFeature &&
|
||||
@ -645,8 +654,8 @@ class MapGenerationRandomness {
|
||||
}
|
||||
if (chosenTiles.size == number || distanceBetweenResources == 1) {
|
||||
// Either we got them all, or we're not going to get anything better
|
||||
if (MapGenerator.consoleOutput && distanceBetweenResources < initialDistance)
|
||||
println("chooseSpreadOutLocations: distance $distanceBetweenResources < initial $initialDistance")
|
||||
if (Log.shouldLog() && distanceBetweenResources < initialDistance)
|
||||
debug("chooseSpreadOutLocations: distance $distanceBetweenResources < initial $initialDistance")
|
||||
return chosenTiles
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.unciv.logic.map.mapgenerator
|
||||
|
||||
import com.unciv.Constants
|
||||
import com.unciv.utils.debug
|
||||
import com.unciv.logic.map.TileInfo
|
||||
import com.unciv.logic.map.TileMap
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
@ -49,8 +50,7 @@ class NaturalWonderGenerator(val ruleset: Ruleset, val randomness: MapGeneration
|
||||
}
|
||||
}
|
||||
|
||||
if (MapGenerator.consoleOutput)
|
||||
println("Natural Wonders for this game: $spawned")
|
||||
debug("Natural Wonders for this game: %s", spawned)
|
||||
}
|
||||
|
||||
private fun Unique.getIntParam(index: Int) = params[index].toInt()
|
||||
@ -156,15 +156,13 @@ class NaturalWonderGenerator(val ruleset: Ruleset, val randomness: MapGeneration
|
||||
clearTile(tile)
|
||||
}
|
||||
}
|
||||
if (MapGenerator.consoleOutput)
|
||||
println("Natural Wonder ${wonder.name} @${location.position}")
|
||||
debug("Natural Wonder %s @%s", wonder.name, location.position)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if (MapGenerator.consoleOutput)
|
||||
println("No suitable location for ${wonder.name}")
|
||||
debug("No suitable location for %s", wonder.name)
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package com.unciv.logic.map.mapgenerator
|
||||
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.unciv.Constants
|
||||
import com.unciv.utils.debug
|
||||
import com.unciv.logic.map.TileInfo
|
||||
import com.unciv.logic.map.TileMap
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
@ -93,7 +94,7 @@ class RiverGenerator(
|
||||
}
|
||||
riverCoordinate = newCoordinate
|
||||
}
|
||||
println("River reached max length!")
|
||||
debug("River reached max length!")
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1,7 +1,9 @@
|
||||
package com.unciv.logic.multiplayer.storage
|
||||
|
||||
import com.unciv.utils.debug
|
||||
import com.unciv.json.json
|
||||
import com.unciv.ui.utils.UncivDateFormat.parseDate
|
||||
import com.unciv.utils.Log
|
||||
import java.io.*
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
@ -41,10 +43,10 @@ object DropBox: FileStorage {
|
||||
|
||||
return inputStream
|
||||
} catch (ex: Exception) {
|
||||
println(ex.message)
|
||||
debug("Dropbox exception", ex)
|
||||
val reader = BufferedReader(InputStreamReader(errorStream))
|
||||
val responseString = reader.readText()
|
||||
println(responseString)
|
||||
debug("Response: %s", responseString)
|
||||
|
||||
val error = json().fromJson(ErrorResponse::class.java, responseString)
|
||||
// Throw Exceptions based on the HTTP response from dropbox
|
||||
@ -53,12 +55,11 @@ object DropBox: FileStorage {
|
||||
error.error_summary.startsWith("path/not_found/") -> throw FileNotFoundException()
|
||||
error.error_summary.startsWith("path/conflict/file") -> throw FileStorageConflictException()
|
||||
}
|
||||
|
||||
|
||||
return null
|
||||
} catch (error: Error) {
|
||||
println(error.message)
|
||||
val reader = BufferedReader(InputStreamReader(errorStream))
|
||||
println(reader.readText())
|
||||
Log.error("Dropbox error", error)
|
||||
debug("Error stream: %s", { BufferedReader(InputStreamReader(errorStream)).readText() })
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package com.unciv.logic.multiplayer.storage
|
||||
|
||||
import com.badlogic.gdx.Net
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.utils.debug
|
||||
import java.io.BufferedReader
|
||||
import java.io.DataOutputStream
|
||||
import java.io.InputStreamReader
|
||||
@ -48,11 +49,11 @@ object SimpleHttp {
|
||||
val text = BufferedReader(InputStreamReader(inputStream)).readText()
|
||||
action(true, text, responseCode)
|
||||
} catch (t: Throwable) {
|
||||
println(t.message)
|
||||
debug("Error during HTTP request", t)
|
||||
val errorMessageToReturn =
|
||||
if (errorStream != null) BufferedReader(InputStreamReader(errorStream)).readText()
|
||||
else t.message!!
|
||||
println(errorMessageToReturn)
|
||||
debug("Returning error message [%s]", errorMessageToReturn)
|
||||
action(false, errorMessageToReturn, if (errorStream != null) responseCode else null)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.unciv.logic.multiplayer.storage
|
||||
|
||||
import com.badlogic.gdx.Net
|
||||
import com.unciv.utils.debug
|
||||
import java.io.FileNotFoundException
|
||||
import java.lang.Exception
|
||||
|
||||
@ -9,7 +10,7 @@ class UncivServerFileStorage(val serverUrl: String, val timeout: Int = 30000) :
|
||||
SimpleHttp.sendRequest(Net.HttpMethods.PUT, fileUrl(fileName), data, timeout) {
|
||||
success, result, _ ->
|
||||
if (!success) {
|
||||
println(result)
|
||||
debug("Error from UncivServer during save: %s", result)
|
||||
throw Exception(result)
|
||||
}
|
||||
}
|
||||
@ -20,7 +21,7 @@ class UncivServerFileStorage(val serverUrl: String, val timeout: Int = 30000) :
|
||||
SimpleHttp.sendGetRequest(fileUrl(fileName), timeout = timeout){
|
||||
success, result, code ->
|
||||
if (!success) {
|
||||
println(result)
|
||||
debug("Error from UncivServer during load: %s", result)
|
||||
when (code) {
|
||||
404 -> throw FileNotFoundException(result)
|
||||
else -> throw Exception(result)
|
||||
|
@ -34,6 +34,8 @@ import com.unciv.models.translations.fillPlaceholders
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.utils.colorFromRGB
|
||||
import com.unciv.ui.utils.getRelativeTextDistance
|
||||
import com.unciv.utils.Log
|
||||
import com.unciv.utils.debug
|
||||
import kotlin.collections.set
|
||||
|
||||
object ModOptionsConstants {
|
||||
@ -192,7 +194,7 @@ class Ruleset {
|
||||
fun allIHasUniques(): Sequence<IHasUniques> =
|
||||
allRulesetObjects() + sequenceOf(modOptions)
|
||||
|
||||
fun load(folderHandle: FileHandle, printOutput: Boolean) {
|
||||
fun load(folderHandle: FileHandle) {
|
||||
val gameBasicsStartTime = System.currentTimeMillis()
|
||||
|
||||
val modOptionsFile = folderHandle.child("ModOptions.json")
|
||||
@ -342,8 +344,7 @@ class Ruleset {
|
||||
}
|
||||
}
|
||||
|
||||
val gameBasicsLoadTime = System.currentTimeMillis() - gameBasicsStartTime
|
||||
if (printOutput) println("Loading ruleset - " + gameBasicsLoadTime + "ms")
|
||||
debug("Loading ruleset - %sms", System.currentTimeMillis() - gameBasicsStartTime)
|
||||
}
|
||||
|
||||
/** Building costs are unique in that they are dependant on info in the technology part.
|
||||
@ -859,7 +860,7 @@ object RulesetCache : HashMap<String,Ruleset>() {
|
||||
|
||||
|
||||
/** Returns error lines from loading the rulesets, so we can display the errors to users */
|
||||
fun loadRulesets(consoleMode: Boolean = false, printOutput: Boolean = false, noMods: Boolean = false) :List<String> {
|
||||
fun loadRulesets(consoleMode: Boolean = false, noMods: Boolean = false) :List<String> {
|
||||
clear()
|
||||
for (ruleset in BaseRuleset.values()) {
|
||||
val fileName = "jsons/${ruleset.fullName}"
|
||||
@ -867,7 +868,7 @@ object RulesetCache : HashMap<String,Ruleset>() {
|
||||
if (consoleMode) FileHandle(fileName)
|
||||
else Gdx.files.internal(fileName)
|
||||
this[ruleset.fullName] = Ruleset().apply {
|
||||
load(fileHandle, printOutput)
|
||||
load(fileHandle)
|
||||
name = ruleset.fullName
|
||||
}
|
||||
}
|
||||
@ -883,13 +884,16 @@ object RulesetCache : HashMap<String,Ruleset>() {
|
||||
if (!modFolder.isDirectory) continue
|
||||
try {
|
||||
val modRuleset = Ruleset()
|
||||
modRuleset.load(modFolder.child("jsons"), printOutput)
|
||||
modRuleset.load(modFolder.child("jsons"))
|
||||
modRuleset.name = modFolder.name()
|
||||
modRuleset.folderLocation = modFolder
|
||||
this[modRuleset.name] = modRuleset
|
||||
if (printOutput) {
|
||||
println("Mod loaded successfully: " + modRuleset.name)
|
||||
println(modRuleset.checkModLinks().getErrorText())
|
||||
debug("Mod loaded successfully: %s", modRuleset.name)
|
||||
if (Log.shouldLog()) {
|
||||
val modLinksErrors = modRuleset.checkModLinks()
|
||||
if (modLinksErrors.any()) {
|
||||
debug("checkModLinks errors: %s", modLinksErrors.getErrorText())
|
||||
}
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
errorLines += "Exception loading mod '${modFolder.name()}':"
|
||||
@ -897,7 +901,7 @@ object RulesetCache : HashMap<String,Ruleset>() {
|
||||
errorLines += " ${ex.cause?.localizedMessage}"
|
||||
}
|
||||
}
|
||||
if (printOutput) for (line in errorLines) println(line)
|
||||
if (Log.shouldLog()) for (line in errorLines) debug(line)
|
||||
return errorLines
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ import com.unciv.json.fromJsonFile
|
||||
import com.unciv.json.json
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.utils.debug
|
||||
|
||||
object TileSetCache : HashMap<String, TileSetConfig>() {
|
||||
private data class TileSetAndMod(val tileSet: String, val mod: String)
|
||||
@ -36,7 +37,7 @@ object TileSetCache : HashMap<String, TileSetConfig>() {
|
||||
}
|
||||
}
|
||||
|
||||
fun loadTileSetConfigs(consoleMode: Boolean = false, printOutput: Boolean = false){
|
||||
fun loadTileSetConfigs(consoleMode: Boolean = false){
|
||||
allConfigs.clear()
|
||||
var tileSetName = ""
|
||||
|
||||
@ -51,21 +52,16 @@ object TileSetCache : HashMap<String, TileSetConfig>() {
|
||||
val key = TileSetAndMod(tileSetName, "")
|
||||
assert(key !in allConfigs)
|
||||
allConfigs[key] = json().fromJsonFile(TileSetConfig::class.java, configFile)
|
||||
if (printOutput) {
|
||||
println("TileSetConfig loaded successfully: ${configFile.name()}")
|
||||
println()
|
||||
}
|
||||
debug("TileSetConfig loaded successfully: %s", configFile.name())
|
||||
} catch (ex: Exception){
|
||||
if (printOutput){
|
||||
println("Exception loading TileSetConfig '${configFile.path()}':")
|
||||
println(" ${ex.localizedMessage}")
|
||||
println(" (Source file ${ex.stackTrace[0].fileName} line ${ex.stackTrace[0].lineNumber})")
|
||||
}
|
||||
debug("Exception loading TileSetConfig '%s':", configFile.path())
|
||||
debug(" %s", ex.localizedMessage)
|
||||
debug(" (Source file %s line %s)", ex.stackTrace[0].fileName, ex.stackTrace[0].lineNumber)
|
||||
}
|
||||
}
|
||||
|
||||
//load mod TileSets
|
||||
val modsHandles =
|
||||
val modsHandles =
|
||||
if (consoleMode) FileHandle("mods").list().toList()
|
||||
else RulesetCache.values.mapNotNull { it.folderLocation }
|
||||
|
||||
@ -80,17 +76,12 @@ object TileSetCache : HashMap<String, TileSetConfig>() {
|
||||
val key = TileSetAndMod(tileSetName, modName)
|
||||
assert(key !in allConfigs)
|
||||
allConfigs[key] = json().fromJsonFile(TileSetConfig::class.java, configFile)
|
||||
if (printOutput) {
|
||||
println("TileSetConfig loaded successfully: ${configFile.path()}")
|
||||
println()
|
||||
}
|
||||
debug("TileSetConfig loaded successfully: %s", configFile.path())
|
||||
}
|
||||
} catch (ex: Exception){
|
||||
if (printOutput) {
|
||||
println("Exception loading TileSetConfig '${modFolder.name()}/jsons/TileSets/${tileSetName}':")
|
||||
println(" ${ex.localizedMessage}")
|
||||
println(" (Source file ${ex.stackTrace[0].fileName} line ${ex.stackTrace[0].lineNumber})")
|
||||
}
|
||||
debug("Exception loading TileSetConfig '%s/jsons/TileSets/%s':", modFolder.name(), tileSetName)
|
||||
debug(" %s", ex.localizedMessage)
|
||||
debug(" (Source file %s line %s)", ex.stackTrace[0].fileName, ex.stackTrace[0].lineNumber)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ import com.unciv.models.ruleset.unique.*
|
||||
import com.unciv.models.ruleset.unit.BaseUnit
|
||||
import com.unciv.models.ruleset.unit.Promotion
|
||||
import com.unciv.models.ruleset.unit.UnitType
|
||||
import com.unciv.utils.debug
|
||||
import java.io.File
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.Modifier
|
||||
@ -143,7 +144,7 @@ object TranslationFileWriter {
|
||||
}
|
||||
|
||||
val translationKey = line.split(" = ")[0].replace("\\n", "\n")
|
||||
val hashMapKey =
|
||||
val hashMapKey =
|
||||
if (translationKey == Translations.englishConditionalOrderingString)
|
||||
Translations.englishConditionalOrderingString
|
||||
else translationKey
|
||||
@ -279,7 +280,7 @@ object TranslationFileWriter {
|
||||
.sortedBy { it.name() } // generatedStrings maintains order, so let's feed it a predictable one
|
||||
|
||||
// One set per json file, secondary loop var. Could be nicer to isolate all per-file
|
||||
// processing into another class, but then we'd have to pass uniqueIndexOfNewLine around.
|
||||
// processing into another class, but then we'd have to pass uniqueIndexOfNewLine around.
|
||||
lateinit var resultStrings: MutableSet<String>
|
||||
|
||||
init {
|
||||
@ -304,7 +305,7 @@ object TranslationFileWriter {
|
||||
}
|
||||
val displayName = if (jsonsFolder.name() != "jsons") jsonsFolder.name()
|
||||
else jsonsFolder.parent().name() // Show mod name
|
||||
println("Translation writer took ${System.currentTimeMillis() - startMillis}ms for $displayName")
|
||||
debug("Translation writer took %sms for %s", System.currentTimeMillis() - startMillis, displayName)
|
||||
}
|
||||
|
||||
fun submitString(string: String) {
|
||||
@ -390,7 +391,7 @@ object TranslationFileWriter {
|
||||
// Promotion names are not uniques but since we did the "[unitName] ability"
|
||||
// they need the "parameters" treatment too
|
||||
// Same for victory milestones
|
||||
(field.name == "uniques" || field.name == "promotions" || field.name == "milestones")
|
||||
(field.name == "uniques" || field.name == "promotions" || field.name == "milestones")
|
||||
&& (fieldValue is java.util.AbstractCollection<*>) ->
|
||||
for (item in fieldValue)
|
||||
if (item is String) submitString(item, Unique(item)) else serializeElement(item!!)
|
||||
@ -478,7 +479,7 @@ object TranslationFileWriter {
|
||||
}
|
||||
|
||||
//endregion
|
||||
//region Fastlane
|
||||
//region Fastlane
|
||||
|
||||
/** This writes translated short_description.txt and full_description.txt files into the Fastlane structure.
|
||||
* @param [translations] A [Translations] instance with all languages loaded.
|
||||
@ -511,7 +512,7 @@ object TranslationFileWriter {
|
||||
File(path + File.separator + fileName).writeText(fileContent)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Original changelog entry, written by incrementVersionAndChangelog, is often changed manually for readability.
|
||||
// This updates the fastlane changelog entry to match the latest one in changelog.md
|
||||
private fun updateFastlaneChangelog() {
|
||||
|
@ -6,6 +6,8 @@ import com.unciv.models.ruleset.RulesetCache
|
||||
import com.unciv.models.ruleset.unique.Unique
|
||||
import com.unciv.models.stats.Stat
|
||||
import com.unciv.models.stats.Stats
|
||||
import com.unciv.utils.Log
|
||||
import com.unciv.utils.debug
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
import kotlin.collections.LinkedHashSet
|
||||
@ -30,7 +32,7 @@ import kotlin.collections.LinkedHashSet
|
||||
* @see String.tr for more explanations (below)
|
||||
*/
|
||||
class Translations : LinkedHashMap<String, TranslationEntry>(){
|
||||
|
||||
|
||||
var percentCompleteOfLanguages = HashMap<String,Int>()
|
||||
.apply { put("English",100) } // So even if we don't manage to load the percentages, we can still pass the language screen
|
||||
|
||||
@ -79,7 +81,7 @@ class Translations : LinkedHashMap<String, TranslationEntry>(){
|
||||
/** This reads all translations for a specific language, including _all_ installed mods.
|
||||
* Vanilla translations go into `this` instance, mod translations into [modsWithTranslations].
|
||||
*/
|
||||
private fun tryReadTranslationForLanguage(language: String, printOutput: Boolean) {
|
||||
private fun tryReadTranslationForLanguage(language: String) {
|
||||
val translationStart = System.currentTimeMillis()
|
||||
|
||||
val translationFileName = "jsons/translations/$language.properties"
|
||||
@ -90,7 +92,7 @@ class Translations : LinkedHashMap<String, TranslationEntry>(){
|
||||
// which is super odd because everyone should support UTF-8
|
||||
languageTranslations = TranslationFileReader.read(Gdx.files.internal(translationFileName))
|
||||
} catch (ex: Exception) {
|
||||
println("Exception reading translations for $language: ${ex.message}")
|
||||
Log.error("Exception reading translations for $language", ex)
|
||||
return
|
||||
}
|
||||
|
||||
@ -106,15 +108,14 @@ class Translations : LinkedHashMap<String, TranslationEntry>(){
|
||||
try {
|
||||
translationsForMod.createTranslations(language, TranslationFileReader.read(modTranslationFile))
|
||||
} catch (ex: Exception) {
|
||||
println("Exception reading translations for ${modFolder.name()} $language: ${ex.message}")
|
||||
Log.error("Exception reading translations for ${modFolder.name()} $language", ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createTranslations(language, languageTranslations)
|
||||
|
||||
val translationFilesTime = System.currentTimeMillis() - translationStart
|
||||
if (printOutput) println("Loading translation file for $language - " + translationFilesTime + "ms")
|
||||
debug("Loading translation file for %s - %sms", language, System.currentTimeMillis() - translationStart)
|
||||
}
|
||||
|
||||
private fun createTranslations(language: String, languageTranslations: HashMap<String,String>) {
|
||||
@ -132,7 +133,7 @@ class Translations : LinkedHashMap<String, TranslationEntry>(){
|
||||
}
|
||||
|
||||
fun tryReadTranslationForCurrentLanguage(){
|
||||
tryReadTranslationForLanguage(UncivGame.Current.settings.language, false)
|
||||
tryReadTranslationForLanguage(UncivGame.Current.settings.language)
|
||||
}
|
||||
|
||||
/** Get a list of supported languages for [readAllLanguagesTranslation] */
|
||||
@ -166,7 +167,7 @@ class Translations : LinkedHashMap<String, TranslationEntry>(){
|
||||
}
|
||||
|
||||
/** Ensure _all_ languages are loaded, used by [TranslationFileWriter] and `TranslationTests` */
|
||||
fun readAllLanguagesTranslation(printOutput:Boolean=false) {
|
||||
fun readAllLanguagesTranslation() {
|
||||
// Apparently you can't iterate over the files in a directory when running out of a .jar...
|
||||
// https://www.badlogicgames.com/forum/viewtopic.php?f=11&t=27250
|
||||
// which means we need to list everything manually =/
|
||||
@ -174,11 +175,10 @@ class Translations : LinkedHashMap<String, TranslationEntry>(){
|
||||
val translationStart = System.currentTimeMillis()
|
||||
|
||||
for (language in getLanguagesWithTranslationFile()) {
|
||||
tryReadTranslationForLanguage(language, printOutput)
|
||||
tryReadTranslationForLanguage(language)
|
||||
}
|
||||
|
||||
val translationFilesTime = System.currentTimeMillis() - translationStart
|
||||
if(printOutput) println("Loading translation files - ${translationFilesTime}ms")
|
||||
debug("Loading translation files - %sms", System.currentTimeMillis() - translationStart)
|
||||
}
|
||||
|
||||
fun loadPercentageCompleteOfLanguages(){
|
||||
@ -186,14 +186,13 @@ class Translations : LinkedHashMap<String, TranslationEntry>(){
|
||||
|
||||
percentCompleteOfLanguages = TranslationFileReader.readLanguagePercentages()
|
||||
|
||||
val translationFilesTime = System.currentTimeMillis() - startTime
|
||||
println("Loading percent complete of languages - ${translationFilesTime}ms")
|
||||
debug("Loading percent complete of languages - %sms", System.currentTimeMillis() - startTime)
|
||||
}
|
||||
|
||||
|
||||
fun getConditionalOrder(language: String): String {
|
||||
return getText(englishConditionalOrderingString, language, null)
|
||||
}
|
||||
|
||||
|
||||
fun placeConditionalsAfterUnique(language: String): Boolean {
|
||||
if (get(conditionalUniqueOrderString, language, null)?.get(language) == "before")
|
||||
return false
|
||||
@ -207,15 +206,15 @@ class Translations : LinkedHashMap<String, TranslationEntry>(){
|
||||
val translation = getText("\" \"", language, null)
|
||||
return translation.substring(1, translation.length-1)
|
||||
}
|
||||
|
||||
|
||||
fun shouldCapitalize(language: String): Boolean {
|
||||
return get(shouldCapitalizeString, language, null)?.get(language)?.toBoolean() ?: true
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
// Whenever this string is changed, it should also be changed in the translation files!
|
||||
// It is mostly used as the template for translating the order of conditionals
|
||||
const val englishConditionalOrderingString =
|
||||
// It is mostly used as the template for translating the order of conditionals
|
||||
const val englishConditionalOrderingString =
|
||||
"<with a garrison> <for [mapUnitFilter] units> <above [amount] HP> <below [amount] HP> <vs cities> <vs [mapUnitFilter] units> <when fighting in [tileFilter] tiles> <when attacking> <when defending> <if this city has at least [amount] specialists> <when at war> <when not at war> <while the empire is happy> <during a Golden Age> <during the [era]> <before the [era]> <starting from the [era]> <with [techOrPolicy]> <without [techOrPolicy]>"
|
||||
const val conditionalUniqueOrderString = "ConditionalsPlacement"
|
||||
const val shouldCapitalizeString = "StartWithCapitalLetter"
|
||||
|
@ -7,6 +7,8 @@ import com.badlogic.gdx.files.FileHandle
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.models.metadata.GameSettings
|
||||
import com.unciv.logic.multiplayer.storage.DropBox
|
||||
import com.unciv.utils.Log
|
||||
import com.unciv.utils.debug
|
||||
import java.util.*
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.concurrent.timer
|
||||
@ -15,7 +17,7 @@ import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* Play, choose, fade-in/out and generally manage music track playback.
|
||||
*
|
||||
*
|
||||
* Main methods: [chooseTrack], [pause], [resume], [setModList], [isPlaying], [gracefulShutdown]
|
||||
*/
|
||||
class MusicController {
|
||||
@ -35,8 +37,6 @@ class MusicController {
|
||||
private const val musicHistorySize = 8 // number of names to keep to avoid playing the same in short succession
|
||||
private val fileExtensions = listOf("mp3", "ogg", "wav") // All Gdx formats
|
||||
|
||||
internal const val consoleLog = false
|
||||
|
||||
private fun getFile(path: String) =
|
||||
if (musicLocation == FileType.External && Gdx.files.isExternalStorageAvailable)
|
||||
Gdx.files.external(path)
|
||||
@ -232,8 +232,7 @@ class MusicController {
|
||||
clearNext()
|
||||
clearCurrent()
|
||||
musicHistory.clear()
|
||||
if (consoleLog)
|
||||
println("MusicController shut down.")
|
||||
debug("MusicController shut down.")
|
||||
}
|
||||
|
||||
private fun audioExceptionHandler(ex: Throwable, music: Music) {
|
||||
@ -247,12 +246,7 @@ class MusicController {
|
||||
if (music == next?.music) clearNext()
|
||||
if (music == current?.music) clearCurrent()
|
||||
|
||||
if (consoleLog) {
|
||||
println("${ex.javaClass.simpleName} playing music: ${ex.message}")
|
||||
if (ex.stackTrace != null) ex.printStackTrace()
|
||||
} else {
|
||||
println("Error playing music: ${ex.message ?: ""}")
|
||||
}
|
||||
Log.error("Error playing music", ex)
|
||||
|
||||
// Since this is a rare emergency, go a simple way to reboot music later
|
||||
thread(isDaemon = true) {
|
||||
@ -303,7 +297,7 @@ class MusicController {
|
||||
// Then just pick the first one. Not as wasteful as it looks - need to check all names anyway
|
||||
)).firstOrNull()
|
||||
// Note: shuffled().sortedWith(), ***not*** .sortedWith(.., Random)
|
||||
// the latter worked with older JVM's, current ones *crash* you when a compare is not transitive.
|
||||
// the latter worked with older JVM's, current ones *crash* you when a compare is not transitive.
|
||||
}
|
||||
|
||||
private fun fireOnChange() {
|
||||
@ -324,8 +318,7 @@ class MusicController {
|
||||
try {
|
||||
onTrackChangeListener?.invoke(trackLabel)
|
||||
} catch (ex: Throwable) {
|
||||
if (consoleLog)
|
||||
println("onTrackChange event invoke failed: ${ex.message}")
|
||||
debug("onTrackChange event invoke failed", ex)
|
||||
onTrackChangeListener = null
|
||||
}
|
||||
}
|
||||
@ -346,7 +339,7 @@ class MusicController {
|
||||
* Chooses and plays a music track using an adaptable approach - for details see the wiki.
|
||||
* Called without parameters it will choose a new ambient music track and start playing it with fade-in/out.
|
||||
* Will do nothing when no music files exist or the master volume is zero.
|
||||
*
|
||||
*
|
||||
* @param prefix file name prefix, meant to represent **Context** - in most cases a Civ name
|
||||
* @param suffix file name suffix, meant to represent **Mood** - e.g. Peace, War, Theme, Defeat, Ambient
|
||||
* (Ambient is the default when a track ends and exists so War Peace and the others are not chosen in that case)
|
||||
@ -355,7 +348,7 @@ class MusicController {
|
||||
*/
|
||||
fun chooseTrack (
|
||||
prefix: String = "",
|
||||
suffix: String = "Ambient",
|
||||
suffix: String = "Ambient",
|
||||
flags: EnumSet<MusicTrackChooserFlags> = EnumSet.noneOf(MusicTrackChooserFlags::class.java)
|
||||
): Boolean {
|
||||
if (baseVolume == 0f) return false
|
||||
@ -364,8 +357,7 @@ class MusicController {
|
||||
|
||||
if (musicFile == null) {
|
||||
// MustMatch flags at work or Music folder empty
|
||||
if (consoleLog)
|
||||
println("No music found for prefix=$prefix, suffix=$suffix, flags=$flags")
|
||||
debug("No music found for prefix=%s, suffix=%s, flags=%s", prefix, suffix, flags)
|
||||
return false
|
||||
}
|
||||
if (musicFile.path() == currentlyPlaying())
|
||||
@ -381,8 +373,7 @@ class MusicController {
|
||||
state = ControllerState.Silence // will retry after one silence period
|
||||
next = null
|
||||
}, onSuccess = {
|
||||
if (consoleLog)
|
||||
println("Music queued: ${musicFile.path()} for prefix=$prefix, suffix=$suffix, flags=$flags")
|
||||
debug("Music queued: %s for prefix=%s, suffix=%s, flags=%s", musicFile.path(), prefix, suffix, flags)
|
||||
|
||||
if (musicHistory.size >= musicHistorySize) musicHistory.removeFirst()
|
||||
musicHistory.addLast(musicFile.path())
|
||||
@ -407,7 +398,7 @@ class MusicController {
|
||||
startTimer()
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
/** Variant of [chooseTrack] that tries several moods ([suffixes]) until a match is chosen */
|
||||
fun chooseTrack (
|
||||
prefix: String = "",
|
||||
@ -422,12 +413,11 @@ class MusicController {
|
||||
|
||||
/**
|
||||
* Pause playback with fade-out
|
||||
*
|
||||
*
|
||||
* @param speedFactor accelerate (>1) or slow down (<1) the fade-out. Clamped to 1/1000..1000.
|
||||
*/
|
||||
fun pause(speedFactor: Float = 1f) {
|
||||
if (consoleLog)
|
||||
println("MusicTrackController.pause called")
|
||||
debug("MusicTrackController.pause called")
|
||||
if ((state != ControllerState.Playing && state != ControllerState.PlaySingle) || current == null) return
|
||||
val fadingStep = defaultFadingStep * speedFactor.coerceIn(0.001f..1000f)
|
||||
current!!.startFade(MusicTrackController.State.FadeOut, fadingStep)
|
||||
@ -443,8 +433,7 @@ class MusicController {
|
||||
* @param speedFactor accelerate (>1) or slow down (<1) the fade-in. Clamped to 1/1000..1000.
|
||||
*/
|
||||
fun resume(speedFactor: Float = 1f) {
|
||||
if (consoleLog)
|
||||
println("MusicTrackController.resume called")
|
||||
debug("MusicTrackController.resume called")
|
||||
if (state == ControllerState.Pause && current != null) {
|
||||
val fadingStep = defaultFadingStep * speedFactor.coerceIn(0.001f..1000f)
|
||||
current!!.startFade(MusicTrackController.State.FadeIn, fadingStep)
|
||||
|
@ -3,6 +3,8 @@ package com.unciv.ui.audio
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.audio.Music
|
||||
import com.badlogic.gdx.files.FileHandle
|
||||
import com.unciv.utils.Log
|
||||
import com.unciv.utils.debug
|
||||
|
||||
/** Wraps one Gdx Music instance and manages loading, playback, fading and cleanup */
|
||||
internal class MusicTrackController(private var volume: Float) {
|
||||
@ -54,8 +56,7 @@ internal class MusicTrackController(private var volume: Float) {
|
||||
clear()
|
||||
} else {
|
||||
state = State.Idle
|
||||
if (MusicController.consoleLog)
|
||||
println("Music loaded: $file")
|
||||
debug("Music loaded %s", file)
|
||||
onSuccess?.invoke(this)
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
@ -73,7 +74,7 @@ internal class MusicTrackController(private var volume: Float) {
|
||||
}
|
||||
|
||||
/** Starts fadeIn or fadeOut.
|
||||
*
|
||||
*
|
||||
* Note this does _not_ set the current fade "percentage" to allow smoothly changing direction mid-fade
|
||||
* @param step Overrides current fade step only if >0
|
||||
*/
|
||||
@ -165,12 +166,7 @@ internal class MusicTrackController(private var volume: Float) {
|
||||
|
||||
private fun audioExceptionHandler(ex: Throwable) {
|
||||
clear()
|
||||
if (MusicController.consoleLog) {
|
||||
println("${ex.javaClass.simpleName} playing music: ${ex.message}")
|
||||
if (ex.stackTrace != null) ex.printStackTrace()
|
||||
} else {
|
||||
println("Error playing music: ${ex.message ?: ""}")
|
||||
}
|
||||
Log.error("Error playing music", ex)
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
@ -7,6 +7,7 @@ import com.badlogic.gdx.files.FileHandle
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.models.UncivSound
|
||||
import com.unciv.ui.crashhandling.launchCrashHandling
|
||||
import com.unciv.utils.debug
|
||||
import kotlinx.coroutines.delay
|
||||
import java.io.File
|
||||
|
||||
@ -43,8 +44,6 @@ import java.io.File
|
||||
* app lifetime - and we do dispose them when the app is disposed.
|
||||
*/
|
||||
object Sounds {
|
||||
private const val debugMessages = false
|
||||
|
||||
@Suppress("EnumEntryName")
|
||||
private enum class SupportedExtensions { mp3, ogg, wav } // Per Gdx docs, no aac/m4a
|
||||
|
||||
@ -69,7 +68,7 @@ object Sounds {
|
||||
// Seems the mod list has changed - clear the cache
|
||||
clearCache()
|
||||
modListHash = newHash
|
||||
if (debugMessages) println("Sound cache cleared")
|
||||
debug("Sound cache cleared")
|
||||
}
|
||||
|
||||
/** Release cached Sound resources */
|
||||
@ -135,12 +134,12 @@ object Sounds {
|
||||
|
||||
@Suppress("LiftReturnOrAssignment")
|
||||
if (file == null || !file.exists()) {
|
||||
if (debugMessages) println("Sound ${sound.value} not found!")
|
||||
debug("Sound %s not found!", sound.value)
|
||||
// remember that the actual file is missing
|
||||
soundMap[sound] = null
|
||||
return null
|
||||
} else {
|
||||
if (debugMessages) println("Sound ${sound.value} loaded from ${file.path()}")
|
||||
debug("Sound %s loaded from %s", sound.value, file.path())
|
||||
val newSound = Gdx.audio.newSound(file)
|
||||
// Store Sound for reuse
|
||||
soundMap[sound] = newSound
|
||||
|
@ -17,6 +17,7 @@ import com.unciv.ui.utils.BaseScreen
|
||||
import com.unciv.ui.utils.addSeparator
|
||||
import com.unciv.ui.utils.onClick
|
||||
import com.unciv.ui.utils.toLabel
|
||||
import com.unciv.utils.Log
|
||||
import kotlin.math.max
|
||||
|
||||
/* Ideas:
|
||||
@ -111,7 +112,7 @@ class FormattedLine (
|
||||
val displayColor: Color by lazy { parseColor() }
|
||||
|
||||
/** Returns true if this formatted line will not display anything */
|
||||
fun isEmpty(): Boolean = text.isEmpty() && extraImage.isEmpty() &&
|
||||
fun isEmpty(): Boolean = text.isEmpty() && extraImage.isEmpty() &&
|
||||
!starred && icon.isEmpty() && link.isEmpty() && !separator
|
||||
|
||||
/** Self-check to potentially support the mod checker
|
||||
@ -187,9 +188,8 @@ class FormattedLine (
|
||||
val result = HashMap<String,CivilopediaCategories>()
|
||||
allObjectMapsSequence.filter { !it.first.hide }
|
||||
.flatMap { pair -> pair.second.keys.asSequence().map { key -> pair.first to key } }
|
||||
.forEach {
|
||||
.forEach {
|
||||
result[it.second] = it.first
|
||||
//println(" ${it.second} is a ${it.first}")
|
||||
}
|
||||
result["Maya Long Count calendar cycle"] = CivilopediaCategories.Tutorial
|
||||
|
||||
@ -231,7 +231,7 @@ class FormattedLine (
|
||||
/**
|
||||
* Renders the formatted line as a scene2d [Actor] (currently always a [Table])
|
||||
* @param labelWidth Total width to render into, needed to support wrap on Labels.
|
||||
* @param iconDisplay Flag to omit link or all images.
|
||||
* @param iconDisplay Flag to omit link or all images.
|
||||
*/
|
||||
fun render(labelWidth: Float, iconDisplay: IconDisplay = IconDisplay.All): Actor {
|
||||
if (extraImage.isNotEmpty()) {
|
||||
@ -250,7 +250,7 @@ class FormattedLine (
|
||||
val height = width * image.height / image.width
|
||||
table.add(image).size(width, height)
|
||||
} catch (exception: Exception) {
|
||||
println ("${exception.message}: ${exception.cause?.message}")
|
||||
Log.error("Exception while rendering civilopedia text", exception)
|
||||
}
|
||||
return table
|
||||
}
|
||||
@ -436,7 +436,7 @@ interface ICivilopediaText {
|
||||
* **or** [UncivGame.Current.worldScreen][UncivGame.worldScreen] being initialized,
|
||||
* this should be able to run from the main menu.
|
||||
* (And the info displayed should be about the **ruleset**, not the player situation)
|
||||
*
|
||||
*
|
||||
* Default implementation is empty - no need to call super in overrides.
|
||||
*
|
||||
* @param ruleset The current ruleset for the Civilopedia viewer
|
||||
|
@ -14,6 +14,7 @@ import com.unciv.ui.images.IconTextButton
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.popup.ToastPopup
|
||||
import com.unciv.ui.utils.*
|
||||
import com.unciv.utils.Log
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import kotlin.concurrent.thread
|
||||
@ -107,7 +108,7 @@ class CrashScreen(val exception: Throwable): BaseScreen() {
|
||||
}
|
||||
|
||||
init {
|
||||
println(text) // Also print to system terminal.
|
||||
Log.error(text) // Also print to system terminal.
|
||||
thread { throw exception } // this is so the GPC logs catch the exception
|
||||
stage.addActor(makeLayoutTable())
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import com.unciv.models.ruleset.tile.ResourceType
|
||||
import com.unciv.models.stats.Stats
|
||||
import com.unciv.models.tilesets.TileSetCache
|
||||
import com.unciv.ui.utils.*
|
||||
import com.unciv.utils.debug
|
||||
import kotlin.math.atan2
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
@ -88,7 +89,7 @@ object ImageGetter {
|
||||
val extraAtlas = if (mod.isEmpty()) fileName else if (fileName == "game") mod else "$mod/$fileName"
|
||||
var tempAtlas = atlases[extraAtlas] // fetch if cached
|
||||
if (tempAtlas == null) {
|
||||
println("Loading $extraAtlas = ${file.path()}")
|
||||
debug("Loading %s = %s", extraAtlas, file.path())
|
||||
tempAtlas = TextureAtlas(file) // load if not
|
||||
atlases[extraAtlas] = tempAtlas // cache the freshly loaded
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.mapeditor.MapEditorOptionsTab.TileMatchFuzziness
|
||||
import com.unciv.ui.popup.ToastPopup
|
||||
import com.unciv.ui.utils.*
|
||||
import com.unciv.utils.Log
|
||||
|
||||
class MapEditorEditTab(
|
||||
private val editorScreen: MapEditorScreen,
|
||||
@ -208,7 +209,7 @@ class MapEditorEditTab(
|
||||
val riverGenerator = RiverGenerator(editorScreen.tileMap, randomness, ruleset)
|
||||
riverGenerator.spawnRiver(riverStartTile!!, riverEndTile!!, resultingTiles)
|
||||
} catch (ex: Exception) {
|
||||
println(ex.message)
|
||||
Log.error("Exception while generating rivers", ex)
|
||||
ToastPopup("River generation failed!", editorScreen)
|
||||
}
|
||||
riverStartTile = null
|
||||
|
@ -16,6 +16,7 @@ import com.unciv.ui.newgamescreen.MapParametersTable
|
||||
import com.unciv.ui.popup.Popup
|
||||
import com.unciv.ui.popup.ToastPopup
|
||||
import com.unciv.ui.utils.*
|
||||
import com.unciv.utils.Log
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
class MapEditorGenerateTab(
|
||||
@ -116,7 +117,7 @@ class MapEditorGenerateTab(
|
||||
}
|
||||
}
|
||||
MapGeneratorSteps.Landmass -> {
|
||||
// This step _could_ run on an existing tileMap, but that opens a loophole where you get hills on water - fixing that is more expensive than always recreating
|
||||
// This step _could_ run on an existing tileMap, but that opens a loophole where you get hills on water - fixing that is more expensive than always recreating
|
||||
mapParameters.type = MapType.empty
|
||||
val generatedMap = generator!!.generateMap(mapParameters)
|
||||
mapParameters.type = editorScreen.newMapParameters.type
|
||||
@ -136,7 +137,7 @@ class MapEditorGenerateTab(
|
||||
}
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
println("Map generator exception: ${exception.message}")
|
||||
Log.error("Exception while generating map", exception)
|
||||
Gdx.app.postRunnable {
|
||||
setButtonsEnabled(true)
|
||||
Gdx.input.inputProcessor = editorScreen.stage
|
||||
|
@ -13,6 +13,7 @@ import com.unciv.ui.popup.Popup
|
||||
import com.unciv.ui.popup.ToastPopup
|
||||
import com.unciv.ui.popup.YesNoPopup
|
||||
import com.unciv.ui.utils.*
|
||||
import com.unciv.utils.Log
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
class MapEditorLoadTab(
|
||||
@ -134,7 +135,7 @@ class MapEditorLoadTab(
|
||||
} catch (ex: Throwable) {
|
||||
needPopup = false
|
||||
popup?.close()
|
||||
println("Error displaying map \"$chosenMap\": ${ex.message}")
|
||||
Log.error("Error displaying map \"$chosenMap\"", ex)
|
||||
Gdx.input.inputProcessor = editorScreen.stage
|
||||
ToastPopup("Error loading map!", editorScreen)
|
||||
}
|
||||
@ -143,7 +144,7 @@ class MapEditorLoadTab(
|
||||
needPopup = false
|
||||
Gdx.app.postRunnable {
|
||||
popup?.close()
|
||||
println("Error loading map \"$chosenMap\": ${ex.message}")
|
||||
Log.error("Error loading map \"$chosenMap\"", ex)
|
||||
ToastPopup("{Error loading map!}" +
|
||||
(if (ex is UncivShowableException) "\n{${ex.message}}" else ""), editorScreen)
|
||||
}
|
||||
@ -151,4 +152,4 @@ class MapEditorLoadTab(
|
||||
}
|
||||
|
||||
fun noMapsAvailable() = mapFiles.noMapsAvailable()
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,8 @@ import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.newgamescreen.TranslatedSelectBox
|
||||
import com.unciv.ui.popup.ToastPopup
|
||||
import com.unciv.ui.utils.*
|
||||
import com.unciv.utils.Log
|
||||
import com.unciv.utils.debug
|
||||
|
||||
|
||||
private const val MOD_CHECK_WITHOUT_BASE = "-none-"
|
||||
@ -194,7 +196,7 @@ class ModCheckTab(
|
||||
deprecatedUnique.sourceObjectType!!
|
||||
)
|
||||
for (error in modInvariantErrors)
|
||||
println(error.text + " - " + error.errorSeverityToReport)
|
||||
Log.error("ModInvariantError: %s - %s", error.text, error.errorSeverityToReport)
|
||||
if (modInvariantErrors.isNotEmpty()) continue // errors means no autoreplace
|
||||
|
||||
if (mod.modOptions.isBaseRuleset) {
|
||||
@ -206,12 +208,12 @@ class ModCheckTab(
|
||||
deprecatedUnique.sourceObjectType
|
||||
)
|
||||
for (error in modSpecificErrors)
|
||||
println(error.text + " - " + error.errorSeverityToReport)
|
||||
Log.error("ModSpecificError: %s - %s", error.text, error.errorSeverityToReport)
|
||||
if (modSpecificErrors.isNotEmpty()) continue
|
||||
}
|
||||
|
||||
deprecatedUniquesToReplacementText[deprecatedUnique.text] = uniqueReplacementText
|
||||
println("Replace \"${deprecatedUnique.text}\" with \"$uniqueReplacementText\"")
|
||||
debug("Replace \"%s\" with \"%s\"", deprecatedUnique.text, uniqueReplacementText)
|
||||
}
|
||||
return deprecatedUniquesToReplacementText
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ import com.unciv.json.fromJsonFile
|
||||
import com.unciv.json.json
|
||||
import com.unciv.logic.BackwardCompatibility.updateDeprecations
|
||||
import com.unciv.models.ruleset.ModOptions
|
||||
import com.unciv.utils.Log
|
||||
import com.unciv.utils.debug
|
||||
import java.io.*
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
@ -15,7 +17,7 @@ import java.util.zip.ZipFile
|
||||
|
||||
/**
|
||||
* Utility managing Github access (except the link in WorldScreenCommunityPopup)
|
||||
*
|
||||
*
|
||||
* Singleton - RateLimit is shared app-wide and has local variables, and is not tested for thread safety.
|
||||
* Therefore, additional effort is required should [tryGetGithubReposWithTopic] ever be called non-sequentially.
|
||||
* [download] and [downloadAndExtract] should be thread-safe as they are self-contained.
|
||||
@ -28,8 +30,8 @@ object Github {
|
||||
/**
|
||||
* Helper opens am url and accesses its input stream, logging errors to the console
|
||||
* @param url String representing a [URL] to download.
|
||||
* @param action Optional callback that will be executed between opening the connection and
|
||||
* accessing its data - passes the [connection][HttpURLConnection] and allows e.g. reading the response headers.
|
||||
* @param action Optional callback that will be executed between opening the connection and
|
||||
* accessing its data - passes the [connection][HttpURLConnection] and allows e.g. reading the response headers.
|
||||
* @return The [InputStream] if successful, `null` otherwise.
|
||||
*/
|
||||
fun download(url: String, action: (HttpURLConnection) -> Unit = {}): InputStream? {
|
||||
@ -40,9 +42,9 @@ object Github {
|
||||
return try {
|
||||
inputStream
|
||||
} catch (ex: Exception) {
|
||||
println(ex.message)
|
||||
Log.error("Exception during GitHub download", ex)
|
||||
val reader = BufferedReader(InputStreamReader(errorStream))
|
||||
println(reader.readText())
|
||||
Log.error("Message from GitHub: %s", reader.readText())
|
||||
null
|
||||
}
|
||||
}
|
||||
@ -121,7 +123,7 @@ object Github {
|
||||
if (file().renameTo(dest.child(name()).file())) return
|
||||
else
|
||||
if (file().renameTo(dest.file())) return
|
||||
}
|
||||
}
|
||||
moveTo(dest)
|
||||
}
|
||||
|
||||
@ -204,7 +206,7 @@ object Github {
|
||||
val reset = getHeaderLong("X-RateLimit-Reset")
|
||||
|
||||
if (limit != maxRequestsPerInterval)
|
||||
println("GitHub API Limit reported via http ($limit) not equal assumed value ($maxRequestsPerInterval)")
|
||||
debug("GitHub API Limit reported via http (%s) not equal assumed value (%s)", limit, maxRequestsPerInterval)
|
||||
account = maxRequestsPerInterval - remaining
|
||||
if (reset == 0L) return
|
||||
firstRequest = (reset + 1L) * 1000L - intervalInMilliSeconds
|
||||
@ -234,7 +236,7 @@ object Github {
|
||||
RateLimit.notifyHttpResponse(it)
|
||||
retries++ // An extra retry so the 403 is ignored in the retry count
|
||||
}
|
||||
} ?: continue
|
||||
} ?: continue
|
||||
return json().fromJson(RepoSearch::class.java, inputStream.bufferedReader().readText())
|
||||
}
|
||||
return null
|
||||
@ -350,7 +352,7 @@ object Zip {
|
||||
// (with mild changes to fit the FileHandles)
|
||||
// https://stackoverflow.com/questions/981578/how-to-unzip-files-recursively-in-java
|
||||
|
||||
println("Extracting $zipFile to $unzipDestination")
|
||||
debug("Extracting %s to %s", zipFile, unzipDestination)
|
||||
// establish buffer for writing file
|
||||
val data = ByteArray(bufferSize)
|
||||
|
||||
@ -389,7 +391,7 @@ object Zip {
|
||||
if (!entry.isDirectory) {
|
||||
streamCopy ( zip.getInputStream(entry), destFile)
|
||||
}
|
||||
// The new file has a current last modification time
|
||||
// The new file has a current last modification time
|
||||
// and not the one stored in the archive - we could:
|
||||
// 'destFile.file().setLastModified(entry.time)'
|
||||
// but later handling will throw these away anyway,
|
||||
|
@ -6,6 +6,7 @@ import com.badlogic.gdx.scenes.scene2d.EventListener
|
||||
import com.badlogic.gdx.scenes.scene2d.InputEvent
|
||||
import com.badlogic.gdx.scenes.scene2d.InputListener
|
||||
import com.badlogic.gdx.scenes.scene2d.Stage
|
||||
import com.unciv.utils.debug
|
||||
|
||||
/*
|
||||
* For now, many combination keys cannot easily be expressed.
|
||||
@ -181,8 +182,7 @@ class KeyPressDispatcher(val name: String? = null) : HashMap<KeyCharAndCode, (()
|
||||
* @param checkIgnoreKeys An optional lambda - when it returns true all keys are ignored
|
||||
*/
|
||||
fun install(stage: Stage, checkIgnoreKeys: (() -> Boolean)? = null) {
|
||||
if (consoleLog)
|
||||
println("$this: install")
|
||||
debug("%s: install", this)
|
||||
if (installStage != null) uninstall()
|
||||
listener =
|
||||
object : InputListener() {
|
||||
@ -198,13 +198,11 @@ class KeyPressDispatcher(val name: String? = null) : HashMap<KeyCharAndCode, (()
|
||||
|
||||
// see if we want to handle this key, and if not, let it propagate
|
||||
if (!contains(key) || (checkIgnoreKeys?.invoke() == true)) {
|
||||
if (consoleLog)
|
||||
println("${this@KeyPressDispatcher}: $key not handled")
|
||||
debug("%s: %s not handled", this@KeyPressDispatcher, key)
|
||||
return super.keyDown(event, keycode)
|
||||
}
|
||||
|
||||
if (consoleLog)
|
||||
println("${this@KeyPressDispatcher}: handling $key")
|
||||
debug("%s: handling %s", this@KeyPressDispatcher, key)
|
||||
this@KeyPressDispatcher[key]?.invoke()
|
||||
return true
|
||||
}
|
||||
@ -215,8 +213,7 @@ class KeyPressDispatcher(val name: String? = null) : HashMap<KeyCharAndCode, (()
|
||||
|
||||
/** uninstall our [EventListener] from the stage it was installed on. */
|
||||
fun uninstall() {
|
||||
if (consoleLog)
|
||||
println("$this: uninstall")
|
||||
debug("%s: uninstall", this)
|
||||
checkInstall(forceRemove = true)
|
||||
listener = null
|
||||
installStage = null
|
||||
@ -232,18 +229,14 @@ class KeyPressDispatcher(val name: String? = null) : HashMap<KeyCharAndCode, (()
|
||||
if (listener == null || installStage == null) return
|
||||
if (listenerInstalled && (isEmpty() || isPaused || forceRemove)) {
|
||||
listenerInstalled = false
|
||||
if (consoleLog)
|
||||
println("$this: removeListener")
|
||||
debug("%s: removeListener", this)
|
||||
installStage!!.removeListener(listener)
|
||||
if (consoleLog)
|
||||
println("$this: Listener removed")
|
||||
debug("%s: Listener removed", this)
|
||||
} else if (!listenerInstalled && !(isEmpty() || isPaused)) {
|
||||
if (consoleLog)
|
||||
println("$this: addListener")
|
||||
debug("%s: addListener", this)
|
||||
installStage!!.addListener(listener)
|
||||
listenerInstalled = true
|
||||
if (consoleLog)
|
||||
println("$this: Listener added")
|
||||
debug("%s: Listener added", this)
|
||||
}
|
||||
}
|
||||
|
||||
@ -255,9 +248,6 @@ class KeyPressDispatcher(val name: String? = null) : HashMap<KeyCharAndCode, (()
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Control debug logging
|
||||
private const val consoleLog = false
|
||||
|
||||
/** Tests presence of a physical keyboard - static here as convenience shortcut only */
|
||||
val keyboardAvailable = Gdx.input.isPeripheralAvailable(Input.Peripheral.HardwareKeyboard)
|
||||
}
|
||||
|
214
core/src/com/unciv/ui/utils/Log.kt
Normal file
214
core/src/com/unciv/ui/utils/Log.kt
Normal file
@ -0,0 +1,214 @@
|
||||
package com.unciv.utils
|
||||
|
||||
import java.time.Instant
|
||||
import java.util.regex.Pattern
|
||||
|
||||
|
||||
/**
|
||||
* If you wonder why something isn't logged, it's probably because it's in the [disableLogsFrom] field.
|
||||
*
|
||||
* To stop logging/start logging classes, you have these options:
|
||||
*
|
||||
* 1. Edit the set [disableLogsFrom] here in the source code
|
||||
* 2. Use a Java system property `-DnoLog=<comma-separated-list-of-class-names>` to overwrite [disableLogsFrom] completely
|
||||
* (potentially copy/pasting the default class list from here and adjusting to your liking)
|
||||
* 3. While the application is running, set a breakpoint somewhere and do a "Watch"/"Evaluate expression" with `Log.disableLogsFrom.add/remove("Something")`
|
||||
*/
|
||||
object Log {
|
||||
|
||||
/** Add -DnoLog=<comma-separated-list-of-class-names> to not log these classes. */
|
||||
private val disabledLogsFromProperty = System.getProperty("noLog")?.split(',')?.toMutableSet() ?: mutableSetOf()
|
||||
|
||||
/** Log tags (= class names) **containing** these Strings will not be logged. */
|
||||
val disableLogsFrom = if (disabledLogsFromProperty.isEmpty()) {
|
||||
"Battle,KeyPressDispatcher,Music,Sounds,Translations,WorkerAutomation"
|
||||
.split(',').toMutableSet()
|
||||
} else {
|
||||
disabledLogsFromProperty
|
||||
}
|
||||
|
||||
var backend: LogBackend = DefaultLogBackend()
|
||||
|
||||
fun shouldLog(tag: Tag = getTag()): Boolean {
|
||||
return !backend.isRelease() && !isTagDisabled(tag)
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the given message by [java.lang.String.format]ting them with an optional list of [params].
|
||||
*
|
||||
* Only actually does something when logging is enabled.
|
||||
*
|
||||
* A tag will be added equal to the name of the calling class.
|
||||
*
|
||||
* The [params] can contain value-producing lambdas, which will be called and their value used as parameter for the message instead.
|
||||
*/
|
||||
fun debug(msg: String, vararg params: Any?) {
|
||||
if (backend.isRelease()) return
|
||||
debug(getTag(), msg, *params)
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the given message by [java.lang.String.format]ting them with an optional list of [params].
|
||||
*
|
||||
* Only actually does something when logging is enabled.
|
||||
*
|
||||
* The [params] can contain value-producing lambdas, which will be called and their value used as parameter for the message instead.
|
||||
*/
|
||||
fun debug(tag: Tag, msg: String, vararg params: Any?) {
|
||||
if (!shouldLog(tag)) return
|
||||
val formatArgs = replaceLambdasWithValues(params)
|
||||
doLog(backend::debug, tag, msg, *formatArgs)
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the given [throwable] by appending it to [msg]
|
||||
*
|
||||
* Only actually does something when logging is enabled.
|
||||
*
|
||||
* A tag will be added equal to the name of the calling class.
|
||||
*/
|
||||
fun debug(msg: String, throwable: Throwable) {
|
||||
if (backend.isRelease()) return
|
||||
debug(getTag(), msg, throwable)
|
||||
}
|
||||
/**
|
||||
* Logs the given [throwable] by appending it to [msg]
|
||||
*
|
||||
* Only actually does something when logging is enabled.
|
||||
*/
|
||||
fun debug(tag: Tag, msg: String, throwable: Throwable) {
|
||||
if (!shouldLog(tag)) return
|
||||
doLog(backend::debug, tag, buildThrowableMessage(msg, throwable))
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the given error message by [java.lang.String.format]ting them with an optional list of [params].
|
||||
*
|
||||
* Always logs, even in release builds.
|
||||
*
|
||||
* A tag will be added equal to the name of the calling class.
|
||||
*
|
||||
* The [params] can contain value-producing lambdas, which will be called and their value used as parameter for the message instead.
|
||||
*/
|
||||
fun error(msg: String, vararg params: Any?) {
|
||||
error(getTag(), msg, *params)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Logs the given error message by [java.lang.String.format]ting them with an optional list of [params].
|
||||
*
|
||||
* Always logs, even in release builds.
|
||||
*
|
||||
* The [params] can contain value-producing lambdas, which will be called and their value used as parameter for the message instead.
|
||||
*/
|
||||
fun error(tag: Tag, msg: String, vararg params: Any?) {
|
||||
val formatArgs = replaceLambdasWithValues(params)
|
||||
doLog(backend::error, tag, msg, *formatArgs)
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the given [throwable] by appending it to [msg]
|
||||
*
|
||||
* Always logs, even in release builds.
|
||||
*
|
||||
* A tag will be added equal to the name of the calling class.
|
||||
*/
|
||||
fun error(msg: String, throwable: Throwable) {
|
||||
error(getTag(), msg, throwable)
|
||||
}
|
||||
/**
|
||||
* Logs the given [throwable] by appending it to [msg]
|
||||
*
|
||||
* Always logs, even in release builds.
|
||||
*/
|
||||
fun error(tag: Tag, msg: String, throwable: Throwable) {
|
||||
doLog(backend::error, tag, buildThrowableMessage(msg, throwable))
|
||||
}
|
||||
}
|
||||
|
||||
class Tag(val name: String)
|
||||
|
||||
interface LogBackend {
|
||||
fun debug(tag: Tag, curThreadName: String, msg: String)
|
||||
fun error(tag: Tag, curThreadName: String, msg: String)
|
||||
|
||||
/** Do not log on release builds for performance reasons. */
|
||||
fun isRelease(): Boolean
|
||||
}
|
||||
|
||||
/** Only for tests, or temporary main() functions */
|
||||
open class DefaultLogBackend : LogBackend {
|
||||
override fun debug(tag: Tag, curThreadName: String, msg: String) {
|
||||
println("${Instant.now()} [${curThreadName}] [${tag.name}] $msg")
|
||||
}
|
||||
|
||||
override fun error(tag: Tag, curThreadName: String, msg: String) {
|
||||
println("${Instant.now()} [${curThreadName}] [${tag.name}] [ERROR] $msg")
|
||||
}
|
||||
|
||||
override fun isRelease(): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/** Shortcut for [Log.debug] */
|
||||
fun debug(msg: String, vararg params: Any?) {
|
||||
Log.debug(msg, *params)
|
||||
}
|
||||
|
||||
/** Shortcut for [Log.debug] */
|
||||
fun debug(tag: Tag, msg: String, vararg params: Any?) {
|
||||
Log.debug(tag, msg, *params)
|
||||
}
|
||||
|
||||
/** Shortcut for [Log.debug] */
|
||||
fun debug(msg: String, throwable: Throwable) {
|
||||
Log.debug(msg, throwable)
|
||||
}
|
||||
|
||||
/** Shortcut for [Log.debug] */
|
||||
fun debug(tag: Tag, msg: String, throwable: Throwable) {
|
||||
Log.debug(tag, msg, throwable)
|
||||
}
|
||||
|
||||
private fun doLog(logger: (Tag, String, String) -> Unit, tag: Tag, msg: String, vararg params: Any?) {
|
||||
logger(tag, Thread.currentThread().name, msg.format(*params))
|
||||
}
|
||||
|
||||
private fun isTagDisabled(tag: Tag): Boolean {
|
||||
return Log.disableLogsFrom.any { it in tag.name }
|
||||
}
|
||||
|
||||
private fun buildThrowableMessage(msg: String, throwable: Throwable): String {
|
||||
return "$msg | ${throwable.stackTraceToString()}"
|
||||
}
|
||||
|
||||
private fun replaceLambdasWithValues(params: Array<out Any?>): Array<out Any?> {
|
||||
var out: Array<Any?>? = null
|
||||
for (i in 0 until params.size) {
|
||||
val param = params[i]
|
||||
if (param is Function0<*>) {
|
||||
if (out == null) out = arrayOf(*params)
|
||||
out[i] = param.invoke()
|
||||
}
|
||||
}
|
||||
return out ?: params
|
||||
}
|
||||
|
||||
|
||||
private fun getTag(): Tag {
|
||||
val firstOutsideStacktrace = Throwable().stackTrace.filter { "com.unciv.utils.Log" !in it.className }.first()
|
||||
val simpleClassName = firstOutsideStacktrace.className.substringAfterLast('.')
|
||||
return Tag(removeAnonymousSuffix(simpleClassName))
|
||||
}
|
||||
|
||||
private val ANONYMOUS_CLASS_PATTERN = Pattern.compile("(\\$\\d+)+$") // all "$123" at the end of the class name
|
||||
private fun removeAnonymousSuffix(tag: String): String {
|
||||
val matcher = ANONYMOUS_CLASS_PATTERN.matcher(tag)
|
||||
return if (matcher.find()) {
|
||||
matcher.replaceAll("")
|
||||
} else {
|
||||
tag
|
||||
}
|
||||
}
|
@ -34,6 +34,7 @@ import com.unciv.ui.tilegroups.TileGroup
|
||||
import com.unciv.ui.tilegroups.TileSetStrings
|
||||
import com.unciv.ui.tilegroups.WorldTileGroup
|
||||
import com.unciv.ui.utils.*
|
||||
import com.unciv.utils.Log
|
||||
|
||||
|
||||
class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap: TileMap): ZoomableScrollPane() {
|
||||
@ -226,8 +227,7 @@ class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap
|
||||
try {
|
||||
tileToMoveTo = selectedUnit.movement.getTileToMoveToThisTurn(targetTile)
|
||||
} catch (ex: Exception) {
|
||||
println("Exception in getTileToMoveToThisTurn: ${ex.message}")
|
||||
ex.printStackTrace()
|
||||
Log.error("Exception in getTileToMoveToThisTurn", ex)
|
||||
return@launchCrashHandling
|
||||
} // can't move here
|
||||
|
||||
@ -251,8 +251,7 @@ class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap
|
||||
moveUnitToTargetTile(selectedUnits.subList(1, selectedUnits.size), targetTile)
|
||||
} else removeUnitActionOverlay() //we're done here
|
||||
} catch (ex: Exception) {
|
||||
println("Exception in moveUnitToTargetTile: ${ex.message}")
|
||||
ex.printStackTrace()
|
||||
Log.error("Exception in moveUnitToTargetTile", ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import com.badlogic.gdx.utils.Align
|
||||
import com.unciv.Constants
|
||||
import com.unciv.MainMenuScreen
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.utils.debug
|
||||
import com.unciv.logic.GameInfo
|
||||
import com.unciv.logic.civilization.CivilizationInfo
|
||||
import com.unciv.logic.civilization.ReligionState
|
||||
@ -128,10 +129,6 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas
|
||||
|
||||
private val events = EventBus.EventReceiver()
|
||||
|
||||
companion object {
|
||||
/** Switch for console logging of next turn duration */
|
||||
private const val consoleLog = false
|
||||
}
|
||||
|
||||
init {
|
||||
topBar.setPosition(0f, stage.height - topBar.height)
|
||||
@ -377,7 +374,11 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas
|
||||
}
|
||||
|
||||
try {
|
||||
debug("loadLatestMultiplayerState current game: gameId: %s, turn: %s, curCiv: %s",
|
||||
game.worldScreen.gameInfo.gameId, game.worldScreen.gameInfo.turns, game.worldScreen.gameInfo.currentPlayer)
|
||||
val latestGame = game.onlineMultiplayer.downloadGame(gameInfo.gameId)
|
||||
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()
|
||||
}
|
||||
@ -660,8 +661,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas
|
||||
|
||||
// on a separate thread so the user can explore their world while we're passing the turn
|
||||
nextTurnUpdateJob = launchCrashHandling("NextTurn", runAsDaemon = false) {
|
||||
if (consoleLog)
|
||||
println("\nNext turn starting " + Date().formatDate())
|
||||
debug("Next turn starting")
|
||||
val startTime = System.currentTimeMillis()
|
||||
val originalGameInfo = gameInfo
|
||||
val gameInfoClone = originalGameInfo.clone()
|
||||
@ -693,8 +693,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas
|
||||
return@launchCrashHandling
|
||||
|
||||
this@WorldScreen.game.gameInfo = gameInfoClone
|
||||
if (consoleLog)
|
||||
println("Next turn took ${System.currentTimeMillis()-startTime}ms")
|
||||
debug("Next turn took %sms", System.currentTimeMillis() - startTime)
|
||||
|
||||
val shouldAutoSave = gameInfoClone.turns % game.settings.turnsBetweenAutosaves == 0
|
||||
|
||||
|
@ -3,6 +3,7 @@ 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
|
||||
import com.unciv.logic.map.MapParameters
|
||||
@ -22,6 +23,7 @@ internal object ConsoleLauncher {
|
||||
@ExperimentalTime
|
||||
@JvmStatic
|
||||
fun main(arg: Array<String>) {
|
||||
Log.backend = DesktopLogBackend()
|
||||
|
||||
val version = "0.1"
|
||||
val consoleParameters = UncivGameParameters(
|
||||
|
@ -9,8 +9,9 @@ import com.badlogic.gdx.graphics.glutils.HdpiMode
|
||||
import com.sun.jna.Native
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.UncivGameParameters
|
||||
import com.unciv.utils.Log
|
||||
import com.unciv.utils.debug
|
||||
import com.unciv.logic.GameSaver
|
||||
import com.unciv.models.metadata.GameSettings
|
||||
import com.unciv.ui.utils.Fonts
|
||||
import java.util.*
|
||||
import kotlin.concurrent.timer
|
||||
@ -20,6 +21,7 @@ internal object DesktopLauncher {
|
||||
|
||||
@JvmStatic
|
||||
fun main(arg: Array<String>) {
|
||||
Log.backend = DesktopLogBackend()
|
||||
// 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")
|
||||
@ -85,7 +87,7 @@ internal object DesktopLauncher {
|
||||
}
|
||||
} 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.
|
||||
println("Could not initialize Discord")
|
||||
debug("Could not initialize Discord")
|
||||
}
|
||||
}
|
||||
|
||||
|
16
desktop/src/com/unciv/app/desktop/DesktopLogBackend.kt
Normal file
16
desktop/src/com/unciv/app/desktop/DesktopLogBackend.kt
Normal file
@ -0,0 +1,16 @@
|
||||
package com.unciv.app.desktop
|
||||
|
||||
import com.unciv.utils.DefaultLogBackend
|
||||
import java.lang.management.ManagementFactory
|
||||
|
||||
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")
|
||||
&& System.getProperty("kotlinx.coroutines.debug") == null
|
||||
|
||||
override fun isRelease(): Boolean {
|
||||
return release
|
||||
}
|
||||
}
|
@ -3,15 +3,17 @@ package com.unciv.app.desktop
|
||||
import com.badlogic.gdx.graphics.Texture
|
||||
import com.badlogic.gdx.tools.texturepacker.TexturePacker
|
||||
import com.badlogic.gdx.utils.Json
|
||||
import com.unciv.utils.Log
|
||||
import com.unciv.utils.debug
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Entry point: _ImagePacker.[packImages] ()_
|
||||
*
|
||||
*
|
||||
* Re-packs our texture assets into atlas + png File pairs, which will be loaded by the game.
|
||||
* With the exception of the ExtraImages folder and the Font system these are the only
|
||||
* graphics used (The source Image folders are unused at run time except here).
|
||||
*
|
||||
*
|
||||
* [TexturePacker] documentation is [here](https://github.com/libgdx/libgdx/wiki/Texture-packer)
|
||||
*/
|
||||
internal object ImagePacker {
|
||||
@ -68,14 +70,14 @@ internal object ImagePacker {
|
||||
try {
|
||||
packImagesPerMod(mod.path, mod.path, defaultSettings)
|
||||
} catch (ex: Throwable) {
|
||||
println("Exception in ImagePacker: ${ex.message}")
|
||||
Log.error("Exception in ImagePacker: %s", ex.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val texturePackingTime = System.currentTimeMillis() - startTime
|
||||
println("Packing textures - " + texturePackingTime + "ms")
|
||||
debug("Packing textures - %sms", texturePackingTime)
|
||||
}
|
||||
|
||||
// Scan multiple image folders and generate an atlas for each - if outdated
|
||||
|
@ -7,6 +7,7 @@ import com.sun.jna.Pointer
|
||||
import com.sun.jna.platform.win32.User32
|
||||
import com.sun.jna.platform.win32.WinNT
|
||||
import com.sun.jna.platform.win32.WinUser
|
||||
import com.unciv.utils.Log
|
||||
import org.lwjgl.glfw.GLFWNativeWin32
|
||||
|
||||
class MultiplayerTurnNotifierDesktop: Lwjgl3WindowAdapter() {
|
||||
@ -18,7 +19,7 @@ class MultiplayerTurnNotifierDesktop: Lwjgl3WindowAdapter() {
|
||||
null
|
||||
}
|
||||
} catch (e: UnsatisfiedLinkError) {
|
||||
println("Error while initializing turn notifier: " + e.message)
|
||||
Log.error("Error while initializing turn notifier", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
@ -58,7 +59,7 @@ class MultiplayerTurnNotifierDesktop: Lwjgl3WindowAdapter() {
|
||||
user32.FlashWindowEx(flashwinfo)
|
||||
} catch (e: Throwable) {
|
||||
/** try to ignore even if we get an [Error], just log it */
|
||||
println("Error while notifying the user of their turn: " + e.message)
|
||||
Log.error("Error while notifying the user of their turn", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,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.debug
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
@ -67,7 +68,7 @@ class BasicTests {
|
||||
var allObsoletingUnitsHaveUpgrades = true
|
||||
for (unit in units) {
|
||||
if (unit.obsoleteTech != null && unit.upgradesTo == null && unit.name !="Scout" ) {
|
||||
println(unit.name + " obsoletes but has no upgrade")
|
||||
debug(unit.name + " obsoletes but has no upgrade")
|
||||
allObsoletingUnitsHaveUpgrades = false
|
||||
}
|
||||
}
|
||||
@ -93,7 +94,7 @@ class BasicTests {
|
||||
val ruleset = RulesetCache[baseRuleset.fullName]!!
|
||||
val modCheck = ruleset.checkModLinks()
|
||||
if (modCheck.isNotOK())
|
||||
println(modCheck.getErrorText(true))
|
||||
debug(modCheck.getErrorText(true))
|
||||
Assert.assertFalse(modCheck.isNotOK())
|
||||
}
|
||||
}
|
||||
@ -106,7 +107,7 @@ class BasicTests {
|
||||
for (paramType in entry.value) {
|
||||
if (paramType == UniqueParameterType.Unknown) {
|
||||
val badParam = uniqueType.text.getPlaceholderParameters()[entry.index]
|
||||
println("${uniqueType.name} param[${entry.index}] type \"$badParam\" is unknown")
|
||||
debug("${uniqueType.name} param[${entry.index}] type \"$badParam\" is unknown")
|
||||
noUnknownParameters = false
|
||||
}
|
||||
}
|
||||
@ -120,7 +121,7 @@ class BasicTests {
|
||||
var allOK = true
|
||||
for (uniqueType in UniqueType.values()) {
|
||||
if (uniqueType.targetTypes.isEmpty()) {
|
||||
println("${uniqueType.name} has no targets.")
|
||||
debug("${uniqueType.name} has no targets.")
|
||||
allOK = false
|
||||
}
|
||||
}
|
||||
@ -134,7 +135,7 @@ class BasicTests {
|
||||
for (unit in units) {
|
||||
for (unique in unit.uniques) {
|
||||
if (!UniqueType.values().any { it.placeholderText == unique.getPlaceholderText() }) {
|
||||
println("${unit.name}: $unique")
|
||||
debug("${unit.name}: $unique")
|
||||
allOK = false
|
||||
}
|
||||
}
|
||||
@ -149,7 +150,7 @@ class BasicTests {
|
||||
for (building in buildings) {
|
||||
for (unique in building.uniques) {
|
||||
if (!UniqueType.values().any { it.placeholderText == unique.getPlaceholderText() }) {
|
||||
println("${building.name}: $unique")
|
||||
debug("${building.name}: $unique")
|
||||
allOK = false
|
||||
}
|
||||
}
|
||||
@ -164,7 +165,7 @@ class BasicTests {
|
||||
for (promotion in promotions) {
|
||||
for (unique in promotion.uniques) {
|
||||
if (!UniqueType.values().any { it.placeholderText == unique.getPlaceholderText() }) {
|
||||
println("${promotion.name}: $unique")
|
||||
debug("${promotion.name}: $unique")
|
||||
allOK = false
|
||||
}
|
||||
}
|
||||
@ -182,7 +183,7 @@ class BasicTests {
|
||||
for (obj in objects) {
|
||||
for (unique in obj.uniques) {
|
||||
if (!UniqueType.values().any { it.placeholderText == unique.getPlaceholderText() }) {
|
||||
println("${obj.name}: $unique")
|
||||
debug("${obj.name}: $unique")
|
||||
allOK = false
|
||||
}
|
||||
}
|
||||
@ -195,24 +196,24 @@ class BasicTests {
|
||||
var allOK = true
|
||||
for (uniqueType in UniqueType.values()) {
|
||||
val deprecationAnnotation = uniqueType.getDeprecationAnnotation() ?: continue
|
||||
|
||||
|
||||
val uniquesToCheck = deprecationAnnotation.replaceWith.expression.split("\", \"", Constants.uniqueOrDelimiter)
|
||||
|
||||
|
||||
for (uniqueText in uniquesToCheck) {
|
||||
val replacementTextUnique = Unique(uniqueText)
|
||||
|
||||
|
||||
if (replacementTextUnique.type == null) {
|
||||
println("${uniqueType.name}'s deprecation text \"$uniqueText\" does not match any existing type!")
|
||||
debug("${uniqueType.name}'s deprecation text \"$uniqueText\" does not match any existing type!")
|
||||
allOK = false
|
||||
}
|
||||
if (replacementTextUnique.type == uniqueType) {
|
||||
println("${uniqueType.name}'s deprecation text references itself!")
|
||||
debug("${uniqueType.name}'s deprecation text references itself!")
|
||||
allOK = false
|
||||
}
|
||||
for (conditional in replacementTextUnique.conditionals) {
|
||||
if (conditional.type == null) {
|
||||
println("${uniqueType.name}'s deprecation text contains conditional \"${conditional.text}\" which does not match any existing type!")
|
||||
debug("${uniqueType.name}'s deprecation text contains conditional \"${conditional.text}\" which does not match any existing type!")
|
||||
allOK = false
|
||||
}
|
||||
}
|
||||
@ -222,7 +223,7 @@ class BasicTests {
|
||||
while (replacementUnique.getDeprecationAnnotation() != null) {
|
||||
if (iteration == 10) {
|
||||
allOK = false
|
||||
println("${uniqueType.name}'s deprecation text never references an undeprecated unique!")
|
||||
debug("${uniqueType.name}'s deprecation text never references an undeprecated unique!")
|
||||
break
|
||||
}
|
||||
iteration++
|
||||
@ -240,7 +241,7 @@ class BasicTests {
|
||||
Thread.sleep(5000) // makes timings a little more repeatable
|
||||
val startTime = System.nanoTime()
|
||||
statMathRunner(iterations = 1_000_000)
|
||||
println("statMathStressTest took ${(System.nanoTime()-startTime)/1000}µs")
|
||||
debug("statMathStressTest took ${(System.nanoTime()-startTime)/1000}µs")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -16,6 +16,7 @@ import com.unciv.models.metadata.Player
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
import com.unciv.models.metadata.GameSetupInfo
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.utils.debug
|
||||
import org.junit.After
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
@ -111,7 +112,7 @@ class SerializationTests {
|
||||
val pattern = """\{(\w+)\${'$'}delegate:\{class:kotlin.SynchronizedLazyImpl,"""
|
||||
val matches = Regex(pattern).findAll(json)
|
||||
matches.forEach {
|
||||
println("Lazy missing `@delegate:Transient` annotation: " + it.groups[1]!!.value)
|
||||
debug("Lazy missing `@delegate:Transient` annotation: " + it.groups[1]!!.value)
|
||||
}
|
||||
val result = matches.any()
|
||||
Assert.assertFalse("This test will only pass when no serializable lazy fields are found", result)
|
||||
|
@ -7,6 +7,7 @@ import com.unciv.models.metadata.GameSettings
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
import com.unciv.models.translations.*
|
||||
import com.unciv.utils.debug
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
@ -39,13 +40,13 @@ class TranslationTests {
|
||||
translations.size > 0)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// This test is incorrectly defined: it should read from the template.properties file and not fro the final translation files.
|
||||
// @Test
|
||||
// fun allUnitActionsHaveTranslation() {
|
||||
// val actions: MutableSet<String> = HashSet()
|
||||
// for (action in UnitActionType.values()) {
|
||||
// actions.add(
|
||||
// actions.add(
|
||||
// when(action) {
|
||||
// UnitActionType.Upgrade -> "Upgrade to [unitType] ([goldCost] gold)"
|
||||
// UnitActionType.Create -> "Create [improvement]"
|
||||
@ -95,7 +96,7 @@ class TranslationTests {
|
||||
for (placeholder in placeholders) {
|
||||
if (!output.contains(placeholder)) {
|
||||
allTranslationsHaveCorrectPlaceholders = false
|
||||
println("Placeholder `$placeholder` not found in `$language` for entry `$translationEntry`")
|
||||
debug("Placeholder `$placeholder` not found in `$language` for entry `$translationEntry`")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -115,7 +116,7 @@ class TranslationTests {
|
||||
val keyFromEntry = translationEntry.replace(squareBraceRegex, "[]")
|
||||
if (key != keyFromEntry) {
|
||||
allPlaceholderKeysMatchEntry = false
|
||||
println("Entry $translationEntry found under bad key $key")
|
||||
debug("Entry $translationEntry found under bad key $key")
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -135,7 +136,7 @@ class TranslationTests {
|
||||
for (placeholder in placeholders)
|
||||
if (placeholders.count { it == placeholder } > 1) {
|
||||
noTwoPlaceholdersAreTheSame = false
|
||||
println("Entry $translationEntry has the parameter $placeholder more than once")
|
||||
debug("Entry $translationEntry has the parameter $placeholder more than once")
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -151,7 +152,7 @@ class TranslationTests {
|
||||
var failed = false
|
||||
for (line in templateLines) {
|
||||
if (line.endsWith(" =")) {
|
||||
println("$line ends without a space at the end")
|
||||
debug("$line ends without a space at the end")
|
||||
failed = true
|
||||
}
|
||||
}
|
||||
@ -176,7 +177,7 @@ class TranslationTests {
|
||||
translationEntry.entry.tr()
|
||||
} catch (ex: Exception) {
|
||||
allWordsTranslatedCorrectly = false
|
||||
println("Crashed when translating ${translationEntry.entry} to $language")
|
||||
debug("Crashed when translating ${translationEntry.entry} to $language")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -185,11 +186,11 @@ class TranslationTests {
|
||||
allWordsTranslatedCorrectly
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun wordBoundaryTranslationIsFormattedCorrectly() {
|
||||
val translationEntry = translations["\" \""]!!
|
||||
|
||||
|
||||
var allTranslationsCheckedOut = true
|
||||
for ((language, translation) in translationEntry) {
|
||||
if (!translation.startsWith("\"")
|
||||
@ -197,10 +198,10 @@ class TranslationTests {
|
||||
|| translation.count { it == '\"' } != 2
|
||||
) {
|
||||
allTranslationsCheckedOut = false
|
||||
println("Translation of the word boundary in $language was incorrectly formatted")
|
||||
debug("Translation of the word boundary in $language was incorrectly formatted")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Assert.assertTrue(
|
||||
"This test will only pass when the word boundrary translation succeeds",
|
||||
allTranslationsCheckedOut
|
||||
@ -210,18 +211,18 @@ class TranslationTests {
|
||||
|
||||
@Test
|
||||
fun translationParameterExtractionForNestedBracesWorks() {
|
||||
Assert.assertEquals(listOf("New [York]"),
|
||||
Assert.assertEquals(listOf("New [York]"),
|
||||
"The city of [New [York]]".getPlaceholderParametersIgnoringLowerLevelBraces())
|
||||
|
||||
// Closing braces without a matching opening brace - 'level 0' - are ignored
|
||||
Assert.assertEquals(listOf("New [York]"),
|
||||
"The city of [New [York]]]".getPlaceholderParametersIgnoringLowerLevelBraces())
|
||||
|
||||
|
||||
// Opening braces without a matching closing brace mean that the term is never 'closed'
|
||||
// so there are no parameters
|
||||
Assert.assertEquals(listOf<String>(),
|
||||
"The city of [[New [York]".getPlaceholderParametersIgnoringLowerLevelBraces())
|
||||
|
||||
|
||||
// Supernesting
|
||||
val superNestedString = "The brother of [[my [best friend]] and [[America]'s greatest [Dad]]]"
|
||||
Assert.assertEquals(listOf("[my [best friend]] and [[America]'s greatest [Dad]]"),
|
||||
@ -239,8 +240,8 @@ class TranslationTests {
|
||||
}
|
||||
addTranslation("The brother of [person]", "The sibling of [person]")
|
||||
Assert.assertEquals("The sibling of bob", "The brother of [bob]".tr())
|
||||
|
||||
|
||||
|
||||
|
||||
addTranslation("[a] and [b]", "[a] and indeed [b]")
|
||||
addTranslation("my [whatever]", "mine own [whatever]")
|
||||
addTranslation("[place]'s greatest [job]", "the greatest [job] in [place]")
|
||||
@ -248,11 +249,11 @@ class TranslationTests {
|
||||
addTranslation("best friend", "closest ally")
|
||||
addTranslation("America", "The old British colonies")
|
||||
|
||||
println("[Dad] and [my [best friend]]".getPlaceholderText())
|
||||
debug("[Dad] and [my [best friend]]".getPlaceholderText())
|
||||
Assert.assertEquals(listOf("Dad","my [best friend]"),
|
||||
"[Dad] and [my [best friend]]".getPlaceholderParametersIgnoringLowerLevelBraces())
|
||||
Assert.assertEquals("Father and indeed mine own closest ally", "[Dad] and [my [best friend]]".tr())
|
||||
|
||||
|
||||
// Reminder: "The brother of [[my [best friend]] and [[America]'s greatest [Dad]]]"
|
||||
Assert.assertEquals("The sibling of mine own closest ally and indeed the greatest Father in The old British colonies",
|
||||
superNestedString.tr())
|
||||
@ -264,7 +265,7 @@ class TranslationTests {
|
||||
// val orderedConditionals = Translations.englishConditionalOrderingString
|
||||
// val orderedConditionalsSet = orderedConditionals.getConditionals().map { it.placeholderText }
|
||||
// val translationEntry = translations[orderedConditionals]!!
|
||||
//
|
||||
//
|
||||
// var allTranslationsCheckedOut = true
|
||||
// for ((language, translation) in translationEntry) {
|
||||
// val translationConditionals = translation.getConditionals().map { it.placeholderText }
|
||||
@ -275,7 +276,7 @@ class TranslationTests {
|
||||
// println("Not all or double parameters found in the conditional ordering for $language")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// Assert.assertTrue(
|
||||
// "This test will only pass when each of the conditionals exists exactly once in the translations for the conditional ordering",
|
||||
// allTranslationsCheckedOut
|
||||
|
Reference in New Issue
Block a user