diff --git a/.gitignore b/.gitignore index 62d35ff697..678dd0a72e 100644 --- a/.gitignore +++ b/.gitignore @@ -129,3 +129,4 @@ android/android-release.apk android/assets/GameSettings.json android/release/output.json android/release/android-release.apk +android/release/release/android.aab diff --git a/android/assets/jsons/Translations.json b/android/assets/jsons/Translations.json index 0b25e70486..bd636a9eaa 100644 --- a/android/assets/jsons/Translations.json +++ b/android/assets/jsons/Translations.json @@ -3038,6 +3038,7 @@ } //world size + "Tiny":{} "Small":{ Italian:"Piccolo" Russian:"Маленький" diff --git a/android/build.gradle b/android/build.gradle index 0d438f671b..10bda086b2 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -21,8 +21,8 @@ android { applicationId "com.unciv.game" minSdkVersion 14 targetSdkVersion 28 - versionCode 174 - versionName "2.10.11" + versionCode 175 + versionName "2.10.12" } // Had to add this crap for Travis to build, it wanted to sign the app diff --git a/android/release/release/android.aab b/android/release/release/android.aab deleted file mode 100644 index e5e87deb5e..0000000000 Binary files a/android/release/release/android.aab and /dev/null differ diff --git a/core/src/com/unciv/ui/worldscreen/TileMapHolder.kt b/core/src/com/unciv/ui/worldscreen/TileMapHolder.kt index ee6ec2edcb..b385797974 100644 --- a/core/src/com/unciv/ui/worldscreen/TileMapHolder.kt +++ b/core/src/com/unciv/ui/worldscreen/TileMapHolder.kt @@ -17,6 +17,7 @@ import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileMap import com.unciv.ui.tilegroups.WorldTileGroup import com.unciv.ui.utils.* +import kotlin.concurrent.thread class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap: TileMap) : ScrollPane(null) { internal var selectedTile: TileInfo? = null @@ -25,6 +26,11 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap: var moveToOverlay :Actor?=null val cityButtonOverlays = ArrayList() + + // Used to transfer data on the "move here" button that should be created, from the side thread to the main thread + class MoveHereButtonDto(val unit: MapUnit, val tileInfo: TileInfo, val turnsToGetThere: Int) + var moveHereButtonDto :MoveHereButtonDto?=null + internal fun addTiles() { val allTiles = Group() val groupPadding = 300f // This is so that no tile will be stuck "on the side" and be unreachable or difficult to reach @@ -59,7 +65,7 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap: // so we zero out the starting position of the whole board so they will be displayed as well allTiles.setSize(topX - bottomX + groupPadding*2, topY - bottomY + groupPadding*2) - widget = allTiles + actor = allTiles setFillParent(true) setOrigin(worldScreen.stage.width/2,worldScreen.stage.height/2) setSize(worldScreen.stage.width, worldScreen.stage.height) @@ -96,45 +102,57 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap: if (selectedUnit != null && selectedUnit.getTile() != tileInfo && selectedUnit.canMoveTo(tileInfo) && selectedUnit.movementAlgs().canReach(tileInfo)) { // this can take a long time, because of the unit-to-tile calculation needed, so we put it in a different thread - kotlin.concurrent.thread { - addMoveHereButtonToTile(selectedUnit, tileInfo, tileGroup) - } + queueAddMoveHereButton(selectedUnit, tileInfo) } worldScreen.bottomBar.unitTable.tileSelected(tileInfo) worldScreen.shouldUpdate=true } - private fun addMoveHereButtonToTile(selectedUnit: MapUnit, tileInfo: TileInfo, tileGroup: WorldTileGroup) { + private fun queueAddMoveHereButton(selectedUnit: MapUnit, tileInfo: TileInfo) { + thread { + /** LibGdx sometimes has these wierd errors when you try to edit the UI layout from 2 separate thread. + * And so, all UI editing will be done on the main thread. + * The only "heavy lifting" that needs to be done is getting the turns to get there, + * so that and that alone will be relegated to the concurrent thread. + */ + val turnsToGetThere = selectedUnit.movementAlgs().getShortestPath(tileInfo).size // this is what takes the most time, tbh + moveHereButtonDto = MoveHereButtonDto(selectedUnit, tileInfo, turnsToGetThere) + worldScreen.shouldUpdate = true // when the world screen updates, is calls our updateTiles, + // which will add the move here button *on the main thread*! Problem solved! + } + } + + + private fun addMoveHereButtonToTile(dto: MoveHereButtonDto, tileGroup: WorldTileGroup) { val size = 60f val moveHereButton = Group().apply { width = size;height = size; } moveHereButton.addActor(ImageGetter.getCircle().apply { width = size; height = size }) moveHereButton.addActor(ImageGetter.getStatIcon("Movement").apply { width = size / 2; height = size / 2; center(moveHereButton) }) - val turnsToGetThere = selectedUnit.movementAlgs().getShortestPath(tileInfo).size val numberCircle = ImageGetter.getCircle().apply { width = size / 2; height = size / 2;color = Color.BLUE } moveHereButton.addActor(numberCircle) - moveHereButton.addActor(Label(turnsToGetThere.toString(), CameraStageBaseScreen.skin) + moveHereButton.addActor(Label(dto.turnsToGetThere.toString(), CameraStageBaseScreen.skin) .apply { center(numberCircle); setFontColor(Color.WHITE) }) - val unitIcon = UnitGroup(selectedUnit, size / 2) + val unitIcon = UnitGroup(dto.unit, size / 2) unitIcon.y = size - unitIcon.height moveHereButton.addActor(unitIcon) - if (selectedUnit.currentMovement > 0) + if (dto.unit.currentMovement > 0) moveHereButton.onClick { // this can take a long time, because of the unit-to-tile calculation needed, so we put it in a different thread kotlin.concurrent.thread { - if (selectedUnit.movementAlgs().canReach(tileInfo)) { + if (dto.unit.movementAlgs().canReach(dto.tileInfo)) { try { // Because this is darned concurrent (as it MUST be to avoid ANRs), // there are edge cases where the canReach is true, // but until it reaches the headTowards the board has changed and so the headTowards fails. // I can't think of any way to avoid this, // but it's so rare and edge-case-y that ignoring its failure is actually acceptable, hence the empty catch - selectedUnit.movementAlgs().headTowards(tileInfo) - if (selectedUnit.currentTile != tileInfo) - selectedUnit.action = "moveTo " + tileInfo.position.x.toInt() + "," + tileInfo.position.y.toInt() + dto.unit.movementAlgs().headTowards(dto.tileInfo) + if (dto.unit.currentTile != dto.tileInfo) + dto.unit.action = "moveTo " + dto.tileInfo.position.x.toInt() + "," + dto.tileInfo.position.y.toInt() } catch (e:Exception){} } @@ -174,42 +192,14 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap: tileGroup.showCircle(Color.RED) // Display ALL viewable enemies with a red circle so that users don't need to go "hunting" for enemy units } + if(moveHereButtonDto!=null) + addMoveHereButtonToTile(moveHereButtonDto!!, tileGroups[moveHereButtonDto!!.tileInfo]!!) + if(worldScreen.bottomBar.unitTable.selectedUnit!=null){ val unit = worldScreen.bottomBar.unitTable.selectedUnit!! - tileGroups[unit.getTile()]!!.selectUnit(unit) - - for(tile: TileInfo in unit.getDistanceToTiles().keys) - if(unit.canMoveTo(tile)) - tileGroups[tile]!!.showCircle(colorFromRGB(0, 120, 215)) - - val unitType = unit.type - val attackableTiles: List = when{ - unitType.isCivilian() -> unit.getDistanceToTiles().keys.toList() - else -> UnitAutomation().getAttackableEnemies(unit, unit.getDistanceToTiles()).map { it.tileToAttack } - } - - - for (tile in attackableTiles.filter { - it.getUnits().isNotEmpty() - && it.getUnits().first().owner != unit.owner - && (UnCivGame.Current.viewEntireMapForDebug || playerViewableTilePositions.contains(it.position))}) { - if(unit.type.isCivilian()) tileGroups[tile]!!.hideCircle() - else { - tileGroups[tile]!!.showCircle(colorFromRGB(237, 41, 57)) - tileGroups[tile]!!.showCrosshair() - } - } - - val fadeout = if(unit.type.isCivilian()) 1f - else 0.5f - - for(tile in tileGroups.values){ - if(tile.populationImage!=null) tile.populationImage!!.color.a=fadeout - if(tile.improvementImage!=null) tile.improvementImage!!.color.a=fadeout - if(tile.resourceImage!=null) tile.resourceImage!!.color.a=fadeout - } - + updateTilegroupsForSelectedUnit(unit, playerViewableTilePositions) } + else if(moveToOverlay!=null){ moveToOverlay!!.remove() moveToOverlay=null @@ -219,6 +209,37 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap: tileGroups[selectedTile!!]!!.showCircle(Color.WHITE) } + private fun updateTilegroupsForSelectedUnit(unit: MapUnit, playerViewableTilePositions: HashSet) { + tileGroups[unit.getTile()]!!.selectUnit(unit) + + for (tile: TileInfo in unit.getDistanceToTiles().keys) + if (unit.canMoveTo(tile)) + tileGroups[tile]!!.showCircle(colorFromRGB(0, 120, 215)) + + val unitType = unit.type + val attackableTiles: List = when { + unitType.isCivilian() -> unit.getDistanceToTiles().keys.toList() + else -> UnitAutomation().getAttackableEnemies(unit, unit.getDistanceToTiles()).map { it.tileToAttack } + .filter { (UnCivGame.Current.viewEntireMapForDebug || playerViewableTilePositions.contains(it.position)) } + } + for (attackableTile in attackableTiles) { + if (unit.type.isCivilian()) tileGroups[attackableTile]!!.hideCircle() + else { + tileGroups[attackableTile]!!.showCircle(colorFromRGB(237, 41, 57)) + tileGroups[attackableTile]!!.showCrosshair() + } + } + + // Fadeout less relevant images if a military unit is selected + val fadeout = if (unit.type.isCivilian()) 1f + else 0.5f + for (tile in tileGroups.values) { + if (tile.populationImage != null) tile.populationImage!!.color.a = fadeout + if (tile.improvementImage != null) tile.improvementImage!!.color.a = fadeout + if (tile.resourceImage != null) tile.resourceImage!!.color.a = fadeout + } + } + fun setCenterPosition(vector: Vector2) { val tileGroup = tileGroups.values.first { it.tileInfo.position == vector } selectedTile = tileGroup.tileInfo