implementing BuildLongRoadAction

This commit is contained in:
martin 2019-05-21 01:34:07 +02:00 committed by Yair Morgenstern
parent a8c3b3755b
commit 98803f0454
12 changed files with 177 additions and 48 deletions

View file

@ -227,6 +227,10 @@
Japanese:"自動化を停止"
}
"Construct road": {
"German": "Straße bauen"
}
"Fortify":{
Italian:"Fortifica"
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,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]!!

View file

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

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

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(),
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")
}

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

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