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:
Timo T
2022-05-27 12:45:13 +02:00
committed by GitHub
parent c48c6df22a
commit 214fae6f59
42 changed files with 545 additions and 284 deletions

View File

@ -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)

View 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)
}
}

View File

@ -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)
}
}

View File

@ -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()

View File

@ -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)
}
}

View File

@ -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) }

View File

@ -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

View File

@ -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))

View File

@ -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){
}
}
}
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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!")
}
/*

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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() {

View File

@ -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"

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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())
}

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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()
}
}

View File

@ -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
}

View File

@ -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,

View File

@ -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)
}

View 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
}
}

View File

@ -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)
}
}
}

View File

@ -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

View File

@ -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(

View File

@ -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")
}
}

View 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
}
}

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -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

View File

@ -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)

View File

@ -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