Merge branch 'roadOnLongClick'

This commit is contained in:
Yair Morgenstern 2019-05-23 22:21:46 +03:00
commit 7e6b102f27
20 changed files with 598 additions and 321 deletions

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 988 KiB

After

Width:  |  Height:  |  Size: 988 KiB

View File

@ -227,6 +227,10 @@
Japanese:"自動化を停止"
}
"Construct road": {
"German": "Straße bauen"
}
"Fortify":{
Italian:"Fortifica"
Russian:"Укрепить"
@ -258,7 +262,7 @@
Romanian:"Dormi"
Simplified_Chinese:"休眠"
Portuguese:"Dormir"
German:"Schlafen legen"
German:"Schlafen"
French:"Dormir"
Japanese:"睡眠"
Russian:"Спать"
@ -266,6 +270,10 @@
Spanish:"Dormir"
}
"Moving": {
"German": "Bewegen"
}
"Set up":{ // For siege units
Italian:"Monta"
Russian:"Подготовится"

View File

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

View File

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

View File

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

View File

@ -6,6 +6,8 @@ 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.logic.map.action.StringAction
import com.unciv.models.gamebasics.GameBasics
import com.unciv.models.gamebasics.tech.TechEra
import com.unciv.models.gamebasics.tile.TerrainType
@ -16,6 +18,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 +35,15 @@ 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 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()
var due: Boolean = true
@ -169,7 +180,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
@ -275,6 +287,7 @@ class MapUnit {
//region state-changing functions
fun setTransients(){
promotions.unit=this
mapUnitAction?.unit = this
baseUnit=GameBasics.Units[name]!!
updateUniques()
}
@ -292,10 +305,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())
@ -368,22 +384,19 @@ class MapUnit {
}
}
/**
* @return The tile that we reached this turn
*/
fun moveToTile(otherTile: TileInfo) {
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()
if(otherTile.isCityCenter() && otherTile.getOwner()!=civInfo) throw Exception("This is an enemy city, you can't go here!")
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]!!
if (currentMovement < 0.1) currentMovement = 0f // silly floats which are "almost zero"

View File

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

View File

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

View File

@ -0,0 +1,103 @@
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(
mapUnit: MapUnit = MapUnit(),
val target: TileInfo = TileInfo()
) : MapUnitAction(mapUnit) {
override fun name(): String = "Build Long Road"
override fun shouldStopOnEnemyInSight(): Boolean = true
override fun isAvailable(): Boolean
= unit.hasUnique("Can build improvements on tiles")
&& getPath(target).isNotEmpty()
&& unit.civInfo.tech.getBestRoadAvailable() != RoadStatus.None
override fun doPreTurnAction() {
// we're working!
if (unit.currentTile.improvementInProgress != null)
return
if (startWorkingOnRoad())
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)) {
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
}
}
// 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
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) {
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
}
private fun isRoadFinished(tile: TileInfo): Boolean {
return tile.roadStatus >= unit.civInfo.tech.getBestRoadAvailable()
}
private fun getPath(destination: TileInfo): List<TileInfo> {
// 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 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) {
tile.startWorkingOnImprovement(improvement, unit.civInfo)
return true
}
}
}
return false
}
}

View File

@ -0,0 +1,15 @@
package com.unciv.logic.map.action
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() {}
open fun shouldStopOnEnemyInSight(): Boolean = false
}

View File

@ -0,0 +1,27 @@
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")
override fun name(): String {
return when {
// translate string-encoded actions to user-readable names
action.startsWith("moveTo") -> "Moving"
else -> action
}
}
}

View File

@ -103,7 +103,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)
@ -259,6 +259,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())
@ -274,6 +275,8 @@ class EmpireOverviewScreen : CameraStageBaseScreen(){
UnCivGame.Current.worldScreen.tileMapHolder.setCenterPosition(unit.currentTile.position)
}
table.add(button).left()
val mapUnitAction = unit.mapUnitAction
if (mapUnitAction != null) table.add(mapUnitAction.name().tr()) else 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())

View File

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

View File

@ -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
@ -121,7 +130,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
}
@ -130,7 +139,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 })
@ -147,48 +156,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) {
@ -216,20 +222,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<Vector2>) {

View File

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

View File

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

View File

@ -0,0 +1,85 @@
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 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() {
init {
space(10f)
addButton(ImageGetter.getStatIcon("Movement"), "Move unit") {
onMoveButtonClick()
}
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)
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
}
}
}

View File

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

View File

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