implementing BuildLongRoadAction
This commit is contained in:
parent
a8c3b3755b
commit
98803f0454
12 changed files with 177 additions and 48 deletions
|
@ -227,6 +227,10 @@
|
|||
Japanese:"自動化を停止"
|
||||
}
|
||||
|
||||
"Construct road": {
|
||||
"German": "Straße bauen"
|
||||
}
|
||||
|
||||
"Fortify":{
|
||||
Italian:"Fortifica"
|
||||
Russian:"Укрепить"
|
||||
|
|
|
@ -50,22 +50,19 @@ 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)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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]!!
|
||||
tilesReached[currentNode]?.let {
|
||||
currentNode = it
|
||||
path.add(currentNode)
|
||||
} ?: return ArrayList() // destination is not in our path
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
|
|
@ -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]!!
|
||||
|
|
|
@ -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") {
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -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]
|
||||
|
||||
}
|
||||
|
|
89
core/src/com/unciv/logic/map/action/BuildLongRoadAction.kt
Normal file
89
core/src/com/unciv/logic/map/action/BuildLongRoadAction.kt
Normal 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
|
||||
}
|
||||
|
||||
|
||||
}
|
15
core/src/com/unciv/logic/map/action/MapUnitAction.kt
Normal file
15
core/src/com/unciv/logic/map/action/MapUnitAction.kt
Normal 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")
|
||||
}
|
||||
|
||||
|
|
@ -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") {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue