From 86eda6208a5d055f9fd391e65aa9ef585e6a9ee8 Mon Sep 17 00:00:00 2001 From: martin Date: Mon, 20 May 2019 23:44:21 +0200 Subject: [PATCH 1/8] show long-press context menu (move here, Construct road this way) --- .../com/unciv/ui/worldscreen/TileMapHolder.kt | 107 ++++++++++-------- .../ui/worldscreen/unit/UnitContextMenu.kt | 72 ++++++++++++ 2 files changed, 130 insertions(+), 49 deletions(-) create mode 100644 core/src/com/unciv/ui/worldscreen/unit/UnitContextMenu.kt diff --git a/core/src/com/unciv/ui/worldscreen/TileMapHolder.kt b/core/src/com/unciv/ui/worldscreen/TileMapHolder.kt index f90483080b..013ae76ed7 100644 --- a/core/src/com/unciv/ui/worldscreen/TileMapHolder.kt +++ b/core/src/com/unciv/ui/worldscreen/TileMapHolder.kt @@ -20,6 +20,7 @@ import com.unciv.logic.map.TileMap import com.unciv.models.gamebasics.unit.UnitType import com.unciv.ui.tilegroups.WorldTileGroup import com.unciv.ui.utils.* +import com.unciv.ui.worldscreen.unit.UnitContextMenu import kotlin.concurrent.thread class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap: TileMap) : ScrollPane(null) { @@ -41,7 +42,15 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap: val allTiles = TileGroupMap(daTileGroups,worldScreen.stage.width) for(tileGroup in tileGroups.values){ - tileGroup.onClick{ onTileClicked(tileGroup.tileInfo)} + tileGroup.addListener (object: ActorGestureListener() { + override fun tap(event: InputEvent?, x: Float, y: Float, count: Int, button: Int) { + onTileClicked(tileGroup.tileInfo) + } + override fun longPress(actor: Actor?, x: Float, y: Float): Boolean { + return onTileLongClicked(tileGroup.tileInfo) + } + + }) } actor = allTiles @@ -77,7 +86,7 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap: private fun onTileClicked(tileInfo: TileInfo) { worldScreen.displayTutorials("TileClicked") - if (unitActionOverlay != null) unitActionOverlay!!.remove() + unitActionOverlay?.remove() selectedTile = tileInfo val selectedUnit = worldScreen.bottomBar.unitTable.selectedUnit @@ -122,7 +131,7 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap: } else { // add "move to" button val moveHereButtonDto = MoveHereButtonDto(selectedUnit, tileInfo, turnsToGetThere) - addMoveHereButtonToTile(moveHereButtonDto, tileGroups[moveHereButtonDto.tileInfo]!!) + addMoveHereButtonToTile(moveHereButtonDto) } worldScreen.shouldUpdate = true } @@ -131,7 +140,7 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap: } - private fun addMoveHereButtonToTile(dto: MoveHereButtonDto, tileGroup: WorldTileGroup) { + private fun addMoveHereButtonToTile(dto: MoveHereButtonDto) { val size = 60f val moveHereButton = Group().apply { width = size;height = size; } moveHereButton.addActor(ImageGetter.getCircle().apply { width = size; height = size }) @@ -148,48 +157,45 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap: moveHereButton.addActor(unitIcon) if (dto.unit.currentMovement > 0) - moveHereButton.onClick(""){onMoveButtonClick(dto)} - - else moveHereButton.color.a = 0.5f - addOverlayOnTileGroup(tileGroup, moveHereButton) - moveHereButton.y += tileGroup.height - unitActionOverlay = moveHereButton - } - - private fun onMoveButtonClick(dto: MoveHereButtonDto) { - // this can take a long time, because of the unit-to-tile calculation needed, so we put it in a different thread - thread { - 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 - dto.unit.movementAlgs().headTowards(dto.tileInfo) - Sounds.play("whoosh") - if (dto.unit.currentTile != dto.tileInfo) - dto.unit.action = "moveTo " + dto.tileInfo.position.x.toInt() + "," + dto.tileInfo.position.y.toInt() - if(dto.unit.currentMovement>0){ - worldScreen.bottomBar.unitTable.selectedUnit=dto.unit - } - } catch (e: Exception) { - } + moveHereButton.onClick(""){ + UnitContextMenu(this, dto.unit, dto.tileInfo).onMoveButtonClick() } - // we don't update it directly because we're on a different thread; instead, we tell it to update itself - worldScreen.shouldUpdate = true - - removeUnitActionOverlay=true - } + else moveHereButton.color.a = 0.5f + addOverlayOnTileGroup(dto.tileInfo, moveHereButton) } - private fun addOverlayOnTileGroup(group:WorldTileGroup, actor: Actor) { + + + fun onTileLongClicked(tileInfo: TileInfo): Boolean { + + unitActionOverlay?.remove() + selectedTile = tileInfo + val selectedUnit = worldScreen.bottomBar.unitTable.selectedUnit + worldScreen.bottomBar.unitTable.tileSelected(tileInfo) + worldScreen.shouldUpdate = true + + if (selectedUnit != null) { + addOverlayOnTileGroup(tileInfo, UnitContextMenu(this, selectedUnit, tileInfo)) + return true + } + + return false + } + + private fun addOverlayOnTileGroup(tileInfo: TileInfo, actor: Actor) { + + val group = tileGroups[tileInfo]!! + actor.center(group) actor.x+=group.x actor.y+=group.y group.parent.addActor(actor) actor.toFront() + + actor.y += actor.height + unitActionOverlay = actor + } internal fun updateTiles(civInfo: CivilizationInfo) { @@ -217,20 +223,23 @@ 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 (worldScreen.bottomBar.unitTable.selectedCity!=null){ - val city = worldScreen.bottomBar.unitTable.selectedCity!! - updateTilegroupsForSelectedCity(city, playerViewableTilePositions) - } else if(worldScreen.bottomBar.unitTable.selectedUnit!=null){ - val unit = worldScreen.bottomBar.unitTable.selectedUnit!! - updateTilegroupsForSelectedUnit(unit, playerViewableTilePositions) - } - else if(unitActionOverlay!=null){ - unitActionOverlay!!.remove() - unitActionOverlay=null + val unitTable = worldScreen.bottomBar.unitTable + when { + unitTable.selectedCity!=null -> { + val city = unitTable.selectedCity!! + updateTilegroupsForSelectedCity(city, playerViewableTilePositions) + } + unitTable.selectedUnit!=null -> { + val unit = unitTable.selectedUnit!! + updateTilegroupsForSelectedUnit(unit, playerViewableTilePositions) + } + unitActionOverlay!=null -> { + unitActionOverlay!!.remove() + unitActionOverlay=null + } } - if(selectedTile!=null) - tileGroups[selectedTile!!]!!.showCircle(Color.WHITE) + tileGroups[selectedTile]?.showCircle(Color.WHITE) } private fun updateTilegroupsForSelectedUnit(unit: MapUnit, playerViewableTilePositions: HashSet) { diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitContextMenu.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitContextMenu.kt new file mode 100644 index 0000000000..94da057ad4 --- /dev/null +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitContextMenu.kt @@ -0,0 +1,72 @@ +package com.unciv.ui.worldscreen.unit + +import com.badlogic.gdx.scenes.scene2d.Actor +import com.badlogic.gdx.scenes.scene2d.ui.Button +import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup +import com.unciv.logic.map.MapUnit +import com.unciv.logic.map.TileInfo +import com.unciv.ui.utils.CameraStageBaseScreen +import com.unciv.ui.utils.ImageGetter +import com.unciv.ui.utils.Sounds +import com.unciv.ui.utils.onClick +import com.unciv.ui.worldscreen.TileMapHolder +import kotlin.concurrent.thread + +class UnitContextMenu(val tileMapHolder: TileMapHolder, val selectedUnit: MapUnit, val targetTile: TileInfo) : VerticalGroup() { + + init { + + space(10f) + + addButton(ImageGetter.getStatIcon("Movement"), "Move here") { + onMoveButtonClick() + } + addButton(ImageGetter.getImprovementIcon("Road"), "Construct Road this way") { + onConstructRoadButtonClick() + } + + pack() + + } + + fun addButton(icon: Actor, label: String, action: () -> Unit) { + val skin = CameraStageBaseScreen.skin + val button = Button(skin) + button.add(icon).size(20f).padRight(10f) + button.add(label) + addActor(button) + button.onClick { action() } + } + + fun onMoveButtonClick() { + // this can take a long time, because of the unit-to-tile calculation needed, so we put it in a different thread + thread { + if (selectedUnit.movementAlgs().canReach(targetTile)) { + 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(targetTile) + Sounds.play("whoosh") + if (selectedUnit.currentTile != targetTile) + selectedUnit.action = "moveTo " + targetTile.position.x.toInt() + "," + targetTile.position.y.toInt() + if(selectedUnit.currentMovement>0){ + tileMapHolder.worldScreen.bottomBar.unitTable.selectedUnit=selectedUnit + } + } catch (e: Exception) { + } + } + + // we don't update it directly because we're on a different thread; instead, we tell it to update itself + tileMapHolder.worldScreen.shouldUpdate = true + + tileMapHolder.removeUnitActionOverlay=true + } + } + + private fun onConstructRoadButtonClick() { + // TODO + } +} \ No newline at end of file From a8c3b3755b1bd1715a61ad4a29bc8b11bbb4bfc5 Mon Sep 17 00:00:00 2001 From: martin Date: Tue, 21 May 2019 00:11:59 +0200 Subject: [PATCH 2/8] adding MapUnitAction, which is compatible to MapUnit.action --- core/src/com/unciv/logic/map/MapUnit.kt | 10 +++++++++- core/src/com/unciv/logic/map/MapUnitAction.kt | 15 +++++++++++++++ .../com/unciv/ui/worldscreen/WorldScreen.kt | 2 -- .../unciv/ui/worldscreen/unit/UnitActions.kt | 2 -- .../ui/worldscreen/unit/UnitContextMenu.kt | 3 ++- .../unciv/ui/worldscreen/unit/UnitTable.kt | 19 +------------------ 6 files changed, 27 insertions(+), 24 deletions(-) create mode 100644 core/src/com/unciv/logic/map/MapUnitAction.kt diff --git a/core/src/com/unciv/logic/map/MapUnit.kt b/core/src/com/unciv/logic/map/MapUnit.kt index e491d0c6cf..8f52ef37c8 100644 --- a/core/src/com/unciv/logic/map/MapUnit.kt +++ b/core/src/com/unciv/logic/map/MapUnit.kt @@ -16,6 +16,7 @@ import java.util.* import kotlin.collections.ArrayList class MapUnit { + @Transient lateinit var civInfo: CivilizationInfo @Transient lateinit var baseUnit: BaseUnit @Transient internal lateinit var currentTile :TileInfo @@ -32,7 +33,13 @@ class MapUnit { lateinit var name: String var currentMovement: Float = 0f var health:Int = 100 - var action: String? = null // work, automation, fortifying, I dunno what. + + var mapUnitAction : MapUnitAction? = null + var action: String? // work, automation, fortifying, I dunno what. + // getter and setter for compatibility: make sure string-based actions still work + get() = mapUnitAction?.name + set(value) { mapUnitAction = value?.let{ MapUnitAction(this, value) } } + var attacksThisTurn = 0 var promotions = UnitPromotions() var due: Boolean = true @@ -275,6 +282,7 @@ class MapUnit { //region state-changing functions fun setTransients(){ promotions.unit=this + mapUnitAction?.unit = this baseUnit=GameBasics.Units[name]!! updateUniques() } diff --git a/core/src/com/unciv/logic/map/MapUnitAction.kt b/core/src/com/unciv/logic/map/MapUnitAction.kt new file mode 100644 index 0000000000..8d7d7dfc07 --- /dev/null +++ b/core/src/com/unciv/logic/map/MapUnitAction.kt @@ -0,0 +1,15 @@ +package com.unciv.logic.map + +open class MapUnitAction( + @Transient var unit: MapUnit = MapUnit(), + var name: String = "" +) + + +class BuildLongRoadAction( + mapUnit: MapUnit = MapUnit() +) : MapUnitAction(mapUnit, "Build Long Road") { + + + +} \ No newline at end of file diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index 5e69d56717..6da39d4a20 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -246,8 +246,6 @@ class WorldScreen : CameraStageBaseScreen() { return@onClick } - bottomBar.unitTable.currentlyExecutingAction = null - Gdx.input.inputProcessor = null // remove input processing - nothing will be clicked! nextTurnButton.disable() nextTurnButton.setText("Working...".tr()) diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt index 4ab1721f8c..80aad0f255 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt @@ -34,7 +34,6 @@ class UnitActions { if(unit.action!=null && unit.action!!.startsWith("moveTo")) { actionList += UnitAction("Stop movement", true) { - unitTable.currentlyExecutingAction = null unit.action = null } } @@ -146,7 +145,6 @@ class UnitActions { unit.civInfo.addCity(tile.position) tile.improvement = null - unitTable.currentlyExecutingAction = null // In case the settler was in the middle of doing something and we then founded a city with it unit.destroy() }.sound("chimes") } diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitContextMenu.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitContextMenu.kt index 94da057ad4..87d10c0619 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitContextMenu.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitContextMenu.kt @@ -10,6 +10,7 @@ import com.unciv.ui.utils.ImageGetter import com.unciv.ui.utils.Sounds import com.unciv.ui.utils.onClick import com.unciv.ui.worldscreen.TileMapHolder +import com.unciv.logic.map.BuildLongRoadAction import kotlin.concurrent.thread class UnitContextMenu(val tileMapHolder: TileMapHolder, val selectedUnit: MapUnit, val targetTile: TileInfo) : VerticalGroup() { @@ -67,6 +68,6 @@ class UnitContextMenu(val tileMapHolder: TileMapHolder, val selectedUnit: MapUni } private fun onConstructRoadButtonClick() { - // TODO + selectedUnit.mapUnitAction = BuildLongRoadAction() } } \ No newline at end of file diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitTable.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitTable.kt index 4d7a689cff..dbeac8e6d0 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitTable.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitTable.kt @@ -22,8 +22,6 @@ class UnitTable(val worldScreen: WorldScreen) : Table(){ private val unitDescriptionTable = Table(CameraStageBaseScreen.skin) var selectedUnit : MapUnit? = null var selectedCity : CityInfo? = null - var currentlyExecutingAction : String? = null - var lastSelectedCityButton : Boolean = false val deselectUnitButton = Table() val helpUnitButton = Table() @@ -88,12 +86,10 @@ class UnitTable(val worldScreen: WorldScreen) : Table(){ if (selectedUnit!!.civInfo != worldScreen.currentPlayerCiv) { // The unit that was selected, was captured. It exists but is no longer ours. selectedUnit = null selectedCity = null - currentlyExecutingAction = null selectedUnitHasChanged = true } else if (selectedUnit!! !in selectedUnit!!.getTile().getUnits()) { // The unit that was there no longer exists} selectedUnit = null selectedCity = null - currentlyExecutingAction = null selectedUnitHasChanged = true } } @@ -190,20 +186,7 @@ class UnitTable(val worldScreen: WorldScreen) : Table(){ fun tileSelected(selectedTile: TileInfo) { val previouslySelectedUnit = selectedUnit - if(currentlyExecutingAction=="moveTo"){ - if(selectedUnit!!.movementAlgs() - .getShortestPath(selectedTile).isEmpty()) - return // can't reach there with the selected unit, watcha want me to do? - - val reachedTile = selectedUnit!!.movementAlgs().headTowards(selectedTile) - - selectedUnit!!.action=null // Disable any prior action (automation, fortification...) - if(reachedTile!=selectedTile) // Didn't get all the way there - selectedUnit!!.action = "moveTo " + selectedTile.position.x.toInt() + "," + selectedTile.position.y.toInt() - currentlyExecutingAction = null - } - - else if(selectedTile.militaryUnit!=null && selectedTile.militaryUnit!!.civInfo == worldScreen.currentPlayerCiv + if(selectedTile.militaryUnit!=null && selectedTile.militaryUnit!!.civInfo == worldScreen.currentPlayerCiv && selectedUnit!=selectedTile.militaryUnit && (selectedTile.civilianUnit==null || selectedUnit!=selectedTile.civilianUnit)){ selectedUnit = selectedTile.militaryUnit From 98803f0454af3bb8f6a6d2b948e801e92236cb9a Mon Sep 17 00:00:00 2001 From: martin Date: Tue, 21 May 2019 01:34:07 +0200 Subject: [PATCH 3/8] implementing BuildLongRoadAction --- android/assets/jsons/Translations/Other.json | 4 + .../logic/automation/WorkerAutomation.kt | 17 ++-- .../unciv/logic/civilization/TechManager.kt | 10 +++ core/src/com/unciv/logic/map/BFS.kt | 9 +- core/src/com/unciv/logic/map/MapUnit.kt | 15 ++-- core/src/com/unciv/logic/map/MapUnitAction.kt | 15 ---- core/src/com/unciv/logic/map/RoadStatus.kt | 13 ++- .../unciv/logic/map/UnitMovementAlgorithms.kt | 8 +- .../logic/map/action/BuildLongRoadAction.kt | 89 +++++++++++++++++++ .../unciv/logic/map/action/MapUnitAction.kt | 15 ++++ .../pickerscreens/GreatPersonPickerScreen.kt | 2 +- .../ui/worldscreen/unit/UnitContextMenu.kt | 28 ++++-- 12 files changed, 177 insertions(+), 48 deletions(-) delete mode 100644 core/src/com/unciv/logic/map/MapUnitAction.kt create mode 100644 core/src/com/unciv/logic/map/action/BuildLongRoadAction.kt create mode 100644 core/src/com/unciv/logic/map/action/MapUnitAction.kt diff --git a/android/assets/jsons/Translations/Other.json b/android/assets/jsons/Translations/Other.json index f8295ed2a4..8b4cbfcc9f 100644 --- a/android/assets/jsons/Translations/Other.json +++ b/android/assets/jsons/Translations/Other.json @@ -227,6 +227,10 @@ Japanese:"自動化を停止" } + "Construct road": { + "German": "Straße bauen" + } + "Fortify":{ Italian:"Fortifica" Russian:"Укрепить" diff --git a/core/src/com/unciv/logic/automation/WorkerAutomation.kt b/core/src/com/unciv/logic/automation/WorkerAutomation.kt index 7167720e12..4784e98326 100644 --- a/core/src/com/unciv/logic/automation/WorkerAutomation.kt +++ b/core/src/com/unciv/logic/automation/WorkerAutomation.kt @@ -49,23 +49,20 @@ class WorkerAutomation(val unit: MapUnit) { - fun tryConnectingCities():Boolean{ // returns whether we actually did anything - val techEnablingRailroad = GameBasics.TileImprovements["Railroad"]!!.techRequired!! - val canBuildRailroad = unit.civInfo.tech.isResearched(techEnablingRailroad) + fun tryConnectingCities():Boolean { // returns whether we actually did anything - val targetRoadName = if (canBuildRailroad) "Railroad" else "Road" - val targetStatus = if (canBuildRailroad) RoadStatus.Railroad else RoadStatus.Road + val targetRoad = unit.civInfo.tech.getBestRoadAvailable() val citiesThatNeedConnecting = unit.civInfo.cities .filter { it.population.population>3 && !it.isCapital() - && !it.cityStats.isConnectedToCapital(targetStatus) } + && !it.cityStats.isConnectedToCapital(targetRoad) } if(citiesThatNeedConnecting.isEmpty()) return false // do nothing. val citiesThatNeedConnectingBfs = citiesThatNeedConnecting .map { city -> BFS(city.getCenterTile()){it.isLand && unit.canPassThrough(it)} } .toMutableList() - val connectedCities = unit.civInfo.cities.filter { it.isCapital() || it.cityStats.isConnectedToCapital(targetStatus) } + val connectedCities = unit.civInfo.cities.filter { it.isCapital() || it.cityStats.isConnectedToCapital(targetRoad) } .map { it.getCenterTile() } while(citiesThatNeedConnectingBfs.any()){ @@ -78,7 +75,7 @@ class WorkerAutomation(val unit: MapUnit) { for(city in connectedCities) if(bfs.tilesToCheck.contains(city)) { // we have a winner! val pathToCity = bfs.getPathTo(city) - val roadableTiles = pathToCity.filter { it.roadStatus < targetStatus } + val roadableTiles = pathToCity.filter { it.roadStatus < targetRoad } val tileToConstructRoadOn :TileInfo if(unit.currentTile in roadableTiles) tileToConstructRoadOn = unit.currentTile else{ @@ -88,8 +85,8 @@ class WorkerAutomation(val unit: MapUnit) { unit.movementAlgs().headTowards(tileToConstructRoadOn) } if(unit.currentMovement>0 && unit.currentTile==tileToConstructRoadOn - && unit.currentTile.improvementInProgress!=targetRoadName) - tileToConstructRoadOn.startWorkingOnImprovement(GameBasics.TileImprovements[targetRoadName]!!,unit.civInfo) + && unit.currentTile.improvementInProgress!=targetRoad.name) + tileToConstructRoadOn.startWorkingOnImprovement(targetRoad.improvement()!!,unit.civInfo) return true } } diff --git a/core/src/com/unciv/logic/civilization/TechManager.kt b/core/src/com/unciv/logic/civilization/TechManager.kt index f356231495..1630794b58 100644 --- a/core/src/com/unciv/logic/civilization/TechManager.kt +++ b/core/src/com/unciv/logic/civilization/TechManager.kt @@ -2,6 +2,7 @@ package com.unciv.logic.civilization import com.badlogic.gdx.graphics.Color +import com.unciv.logic.map.RoadStatus import com.unciv.models.gamebasics.GameBasics import com.unciv.models.gamebasics.tech.Technology import com.unciv.models.gamebasics.tr @@ -196,4 +197,13 @@ class TechManager { if(researchedTechUniques.contains("Enables embarked units to enter ocean tiles")) embarkedUnitsCanEnterOcean=true if(researchedTechUniques.contains("Improves movement speed on roads")) movementSpeedOnRoadsImproved = true } + + fun getBestRoadAvailable(): RoadStatus { + if (!isResearched(RoadStatus.Road.improvement()!!.techRequired!!)) return RoadStatus.None + + val techEnablingRailroad = RoadStatus.Railroad.improvement()!!.techRequired!! + val canBuildRailroad = isResearched(techEnablingRailroad) + + return if (canBuildRailroad) RoadStatus.Railroad else RoadStatus.Road + } } \ No newline at end of file diff --git a/core/src/com/unciv/logic/map/BFS.kt b/core/src/com/unciv/logic/map/BFS.kt index c3b2309776..f087559196 100644 --- a/core/src/com/unciv/logic/map/BFS.kt +++ b/core/src/com/unciv/logic/map/BFS.kt @@ -18,9 +18,10 @@ class BFS(val startingPoint: TileInfo, val predicate : (TileInfo) -> Boolean){ nextStep() } - fun stepUntilDestination(destination: TileInfo){ + fun stepUntilDestination(destination: TileInfo): BFS { while(!tilesReached.containsKey(destination) && tilesToCheck.isNotEmpty()) nextStep() + return this } fun nextStep(){ @@ -39,8 +40,10 @@ class BFS(val startingPoint: TileInfo, val predicate : (TileInfo) -> Boolean){ path.add(destination) var currentNode = destination while(currentNode != startingPoint){ - currentNode = tilesReached[currentNode]!! - path.add(currentNode) + tilesReached[currentNode]?.let { + currentNode = it + path.add(currentNode) + } ?: return ArrayList() // destination is not in our path } return path } diff --git a/core/src/com/unciv/logic/map/MapUnit.kt b/core/src/com/unciv/logic/map/MapUnit.kt index 8f52ef37c8..2ad3794cab 100644 --- a/core/src/com/unciv/logic/map/MapUnit.kt +++ b/core/src/com/unciv/logic/map/MapUnit.kt @@ -6,6 +6,7 @@ import com.unciv.Constants import com.unciv.logic.automation.UnitAutomation import com.unciv.logic.automation.WorkerAutomation import com.unciv.logic.civilization.CivilizationInfo +import com.unciv.logic.map.action.MapUnitAction import com.unciv.models.gamebasics.GameBasics import com.unciv.models.gamebasics.tech.TechEra import com.unciv.models.gamebasics.tile.TerrainType @@ -300,10 +301,13 @@ class MapUnit { val enemyUnitsInWalkingDistance = getDistanceToTiles().keys .filter { it.militaryUnit!=null && civInfo.isAtWarWith(it.militaryUnit!!.civInfo)} if(enemyUnitsInWalkingDistance.isNotEmpty()) { - if (action != null && action!!.startsWith("moveTo")) action=null + if (mapUnitAction?.shouldStopOnEnemyInSight()==true) + mapUnitAction=null return // Don't you dare move. } + mapUnitAction?.doPreTurnAction() + if (action != null && action!!.startsWith("moveTo")) { val destination = action!!.replace("moveTo ", "").split(",").dropLastWhile { it.isEmpty() }.toTypedArray() val destinationVector = Vector2(Integer.parseInt(destination[0]).toFloat(), Integer.parseInt(destination[1]).toFloat()) @@ -383,14 +387,13 @@ class MapUnit { if(otherTile==getTile()) return // already here! val distanceToTiles = getDistanceToTiles() - class YouCantGetThereFromHereException : Exception() + class YouCantGetThereFromHereException(msg: String) : Exception(msg) if (!distanceToTiles.containsKey(otherTile)) + throw YouCantGetThereFromHereException("$this can't get from ${currentTile.position} to ${otherTile.position}.") - throw YouCantGetThereFromHereException() - - class CantEnterThisTileException : Exception() + class CantEnterThisTileException(msg: String) : Exception(msg) if(!canMoveTo(otherTile)) - throw CantEnterThisTileException() + throw CantEnterThisTileException("$this can't enter $otherTile") if(otherTile.isCityCenter() && otherTile.getOwner()!=civInfo) throw Exception("This is an enemy city, you can't go here!") currentMovement -= distanceToTiles[otherTile]!! diff --git a/core/src/com/unciv/logic/map/MapUnitAction.kt b/core/src/com/unciv/logic/map/MapUnitAction.kt deleted file mode 100644 index 8d7d7dfc07..0000000000 --- a/core/src/com/unciv/logic/map/MapUnitAction.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.unciv.logic.map - -open class MapUnitAction( - @Transient var unit: MapUnit = MapUnit(), - var name: String = "" -) - - -class BuildLongRoadAction( - mapUnit: MapUnit = MapUnit() -) : MapUnitAction(mapUnit, "Build Long Road") { - - - -} \ No newline at end of file diff --git a/core/src/com/unciv/logic/map/RoadStatus.kt b/core/src/com/unciv/logic/map/RoadStatus.kt index 5d42a4719f..774eef4a6b 100644 --- a/core/src/com/unciv/logic/map/RoadStatus.kt +++ b/core/src/com/unciv/logic/map/RoadStatus.kt @@ -1,7 +1,18 @@ package com.unciv.logic.map +import com.unciv.models.gamebasics.GameBasics + +/** + * You can use RoadStatus.name to identify [Road] and [Railroad] + * in string-based identification, as done in [improvement]. + */ enum class RoadStatus { + None, Road, - Railroad + Railroad; + + /** returns null for [None] */ + fun improvement() = GameBasics.TileImprovements[this.name] + } diff --git a/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt b/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt index a1cb746301..50ecd8684a 100644 --- a/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt +++ b/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt @@ -137,7 +137,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) { */ fun headTowards(destination: TileInfo): TileInfo { val currentTile = unit.getTile() - if(currentTile==destination) return currentTile + if (currentTile == destination) return currentTile val distanceToTiles = unit.getDistanceToTiles() @@ -152,7 +152,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) { return currentTile val reachableDestinationNeighbors = destinationNeighbors - .filter { distanceToTiles.containsKey(it) && unit.canMoveTo(it)} + .filter { distanceToTiles.containsKey(it) && unit.canMoveTo(it) } if (reachableDestinationNeighbors.isEmpty()) // We can't get closer... return currentTile @@ -160,8 +160,8 @@ class UnitMovementAlgorithms(val unit:MapUnit) { } } else { // If the tile is far away, we need to build a path how to get there, and then take the first step val path = getShortestPath(destination) - class UnreachableDestinationException:Exception() - if(path.isEmpty()) throw UnreachableDestinationException() + class UnreachableDestinationException : Exception() + if (path.isEmpty()) throw UnreachableDestinationException() destinationTileThisTurn = path.first() } diff --git a/core/src/com/unciv/logic/map/action/BuildLongRoadAction.kt b/core/src/com/unciv/logic/map/action/BuildLongRoadAction.kt new file mode 100644 index 0000000000..f22370f567 --- /dev/null +++ b/core/src/com/unciv/logic/map/action/BuildLongRoadAction.kt @@ -0,0 +1,89 @@ +package com.unciv.logic.map.action + +import com.unciv.logic.map.BFS +import com.unciv.logic.map.MapUnit +import com.unciv.logic.map.TileInfo + +class BuildLongRoadAction( + mapUnit: MapUnit = MapUnit(), + val target: TileInfo = TileInfo() +) : MapUnitAction(mapUnit, "Build Long Road") { + + override fun shouldStopOnEnemyInSight(): Boolean = true + + override fun isAvailable(): Boolean + = unit.hasUnique("Can build improvements on tiles") + && getPath(target).isNotEmpty() + + override fun doPreTurnAction() { + + // we're working! + if (unit.currentTile.improvementInProgress != null) + return + else if (startWorking()) { + return + } + + // we reached our target? And road is finished? + if (unit.currentTile.position == target.position + && isRoadFinished(unit.currentTile)) { + unit.action = null + return + } + + // move one step forward - and start building + if (stepForward(target)) { + startWorking() + } else if (unit.currentMovement > 1f) { + unit.action = null + return + } + + } + + // because the unit is building a road, we need to use a shortest path that is + // independent of movement costs, but should respect impassable terrain like water and enemy territory + private fun stepForward(destination: TileInfo): Boolean { + var success = false + for (step in getPath(destination).drop(1)) { + if (unit.currentMovement > 0f && unit.canMoveTo(step)) { + unit.moveToTile(step) + success = true + // if there is a road already, take multiple steps, otherwise break + if (!isRoadFinished(step)) { + break + } + } else break + } + return success + } + + private fun isRoadFinished(tile: TileInfo): Boolean { + return tile.roadStatus == unit.civInfo.tech.getBestRoadAvailable() + } + + private fun getPath(destination: TileInfo): List { + // BFS is not very efficient + return BFS(unit.currentTile) { isRoadableTile(it) } + .stepUntilDestination(destination) + .getPathTo(destination).reversed() + } + + private fun isRoadableTile(it: TileInfo) = it.isLand && unit.canPassThrough(it) + + private fun startWorking(): Boolean { + val tile = unit.currentTile + if (unit.currentMovement > 0 && isRoadableTile(tile)) { + val roadToBuild = unit.civInfo.tech.getBestRoadAvailable() + roadToBuild.improvement()?.let { improvement -> + if (tile.roadStatus != roadToBuild && tile.improvementInProgress != improvement.name) { + tile.startWorkingOnImprovement(improvement, unit.civInfo) + return true + } + } + } + return false + } + + +} \ No newline at end of file diff --git a/core/src/com/unciv/logic/map/action/MapUnitAction.kt b/core/src/com/unciv/logic/map/action/MapUnitAction.kt new file mode 100644 index 0000000000..0b29d42166 --- /dev/null +++ b/core/src/com/unciv/logic/map/action/MapUnitAction.kt @@ -0,0 +1,15 @@ +package com.unciv.logic.map.action + +import com.unciv.logic.map.MapUnit + +open class MapUnitAction( + @Transient var unit: MapUnit = MapUnit(), + var name: String = "" +) { + /** return true if this action is possible in the given conditions */ + open fun isAvailable(): Boolean = true + open fun doPreTurnAction() {} + open fun shouldStopOnEnemyInSight(): Boolean = name.startsWith("moveTo") +} + + diff --git a/core/src/com/unciv/ui/pickerscreens/GreatPersonPickerScreen.kt b/core/src/com/unciv/ui/pickerscreens/GreatPersonPickerScreen.kt index f243914d10..783846d88e 100644 --- a/core/src/com/unciv/ui/pickerscreens/GreatPersonPickerScreen.kt +++ b/core/src/com/unciv/ui/pickerscreens/GreatPersonPickerScreen.kt @@ -30,7 +30,7 @@ class GreatPersonPickerScreen : PickerScreen() { pick("Get " +unit.name) descriptionLabel.setText(unit.uniques.joinToString()) } - topTable.add(button).pad(10f) + topTable.add(button).pad(10f).row() } rightSideButton.onClick("choir") { diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitContextMenu.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitContextMenu.kt index 87d10c0619..973145cf5b 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitContextMenu.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitContextMenu.kt @@ -10,7 +10,8 @@ import com.unciv.ui.utils.ImageGetter import com.unciv.ui.utils.Sounds import com.unciv.ui.utils.onClick import com.unciv.ui.worldscreen.TileMapHolder -import com.unciv.logic.map.BuildLongRoadAction +import com.unciv.logic.map.action.BuildLongRoadAction +import com.unciv.logic.map.action.MapUnitAction import kotlin.concurrent.thread class UnitContextMenu(val tileMapHolder: TileMapHolder, val selectedUnit: MapUnit, val targetTile: TileInfo) : VerticalGroup() { @@ -19,17 +20,31 @@ class UnitContextMenu(val tileMapHolder: TileMapHolder, val selectedUnit: MapUni space(10f) - addButton(ImageGetter.getStatIcon("Movement"), "Move here") { + addButton(ImageGetter.getStatIcon("Movement"), "Move unit") { onMoveButtonClick() } - addButton(ImageGetter.getImprovementIcon("Road"), "Construct Road this way") { - onConstructRoadButtonClick() - } + + addButton( + ImageGetter.getImprovementIcon("Road"), + "Construct road", + BuildLongRoadAction(selectedUnit, targetTile) + ) pack() } + fun addButton(icon: Actor, label: String, action: MapUnitAction) { + if (action.isAvailable()) { + addButton(icon, label) { + selectedUnit.mapUnitAction = action + selectedUnit.mapUnitAction?.doPreTurnAction() + tileMapHolder.removeUnitActionOverlay = true + tileMapHolder.worldScreen.shouldUpdate = true + } + } + } + fun addButton(icon: Actor, label: String, action: () -> Unit) { val skin = CameraStageBaseScreen.skin val button = Button(skin) @@ -67,7 +82,4 @@ class UnitContextMenu(val tileMapHolder: TileMapHolder, val selectedUnit: MapUni } } - private fun onConstructRoadButtonClick() { - selectedUnit.mapUnitAction = BuildLongRoadAction() - } } \ No newline at end of file From 29647896cecb944c259a588d76cca51311fbe7a3 Mon Sep 17 00:00:00 2001 From: Yair Morgenstern Date: Wed, 22 May 2019 22:55:24 +0300 Subject: [PATCH 4/8] We no longer "downgrade" tiles from Railroad to Road when we try to build a Road somewhere Fixed error where if intermediate tiles were full (o e.g. other workers) it would try to "jump" to a tile that was out of its reach --- core/src/com/unciv/logic/map/MapUnit.kt | 9 ++++--- .../logic/map/action/BuildLongRoadAction.kt | 24 +++++++++++-------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/core/src/com/unciv/logic/map/MapUnit.kt b/core/src/com/unciv/logic/map/MapUnit.kt index 2ad3794cab..3c673f8e52 100644 --- a/core/src/com/unciv/logic/map/MapUnit.kt +++ b/core/src/com/unciv/logic/map/MapUnit.kt @@ -177,7 +177,8 @@ class MapUnit { } /** - * Designates whether we can walk to the tile - without attacking + * Designates whether we can enter the tile - without attacking + * DOES NOT designate whether we can reach that tile in the current turn */ fun canMoveTo(tile: TileInfo): Boolean { if(!canPassThrough(tile)) return false @@ -380,9 +381,6 @@ class MapUnit { } } - /** - * @return The tile that we reached this turn - */ fun moveToTile(otherTile: TileInfo) { if(otherTile==getTile()) return // already here! val distanceToTiles = getDistanceToTiles() @@ -394,7 +392,8 @@ class MapUnit { class CantEnterThisTileException(msg: String) : Exception(msg) if(!canMoveTo(otherTile)) throw CantEnterThisTileException("$this can't enter $otherTile") - if(otherTile.isCityCenter() && otherTile.getOwner()!=civInfo) throw Exception("This is an enemy city, you can't go here!") + if(otherTile.isCityCenter() && otherTile.getOwner()!=civInfo) + throw Exception("This is an enemy city, you can't go here!") currentMovement -= distanceToTiles[otherTile]!! if (currentMovement < 0.1) currentMovement = 0f // silly floats which are "almost zero" diff --git a/core/src/com/unciv/logic/map/action/BuildLongRoadAction.kt b/core/src/com/unciv/logic/map/action/BuildLongRoadAction.kt index f22370f567..43d7a06cf4 100644 --- a/core/src/com/unciv/logic/map/action/BuildLongRoadAction.kt +++ b/core/src/com/unciv/logic/map/action/BuildLongRoadAction.kt @@ -20,9 +20,10 @@ class BuildLongRoadAction( // we're working! if (unit.currentTile.improvementInProgress != null) return - else if (startWorking()) { + + if (startWorkingOnRoad()) return - } + // we reached our target? And road is finished? if (unit.currentTile.position == target.position @@ -33,7 +34,7 @@ class BuildLongRoadAction( // move one step forward - and start building if (stepForward(target)) { - startWorking() + startWorkingOnRoad() } else if (unit.currentMovement > 1f) { unit.action = null return @@ -45,21 +46,24 @@ class BuildLongRoadAction( // independent of movement costs, but should respect impassable terrain like water and enemy territory private fun stepForward(destination: TileInfo): Boolean { var success = false + val tilesUnitCanCurrentlyReach = unit.getDistanceToTiles().keys for (step in getPath(destination).drop(1)) { + if(step !in tilesUnitCanCurrentlyReach) return false // we're out of tiles in reachable distance, no need to check any further + if (unit.currentMovement > 0f && unit.canMoveTo(step)) { unit.moveToTile(step) success = true - // if there is a road already, take multiple steps, otherwise break - if (!isRoadFinished(step)) { - break - } + + // if there is a road already, take multiple steps, otherwise this is where we're going to build a road + if (!isRoadFinished(step)) return true + } else break } return success } private fun isRoadFinished(tile: TileInfo): Boolean { - return tile.roadStatus == unit.civInfo.tech.getBestRoadAvailable() + return tile.roadStatus >= unit.civInfo.tech.getBestRoadAvailable() } private fun getPath(destination: TileInfo): List { @@ -71,12 +75,12 @@ class BuildLongRoadAction( private fun isRoadableTile(it: TileInfo) = it.isLand && unit.canPassThrough(it) - private fun startWorking(): Boolean { + private fun startWorkingOnRoad(): Boolean { val tile = unit.currentTile if (unit.currentMovement > 0 && isRoadableTile(tile)) { val roadToBuild = unit.civInfo.tech.getBestRoadAvailable() roadToBuild.improvement()?.let { improvement -> - if (tile.roadStatus != roadToBuild && tile.improvementInProgress != improvement.name) { + if (tile.roadStatus < roadToBuild && tile.improvementInProgress != improvement.name) { tile.startWorkingOnImprovement(improvement, unit.civInfo) return true } From 4330508d2e61e4d0ee6f18a5f5d4bb601885979a Mon Sep 17 00:00:00 2001 From: martin Date: Wed, 22 May 2019 22:15:48 +0200 Subject: [PATCH 5/8] using StringAction for string-encoded actions used throughout the app --- core/src/com/unciv/logic/map/MapUnit.kt | 5 +++-- .../unciv/logic/map/action/MapUnitAction.kt | 5 ++--- .../com/unciv/logic/map/action/StringAction.kt | 18 ++++++++++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 core/src/com/unciv/logic/map/action/StringAction.kt diff --git a/core/src/com/unciv/logic/map/MapUnit.kt b/core/src/com/unciv/logic/map/MapUnit.kt index 3c673f8e52..b569cbf18a 100644 --- a/core/src/com/unciv/logic/map/MapUnit.kt +++ b/core/src/com/unciv/logic/map/MapUnit.kt @@ -7,6 +7,7 @@ import com.unciv.logic.automation.UnitAutomation import com.unciv.logic.automation.WorkerAutomation import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.map.action.MapUnitAction +import com.unciv.logic.map.action.StringAction import com.unciv.models.gamebasics.GameBasics import com.unciv.models.gamebasics.tech.TechEra import com.unciv.models.gamebasics.tile.TerrainType @@ -38,8 +39,8 @@ class MapUnit { var mapUnitAction : MapUnitAction? = null var action: String? // work, automation, fortifying, I dunno what. // getter and setter for compatibility: make sure string-based actions still work - get() = mapUnitAction?.name - set(value) { mapUnitAction = value?.let{ MapUnitAction(this, value) } } + get() = (mapUnitAction as? StringAction)?.action ?: mapUnitAction?.let { "" } // null if no action is assigned. + set(value) { mapUnitAction = value?.let{ StringAction(this, value) } } // wrap traditional string-encoded actions into StringAction var attacksThisTurn = 0 var promotions = UnitPromotions() diff --git a/core/src/com/unciv/logic/map/action/MapUnitAction.kt b/core/src/com/unciv/logic/map/action/MapUnitAction.kt index 0b29d42166..c53ea1b464 100644 --- a/core/src/com/unciv/logic/map/action/MapUnitAction.kt +++ b/core/src/com/unciv/logic/map/action/MapUnitAction.kt @@ -3,13 +3,12 @@ package com.unciv.logic.map.action import com.unciv.logic.map.MapUnit open class MapUnitAction( - @Transient var unit: MapUnit = MapUnit(), - var name: String = "" + @Transient var unit: MapUnit = MapUnit() ) { /** return true if this action is possible in the given conditions */ open fun isAvailable(): Boolean = true open fun doPreTurnAction() {} - open fun shouldStopOnEnemyInSight(): Boolean = name.startsWith("moveTo") + open fun shouldStopOnEnemyInSight(): Boolean = false } diff --git a/core/src/com/unciv/logic/map/action/StringAction.kt b/core/src/com/unciv/logic/map/action/StringAction.kt new file mode 100644 index 0000000000..17f60dd1ef --- /dev/null +++ b/core/src/com/unciv/logic/map/action/StringAction.kt @@ -0,0 +1,18 @@ +package com.unciv.logic.map.action + +import com.unciv.logic.map.MapUnit + +/** + * this class represents all actions that are identified by string only. + * this is the traditional way of handling actions in UnCiv: by coding relevant information + * into a string. This class is here to maintain compatibility to that method, preventing from a huge + * refactoring going on here. + */ +class StringAction( + unit: MapUnit = MapUnit(), + val action: String = "" // traditional string-encoded action like "moveTo x,y" +): MapUnitAction(unit) { + + override fun shouldStopOnEnemyInSight(): Boolean = action.startsWith("moveTo") + +} \ No newline at end of file From d676c18aec8e8f3950265e183b5ad7c3a2b35b86 Mon Sep 17 00:00:00 2001 From: martin Date: Wed, 22 May 2019 22:38:24 +0200 Subject: [PATCH 6/8] adding MapUnitAction.name(), a user-readable name for actions. Eg "Moving" instead of "moveTo x,y" --- android/assets/jsons/Translations/Other.json | 6 +++++- .../com/unciv/logic/map/action/BuildLongRoadAction.kt | 4 +++- core/src/com/unciv/logic/map/action/MapUnitAction.kt | 1 + core/src/com/unciv/logic/map/action/StringAction.kt | 11 ++++++++++- core/src/com/unciv/ui/EmpireOverviewScreen.kt | 4 +++- 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/android/assets/jsons/Translations/Other.json b/android/assets/jsons/Translations/Other.json index 8b4cbfcc9f..7900ea8cac 100644 --- a/android/assets/jsons/Translations/Other.json +++ b/android/assets/jsons/Translations/Other.json @@ -262,7 +262,7 @@ Romanian:"Dormi" Simplified_Chinese:"休眠" Portuguese:"Dormir" - German:"Schlafen legen" + German:"Schlafen" French:"Dormir" Japanese:"睡眠" Russian:"Спать" @@ -270,6 +270,10 @@ Spanish:"Dormir" } + "Moving": { + "German": "Bewegen" + } + "Set up":{ // For siege units Italian:"Monta" Russian:"Подготовится" diff --git a/core/src/com/unciv/logic/map/action/BuildLongRoadAction.kt b/core/src/com/unciv/logic/map/action/BuildLongRoadAction.kt index 43d7a06cf4..2fdbcce9c8 100644 --- a/core/src/com/unciv/logic/map/action/BuildLongRoadAction.kt +++ b/core/src/com/unciv/logic/map/action/BuildLongRoadAction.kt @@ -7,7 +7,9 @@ import com.unciv.logic.map.TileInfo class BuildLongRoadAction( mapUnit: MapUnit = MapUnit(), val target: TileInfo = TileInfo() -) : MapUnitAction(mapUnit, "Build Long Road") { +) : MapUnitAction(mapUnit) { + + override fun name(): String = "Build Long Road" override fun shouldStopOnEnemyInSight(): Boolean = true diff --git a/core/src/com/unciv/logic/map/action/MapUnitAction.kt b/core/src/com/unciv/logic/map/action/MapUnitAction.kt index c53ea1b464..fe2d08c2ef 100644 --- a/core/src/com/unciv/logic/map/action/MapUnitAction.kt +++ b/core/src/com/unciv/logic/map/action/MapUnitAction.kt @@ -5,6 +5,7 @@ import com.unciv.logic.map.MapUnit open class MapUnitAction( @Transient var unit: MapUnit = MapUnit() ) { + open fun name(): String = "" /** return true if this action is possible in the given conditions */ open fun isAvailable(): Boolean = true open fun doPreTurnAction() {} diff --git a/core/src/com/unciv/logic/map/action/StringAction.kt b/core/src/com/unciv/logic/map/action/StringAction.kt index 17f60dd1ef..08b767f323 100644 --- a/core/src/com/unciv/logic/map/action/StringAction.kt +++ b/core/src/com/unciv/logic/map/action/StringAction.kt @@ -11,8 +11,17 @@ import com.unciv.logic.map.MapUnit class StringAction( unit: MapUnit = MapUnit(), val action: String = "" // traditional string-encoded action like "moveTo x,y" -): MapUnitAction(unit) { +) : MapUnitAction(unit) { override fun shouldStopOnEnemyInSight(): Boolean = action.startsWith("moveTo") + override fun name(): String { + return when { + // translate string-encoded actions to user-readable names + action.startsWith("moveTo") -> "Moving" + else -> action + } + } + + } \ No newline at end of file diff --git a/core/src/com/unciv/ui/EmpireOverviewScreen.kt b/core/src/com/unciv/ui/EmpireOverviewScreen.kt index ebb62ac25a..f456f35324 100644 --- a/core/src/com/unciv/ui/EmpireOverviewScreen.kt +++ b/core/src/com/unciv/ui/EmpireOverviewScreen.kt @@ -104,7 +104,7 @@ class EmpireOverviewScreen : CameraStageBaseScreen(){ generalTable.add(createOffersTable(otherCiv, trade.theirOffers, trade.ourOffers.size)) return generalTable } - + private fun createOffersTable(civ: CivilizationInfo, offersList: TradeOffersList, numberOfOtherSidesOffers: Int): Table { val table = Table() table.defaults().pad(10f) @@ -256,6 +256,7 @@ class EmpireOverviewScreen : CameraStageBaseScreen(){ fun getUnitTable(): Table { val table=Table(skin).apply { defaults().pad(5f) } table.add("Name".tr()) + table.add("Action".tr()) table.add("Strength".tr()) table.add("Ranged strength".tr()) table.add("Movement".tr()) @@ -267,6 +268,7 @@ class EmpireOverviewScreen : CameraStageBaseScreen(){ for(unit in currentPlayerCivInfo.getCivUnits()){ val baseUnit = unit.baseUnit() table.add(unit.name.tr()) + unit.mapUnitAction?.let { table.add(it.name().tr()) } ?: table.add() if(baseUnit.strength>0) table.add(baseUnit.strength.toString()) else table.add() if(baseUnit.rangedStrength>0) table.add(baseUnit.rangedStrength.toString()) else table.add() table.add(DecimalFormat("0.#").format(unit.currentMovement)+"/"+unit.getMaxMovement()) From 73c273985f7738356dbe7686e57d2aee6ff13e0d Mon Sep 17 00:00:00 2001 From: martin Date: Wed, 22 May 2019 23:45:08 +0200 Subject: [PATCH 7/8] worker moves on working on road even when the path is blocked by other civilian units. Player will receive messages about those occasions. --- .../logic/map/action/BuildLongRoadAction.kt | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/core/src/com/unciv/logic/map/action/BuildLongRoadAction.kt b/core/src/com/unciv/logic/map/action/BuildLongRoadAction.kt index 2fdbcce9c8..108ace771e 100644 --- a/core/src/com/unciv/logic/map/action/BuildLongRoadAction.kt +++ b/core/src/com/unciv/logic/map/action/BuildLongRoadAction.kt @@ -1,5 +1,6 @@ package com.unciv.logic.map.action +import com.badlogic.gdx.graphics.Color import com.unciv.logic.map.BFS import com.unciv.logic.map.MapUnit import com.unciv.logic.map.TileInfo @@ -38,6 +39,7 @@ class BuildLongRoadAction( if (stepForward(target)) { startWorkingOnRoad() } else if (unit.currentMovement > 1f) { + unit.civInfo.addNotification("[${unit.name}] canceled building road: can't move forward.", unit.currentTile.position, Color.GRAY) unit.action = null return } @@ -52,13 +54,17 @@ class BuildLongRoadAction( for (step in getPath(destination).drop(1)) { if(step !in tilesUnitCanCurrentlyReach) return false // we're out of tiles in reachable distance, no need to check any further - if (unit.currentMovement > 0f && unit.canMoveTo(step)) { - unit.moveToTile(step) - success = true - - // if there is a road already, take multiple steps, otherwise this is where we're going to build a road - if (!isRoadFinished(step)) return true - + if (unit.currentMovement > 0f) { + if(unit.canMoveTo(step)) { + unit.moveToTile(step) + success = true + // if there is a road already, take multiple steps, otherwise this is where we're going to build a road + if (!isRoadFinished(step)) return true + } + else if(!isRoadFinished(step)){ + unit.civInfo.addNotification("[${unit.name}] skipped building road. It can't move here.", step.position, Color.GRAY) + } + // worker moves on even if the current step is blocked } else break } return success From c31f6bc7dd8389bdb1033a8d6e9b5ac75e373231 Mon Sep 17 00:00:00 2001 From: martin Date: Thu, 23 May 2019 00:04:34 +0200 Subject: [PATCH 8/8] show context menu entry only if roads are researched --- core/src/com/unciv/logic/map/action/BuildLongRoadAction.kt | 2 ++ desktop/src/com/unciv/app/desktop/DesktopLauncher.java | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/com/unciv/logic/map/action/BuildLongRoadAction.kt b/core/src/com/unciv/logic/map/action/BuildLongRoadAction.kt index 108ace771e..9a9cf45131 100644 --- a/core/src/com/unciv/logic/map/action/BuildLongRoadAction.kt +++ b/core/src/com/unciv/logic/map/action/BuildLongRoadAction.kt @@ -3,6 +3,7 @@ package com.unciv.logic.map.action import com.badlogic.gdx.graphics.Color import com.unciv.logic.map.BFS import com.unciv.logic.map.MapUnit +import com.unciv.logic.map.RoadStatus import com.unciv.logic.map.TileInfo class BuildLongRoadAction( @@ -17,6 +18,7 @@ class BuildLongRoadAction( override fun isAvailable(): Boolean = unit.hasUnique("Can build improvements on tiles") && getPath(target).isNotEmpty() + && unit.civInfo.tech.getBestRoadAvailable() != RoadStatus.None override fun doPreTurnAction() { diff --git a/desktop/src/com/unciv/app/desktop/DesktopLauncher.java b/desktop/src/com/unciv/app/desktop/DesktopLauncher.java index 402744a21d..0a7af1d2d6 100644 --- a/desktop/src/com/unciv/app/desktop/DesktopLauncher.java +++ b/desktop/src/com/unciv/app/desktop/DesktopLauncher.java @@ -17,7 +17,7 @@ class DesktopLauncher { // This is so they don't look all pixelated settings.filterMag = Texture.TextureFilter.MipMapLinearLinear; settings.filterMin = Texture.TextureFilter.MipMapLinearLinear; - TexturePacker.process(settings, "../images", ".", "game"); + TexturePacker.process(settings, "../Images", ".", "game"); LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); new LwjglApplication(new UnCivGame("Desktop"), config);