mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-21 05:09:25 +07:00
Added unit escorting formation!!! (#11057)
* Added escort button * Added basic escort movement * Improved escort movement * Swapping breaks escorting * Added stop escorting button * Added link icon to unit * getDistanceToTiles() now automatically includes escorting * Multi-turn movement with different units works somewhat * Escorting units persist to escort across saves * Escorting units are only idle if their partner unit is idle as well * Fixed multi-turn escort movement where one unit has more movement points left over * Added basic tests * Added a test for formation idle units * Added some basic movement tests * Added some canMoveTo tests * getDistanceToTiles only caches when includeEscort is true * added getDistanceToTiles test * An entire commit to remove one line of white space just for you! And yes, there are no semi-colons; * Added translations * Added more stopEscorting() calls when the unit is removed * Added extra comments and refactoring * Refactored removeAllTilesNotInSet to use a mutableIterator * Refactored code based on review * Refactored removing tiles in PathsToTilesWithinTurn that aren't in another PathsToTilesWithinTurn
This commit is contained in:
@ -1107,6 +1107,8 @@ Sleep =
|
||||
Sleep until healed =
|
||||
Moving =
|
||||
Set up =
|
||||
Escort formation =
|
||||
Stop Escort formation =
|
||||
Paradrop =
|
||||
Air Sweep =
|
||||
Add in capital =
|
||||
|
@ -60,6 +60,8 @@ class MapUnit : IsPartOfGameInfoSerialization {
|
||||
// Connect roads implies automated is true. It is specified by the action type.
|
||||
var action: String? = null
|
||||
var automated: Boolean = false
|
||||
// We can infer who we are escorting based on our tile
|
||||
var escorting: Boolean = false
|
||||
|
||||
var automatedRoadConnectionDestination: Vector2? = null
|
||||
var automatedRoadConnectionPath: List<Vector2>? = null
|
||||
@ -177,6 +179,7 @@ class MapUnit : IsPartOfGameInfoSerialization {
|
||||
toReturn.health = health
|
||||
toReturn.action = action
|
||||
toReturn.automated = automated
|
||||
toReturn.escorting = escorting
|
||||
toReturn.automatedRoadConnectionDestination = automatedRoadConnectionDestination
|
||||
toReturn.automatedRoadConnectionPath = automatedRoadConnectionPath
|
||||
toReturn.attacksThisTurn = attacksThisTurn
|
||||
@ -231,13 +234,18 @@ class MapUnit : IsPartOfGameInfoSerialization {
|
||||
fun isPreparingAirSweep() = action == UnitActionType.AirSweep.value
|
||||
fun isSetUpForSiege() = action == UnitActionType.SetUp.value
|
||||
|
||||
fun isIdle(): Boolean {
|
||||
/**
|
||||
* @param includeOtherEscortUnit determines whether or not this method will also check if it's other escort unit is idle if it has one
|
||||
* Leave it as default unless you know what [isIdle] does.
|
||||
*/
|
||||
fun isIdle(includeOtherEscortUnit: Boolean = true): Boolean {
|
||||
if (currentMovement == 0f) return false
|
||||
val tile = getTile()
|
||||
if (tile.improvementInProgress != null &&
|
||||
canBuildImprovement(tile.getTileImprovementInProgress()!!) &&
|
||||
!tile.isMarkedForCreatesOneImprovement()
|
||||
) return false
|
||||
if (includeOtherEscortUnit && isEscorting() && !getOtherEscortUnit()!!.isIdle(false)) return false
|
||||
return !(isFortified() || isExploring() || isSleeping() || isAutomated() || isMoving())
|
||||
}
|
||||
|
||||
@ -568,6 +576,20 @@ class MapUnit : IsPartOfGameInfoSerialization {
|
||||
return power
|
||||
}
|
||||
|
||||
fun getOtherEscortUnit(): MapUnit? {
|
||||
if (isCivilian()) return getTile().militaryUnit
|
||||
if (isMilitary()) return getTile().civilianUnit
|
||||
return null
|
||||
}
|
||||
|
||||
fun isEscorting(): Boolean {
|
||||
if (escorting) {
|
||||
if (getOtherEscortUnit() != null) return true
|
||||
escorting = false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun threatensCiv(civInfo: Civilization): Boolean {
|
||||
if (getTile().getOwner() == civInfo)
|
||||
return true
|
||||
@ -687,6 +709,7 @@ class MapUnit : IsPartOfGameInfoSerialization {
|
||||
fun doAction() {
|
||||
if (action == null) return
|
||||
if (currentMovement == 0f) return // We've already done stuff this turn, and can't do any more stuff
|
||||
if (isEscorting() && getOtherEscortUnit()!!.currentMovement == 0f) return
|
||||
|
||||
val enemyUnitsInWalkingDistance = movement.getDistanceToTiles().keys
|
||||
.filter { it.militaryUnit != null && civ.isAtWarWith(it.militaryUnit!!.civ) }
|
||||
@ -730,6 +753,7 @@ class MapUnit : IsPartOfGameInfoSerialization {
|
||||
}
|
||||
|
||||
fun destroy(destroyTransportedUnit: Boolean = true) {
|
||||
stopEscorting()
|
||||
val currentPosition = Vector2(getTile().position)
|
||||
civ.attacksSinceTurnStart.addAll(attacksSinceTurnStart.asSequence().map { Civilization.HistoricalAttackMemory(this.name, currentPosition, it) })
|
||||
currentMovement = 0f
|
||||
@ -746,6 +770,7 @@ class MapUnit : IsPartOfGameInfoSerialization {
|
||||
}
|
||||
|
||||
fun gift(recipient: Civilization) {
|
||||
stopEscorting()
|
||||
civ.units.removeUnit(this)
|
||||
civ.cache.updateViewableTiles()
|
||||
// all transported units should be gift as well
|
||||
@ -849,6 +874,22 @@ class MapUnit : IsPartOfGameInfoSerialization {
|
||||
moveThroughTile(tile)
|
||||
}
|
||||
|
||||
fun startEscorting() {
|
||||
if (getOtherEscortUnit() != null) {
|
||||
escorting = true
|
||||
getOtherEscortUnit()!!.escorting = true
|
||||
} else {
|
||||
escorting = false
|
||||
}
|
||||
movement.clearPathfindingCache()
|
||||
}
|
||||
|
||||
fun stopEscorting() {
|
||||
getOtherEscortUnit()?.escorting = false
|
||||
escorting = false
|
||||
movement.clearPathfindingCache()
|
||||
}
|
||||
|
||||
private fun clearEncampment(tile: Tile) {
|
||||
tile.removeImprovement()
|
||||
|
||||
|
@ -238,8 +238,13 @@ class UnitMovement(val unit: MapUnit) {
|
||||
* @return The tile that we reached this turn
|
||||
*/
|
||||
fun headTowards(destination: Tile): Tile {
|
||||
val escortUnit = if (unit.isEscorting()) unit.getOtherEscortUnit() else null
|
||||
val startTile = unit.getTile()
|
||||
val destinationTileThisTurn = getTileToMoveToThisTurn(destination)
|
||||
moveToTile(destinationTileThisTurn)
|
||||
if (startTile != unit.getTile() && escortUnit != null) {
|
||||
escortUnit.movement.headTowards(unit.getTile())
|
||||
}
|
||||
return unit.currentTile
|
||||
}
|
||||
|
||||
@ -261,7 +266,11 @@ class UnitMovement(val unit: MapUnit) {
|
||||
return getDistanceToTiles().containsKey(destination)
|
||||
}
|
||||
|
||||
fun getReachableTilesInCurrentTurn(): Sequence<Tile> {
|
||||
/**
|
||||
* @param includeOtherEscortUnit determines whether or not this method will also check its the other escort unit if it has one
|
||||
* Leave it as default unless you know what [getReachableTilesInCurrentTurn] does.
|
||||
*/
|
||||
fun getReachableTilesInCurrentTurn(includeOtherEscortUnit: Boolean = true): Sequence<Tile> {
|
||||
return when {
|
||||
unit.cache.cannotMove -> sequenceOf(unit.getTile())
|
||||
unit.baseUnit.movesLikeAirUnits() ->
|
||||
@ -269,8 +278,11 @@ class UnitMovement(val unit: MapUnit) {
|
||||
unit.isPreparingParadrop() ->
|
||||
unit.getTile().getTilesInDistance(unit.cache.paradropRange)
|
||||
.filter { unit.movement.canParadropOn(it) }
|
||||
else ->
|
||||
unit.movement.getDistanceToTiles().keys.asSequence()
|
||||
includeOtherEscortUnit && unit.isEscorting() -> {
|
||||
val otherUnitTiles = unit.getOtherEscortUnit()!!.movement.getReachableTilesInCurrentTurn(false).toSet()
|
||||
unit.movement.getDistanceToTiles().filter { otherUnitTiles.contains(it.key) }.keys.asSequence()
|
||||
}
|
||||
else -> unit.movement.getDistanceToTiles().keys.asSequence()
|
||||
}
|
||||
}
|
||||
|
||||
@ -322,6 +334,7 @@ class UnitMovement(val unit: MapUnit) {
|
||||
* CAN DESTROY THE UNIT.
|
||||
*/
|
||||
fun teleportToClosestMoveableTile() {
|
||||
unit.stopEscorting()
|
||||
if (unit.isTransported) return // handled when carrying unit is teleported
|
||||
var allowedTile: Tile? = null
|
||||
var distance = 0
|
||||
@ -370,6 +383,7 @@ class UnitMovement(val unit: MapUnit) {
|
||||
fun moveToTile(destination: Tile, considerZoneOfControl: Boolean = true) {
|
||||
if (destination == unit.getTile() || unit.isDestroyed) return // already here (or dead)!
|
||||
// Reset closestEnemy chache
|
||||
val escortUnit = if (unit.isEscorting()) unit.getOtherEscortUnit()!! else null
|
||||
|
||||
if (unit.baseUnit.movesLikeAirUnits()) { // air units move differently from all other units
|
||||
if (unit.action != UnitActionType.Automate.value) unit.action = null
|
||||
@ -477,6 +491,10 @@ class UnitMovement(val unit: MapUnit) {
|
||||
payload.isTransported = true // restore the flag to not leave the payload in the city
|
||||
payload.mostRecentMoveType = UnitMovementMemoryType.UnitMoved
|
||||
}
|
||||
if (escortUnit != null) {
|
||||
escortUnit.movement.moveToTile(finalTileReached)
|
||||
unit.startEscorting() // Need to re-apply this
|
||||
}
|
||||
|
||||
// Unit maintenance changed
|
||||
if (unit.canGarrison()
|
||||
@ -498,6 +516,7 @@ class UnitMovement(val unit: MapUnit) {
|
||||
* Precondition: this unit can swap-move to the given tile, as determined by canUnitSwapTo
|
||||
*/
|
||||
fun swapMoveToTile(destination: Tile) {
|
||||
unit.stopEscorting()
|
||||
val otherUnit = (
|
||||
if (unit.isCivilian())
|
||||
destination.civilianUnit
|
||||
@ -548,8 +567,10 @@ class UnitMovement(val unit: MapUnit) {
|
||||
/**
|
||||
* Designates whether we can enter the tile - without attacking
|
||||
* DOES NOT designate whether we can reach that tile in the current turn
|
||||
* @param includeOtherEscortUnit determines whether or not this method will also check if the other escort unit [canMoveTo] if it has one.
|
||||
* Leave it as default unless you know what [canMoveTo] does.
|
||||
*/
|
||||
fun canMoveTo(tile: Tile, assumeCanPassThrough: Boolean = false, canSwap: Boolean = false): Boolean {
|
||||
fun canMoveTo(tile: Tile, assumeCanPassThrough: Boolean = false, canSwap: Boolean = false, includeOtherEscortUnit: Boolean = true): Boolean {
|
||||
if (unit.baseUnit.movesLikeAirUnits())
|
||||
return canAirUnitMoveTo(tile, unit)
|
||||
|
||||
@ -560,6 +581,10 @@ class UnitMovement(val unit: MapUnit) {
|
||||
if (isCityCenterCannotEnter(tile))
|
||||
return false
|
||||
|
||||
if (includeOtherEscortUnit && unit.isEscorting()
|
||||
&& !unit.getOtherEscortUnit()!!.movement.canMoveTo(tile, assumeCanPassThrough,canSwap, includeOtherEscortUnit = false))
|
||||
return false
|
||||
|
||||
return if (unit.isCivilian())
|
||||
(tile.civilianUnit == null || (canSwap && tile.civilianUnit!!.owner == unit.owner))
|
||||
&& (tile.militaryUnit == null || tile.militaryUnit!!.owner == unit.owner)
|
||||
@ -600,8 +625,10 @@ class UnitMovement(val unit: MapUnit) {
|
||||
* This is the most called function in the entire game,
|
||||
* so multiple callees of this function have been optimized,
|
||||
* because optimization on this function results in massive benefits!
|
||||
* @param includeOtherEscortUnit determines whether or not this method will also check if the other escort unit [canPassThrough] if it has one.
|
||||
* Leave it as default unless you know what [canPassThrough] does.
|
||||
*/
|
||||
fun canPassThrough(tile: Tile): Boolean {
|
||||
fun canPassThrough(tile: Tile, includeOtherEscortUnit: Boolean = true): Boolean {
|
||||
if (tile.isImpassible()) {
|
||||
// special exception - ice tiles are technically impassible, but some units can move through them anyway
|
||||
// helicopters can pass through impassable tiles like mountains
|
||||
@ -649,15 +676,21 @@ class UnitMovement(val unit: MapUnit) {
|
||||
if (unit.civ.isAtWarWith(firstUnit.civ))
|
||||
return false
|
||||
}
|
||||
|
||||
if (includeOtherEscortUnit && unit.isEscorting() && !unit.getOtherEscortUnit()!!.movement.canPassThrough(tile,false))
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param includeOtherEscortUnit determines whether or not this method will also check if the other escort units [getDistanceToTiles] if it has one.
|
||||
* Leave it as default unless you know what [getDistanceToTiles] does.
|
||||
*/
|
||||
fun getDistanceToTiles(
|
||||
considerZoneOfControl: Boolean = true,
|
||||
passThroughCache: HashMap<Tile, Boolean> = HashMap(),
|
||||
movementCostCache: HashMap<Pair<Tile, Tile>, Float> = HashMap())
|
||||
movementCostCache: HashMap<Pair<Tile, Tile>, Float> = HashMap(),
|
||||
includeOtherEscortUnit: Boolean = true)
|
||||
: PathsToTilesWithinTurn {
|
||||
val cacheResults = pathfindingCache.getDistanceToTiles(considerZoneOfControl)
|
||||
if (cacheResults != null) {
|
||||
@ -671,7 +704,17 @@ class UnitMovement(val unit: MapUnit) {
|
||||
passThroughCache,
|
||||
movementCostCache
|
||||
)
|
||||
|
||||
if (includeOtherEscortUnit) {
|
||||
// Only save to cache only if we are the original call and not the subsequent escort unit call
|
||||
pathfindingCache.setDistanceToTiles(considerZoneOfControl, distanceToTiles)
|
||||
if (unit.isEscorting()) {
|
||||
// We should only be able to move to tiles that our escort can also move to
|
||||
val escortDistanceToTiles = unit.getOtherEscortUnit()!!.movement
|
||||
.getDistanceToTiles(considerZoneOfControl, includeOtherEscortUnit = false)
|
||||
distanceToTiles.keys.removeIf { !escortDistanceToTiles.containsKey(it) }
|
||||
}
|
||||
}
|
||||
return distanceToTiles
|
||||
}
|
||||
|
||||
|
@ -97,6 +97,10 @@ enum class UnitActionType(
|
||||
/** UI "page" preference, 0-based - Dynamic overrides to this are in `UnitActions.actionTypeToPageGetter` */
|
||||
val defaultPage: Int
|
||||
) {
|
||||
StopEscortFormation("Stop Escort formation",
|
||||
{ ImageGetter.getImage("OtherIcons/Stop") }, false, defaultPage = 1),
|
||||
EscortFormation("Escort formation",
|
||||
{ ImageGetter.getImage("OtherIcons/Link") }, false, defaultPage = 1),
|
||||
SwapUnits("Swap units",
|
||||
{ ImageGetter.getUnitActionPortrait("Swap") }, false, defaultPage = 1),
|
||||
Automate("Automate",
|
||||
|
@ -177,6 +177,7 @@ class UnitGroup(val unit: MapUnit, val size: Float) : Group() {
|
||||
private fun getActionImage(): Image? {
|
||||
return when {
|
||||
unit.isSleeping() -> ImageGetter.getImage("UnitActionIcons/Sleep")
|
||||
unit.isEscorting() -> ImageGetter.getImage("OtherIcons/Link")
|
||||
unit.isMoving() -> ImageGetter.getImage("UnitActionIcons/MoveTo")
|
||||
unit.isExploring() -> ImageGetter.getImage("UnitActionIcons/Explore")
|
||||
unit.getTile().improvementInProgress!=null && unit.canBuildImprovement(unit.getTile().getTileImprovementInProgress()!!) ->
|
||||
|
@ -158,11 +158,42 @@ object UnitActions {
|
||||
GUI.getMap().setCenterPosition(unit.getMovementDestination().position, true)
|
||||
})
|
||||
}
|
||||
|
||||
addEscortAction(unit)
|
||||
addSwapAction(unit)
|
||||
addDisbandAction(unit)
|
||||
}
|
||||
|
||||
private suspend fun SequenceScope<UnitAction>.addEscortAction(unit: MapUnit) {
|
||||
// Air units cannot escort
|
||||
if (unit.baseUnit.movesLikeAirUnits()) return
|
||||
|
||||
val worldScreen = GUI.getWorldScreen()
|
||||
val selectedUnits = worldScreen.bottomUnitTable.selectedUnits
|
||||
if (selectedUnits.size == 2) {
|
||||
// We can still create a formation in the case that we have two units selected
|
||||
// and they are on the same tile. We still have to manualy confirm they are on the same tile here.
|
||||
val tile = selectedUnits.first().getTile()
|
||||
if (selectedUnits.last().getTile() != tile) return
|
||||
if (selectedUnits.any { it.baseUnit.movesLikeAirUnits() }) return
|
||||
} else if (selectedUnits.size != 1) {
|
||||
return
|
||||
}
|
||||
if (unit.getOtherEscortUnit() == null) return
|
||||
if (!unit.isEscorting()) {
|
||||
yield(UnitAction(
|
||||
type = UnitActionType.EscortFormation,
|
||||
action = {
|
||||
unit.startEscorting()
|
||||
}))
|
||||
} else {
|
||||
yield(UnitAction(
|
||||
type = UnitActionType.StopEscortFormation,
|
||||
action = {
|
||||
unit.stopEscorting()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun SequenceScope<UnitAction>.addSwapAction(unit: MapUnit) {
|
||||
// Air units cannot swap
|
||||
if (unit.baseUnit.movesLikeAirUnits()) return
|
||||
|
198
tests/src/com/unciv/logic/map/UnitFomationTests.kt
Normal file
198
tests/src/com/unciv/logic/map/UnitFomationTests.kt
Normal file
@ -0,0 +1,198 @@
|
||||
package com.unciv.logic.map
|
||||
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.unciv.Constants
|
||||
import com.unciv.logic.civilization.Civilization
|
||||
import com.unciv.testing.GdxTestRunner
|
||||
import com.unciv.testing.TestGame
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(GdxTestRunner::class)
|
||||
internal class UnitFormationTests {
|
||||
private lateinit var civInfo: Civilization
|
||||
|
||||
val testGame = TestGame()
|
||||
fun setUp(size: Int, baseTerrain: String = Constants.desert) {
|
||||
testGame.makeHexagonalMap(size)
|
||||
civInfo = testGame.addCiv()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `basic formation functionality civilian`() {
|
||||
setUp(1)
|
||||
val centerTile = testGame.getTile(Vector2(0f,0f))
|
||||
val civilianUnit = testGame.addUnit("Worker", civInfo, centerTile)
|
||||
val militaryUnit = testGame.addUnit("Warrior", civInfo, centerTile)
|
||||
assertTrue(civilianUnit.getOtherEscortUnit() != null)
|
||||
assertFalse(civilianUnit.isEscorting())
|
||||
civilianUnit.startEscorting()
|
||||
assertTrue(civilianUnit.isEscorting())
|
||||
assertTrue(militaryUnit.isEscorting())
|
||||
assertTrue(civilianUnit.getOtherEscortUnit() != null)
|
||||
civilianUnit.stopEscorting()
|
||||
assertFalse(civilianUnit.isEscorting())
|
||||
assertFalse(militaryUnit.isEscorting())
|
||||
assertTrue(civilianUnit.getOtherEscortUnit() != null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `basic formation functionality military`() {
|
||||
setUp(1)
|
||||
val centerTile = testGame.getTile(Vector2(0f,0f))
|
||||
val civilianUnit = testGame.addUnit("Worker", civInfo, centerTile)
|
||||
val militaryUnit = testGame.addUnit("Warrior", civInfo, centerTile)
|
||||
assertTrue(militaryUnit.getOtherEscortUnit() != null)
|
||||
assertFalse(militaryUnit.isEscorting())
|
||||
militaryUnit.startEscorting()
|
||||
assertTrue(militaryUnit.isEscorting())
|
||||
assertTrue(civilianUnit.isEscorting())
|
||||
assertTrue(militaryUnit.getOtherEscortUnit() != null)
|
||||
militaryUnit.stopEscorting()
|
||||
assertFalse(militaryUnit.isEscorting())
|
||||
assertFalse(civilianUnit.isEscorting())
|
||||
assertTrue(militaryUnit.getOtherEscortUnit() != null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `basic formation not available functionality`() {
|
||||
setUp(1)
|
||||
val centerTile = testGame.getTile(Vector2(0f,0f))
|
||||
val civilianUnit = testGame.addUnit("Worker", civInfo, centerTile)
|
||||
assertFalse(civilianUnit.getOtherEscortUnit() != null)
|
||||
assertFalse(civilianUnit.isEscorting())
|
||||
civilianUnit.startEscorting()
|
||||
assertFalse(civilianUnit.isEscorting())
|
||||
civilianUnit.destroy()
|
||||
val militaryUnit = testGame.addUnit("Warrior", civInfo, centerTile)
|
||||
assertFalse(militaryUnit.getOtherEscortUnit() != null)
|
||||
assertFalse(militaryUnit.isEscorting())
|
||||
militaryUnit.startEscorting()
|
||||
assertFalse(militaryUnit.isEscorting())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `formation idle units`() {
|
||||
setUp(1)
|
||||
val centerTile = testGame.getTile(Vector2(0f,0f))
|
||||
val civilianUnit = testGame.addUnit("Worker", civInfo, centerTile)
|
||||
val militaryUnit = testGame.addUnit("Warrior", civInfo, centerTile)
|
||||
civilianUnit.startEscorting()
|
||||
assertTrue(civilianUnit.isIdle())
|
||||
assertTrue(militaryUnit.isIdle())
|
||||
civilianUnit.currentMovement = 0f
|
||||
assertFalse(civilianUnit.isIdle())
|
||||
assertFalse(militaryUnit.isIdle())
|
||||
civilianUnit.currentMovement = 2f
|
||||
civInfo.tech.techsResearched.add(testGame.ruleset.tileImprovements["Farm"]!!.techRequired!!)
|
||||
centerTile.startWorkingOnImprovement(testGame.ruleset.tileImprovements["Farm"]!!, civInfo, civilianUnit)
|
||||
assertFalse(civilianUnit.isIdle())
|
||||
assertFalse(militaryUnit.isIdle())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `formation movement` () {
|
||||
setUp(3)
|
||||
val centerTile = testGame.getTile(Vector2(0f,0f))
|
||||
val civilianUnit = testGame.addUnit("Worker", civInfo, centerTile)
|
||||
val militaryUnit = testGame.addUnit("Warrior", civInfo, centerTile)
|
||||
civilianUnit.startEscorting()
|
||||
val targetTile = testGame.getTile(Vector2(0f,2f))
|
||||
civilianUnit.movement.moveToTile(targetTile)
|
||||
assert(civilianUnit.getTile() == targetTile)
|
||||
assert(militaryUnit.getTile() == targetTile)
|
||||
assertTrue(civilianUnit.isEscorting())
|
||||
assertTrue(militaryUnit.isEscorting())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `stop formation movement` () {
|
||||
setUp(3)
|
||||
val centerTile = testGame.getTile(Vector2(0f,0f))
|
||||
val civilianUnit = testGame.addUnit("Worker", civInfo, centerTile)
|
||||
val militaryUnit = testGame.addUnit("Warrior", civInfo, centerTile)
|
||||
civilianUnit.startEscorting()
|
||||
civilianUnit.stopEscorting()
|
||||
val targetTile = testGame.getTile(Vector2(0f,2f))
|
||||
civilianUnit.movement.moveToTile(targetTile)
|
||||
assert(civilianUnit.getTile() == targetTile)
|
||||
assert(militaryUnit.getTile() == centerTile)
|
||||
assertFalse(civilianUnit.isEscorting())
|
||||
assertFalse(militaryUnit.isEscorting())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `formation canMoveTo` () {
|
||||
setUp(3)
|
||||
val centerTile = testGame.getTile(Vector2(0f,0f))
|
||||
val civilianUnit = testGame.addUnit("Worker", civInfo, centerTile)
|
||||
val militaryUnit = testGame.addUnit("Warrior", civInfo, centerTile)
|
||||
val targetTile = testGame.getTile(Vector2(0f,2f))
|
||||
val blockingCivilianUnit = testGame.addUnit("Worker", civInfo, targetTile)
|
||||
assertFalse(civilianUnit.movement.canMoveTo(targetTile))
|
||||
assertTrue(militaryUnit.movement.canMoveTo(targetTile))
|
||||
civilianUnit.startEscorting()
|
||||
assertFalse(militaryUnit.movement.canMoveTo(targetTile))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `formation canMoveTo water` () {
|
||||
setUp(3, "Ocean")
|
||||
val centerTile = testGame.getTile(Vector2(0f,0f))
|
||||
centerTile.baseTerrain = "Coast"
|
||||
centerTile.isWater = true
|
||||
centerTile.isLand = false
|
||||
civInfo.tech.embarkedUnitsCanEnterOcean = true
|
||||
civInfo.tech.addTechnology("Astronomy")
|
||||
val civilianUnit = testGame.addUnit("Work Boats", civInfo, centerTile) // Can enter ocean
|
||||
val militaryUnit = testGame.addUnit("Trireme", civInfo, centerTile) // Can't enter ocean
|
||||
val targetTile = testGame.getTile(Vector2(0f,1f))
|
||||
targetTile.isWater = true
|
||||
targetTile.isLand = false
|
||||
targetTile.isOcean = true
|
||||
assertFalse(militaryUnit.movement.canMoveTo(targetTile))
|
||||
assertTrue(civilianUnit.movement.canMoveTo(targetTile))
|
||||
civilianUnit.startEscorting()
|
||||
assertFalse(civilianUnit.movement.canMoveTo(targetTile))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `formation head towards with faster units` () {
|
||||
setUp(5)
|
||||
val centerTile = testGame.getTile(Vector2(0f,0f))
|
||||
val civilianUnit = testGame.addUnit("Worker", civInfo, centerTile)
|
||||
val militaryUnit = testGame.addUnit("Horseman", civInfo, centerTile) // 4 movement
|
||||
civilianUnit.startEscorting()
|
||||
val targetTile = testGame.getTile(Vector2(0f,4f))
|
||||
val excpectedTile = testGame.getTile(Vector2(0f,2f))
|
||||
militaryUnit.movement.headTowards(targetTile)
|
||||
assert(civilianUnit.getTile() == excpectedTile)
|
||||
assert(militaryUnit.getTile() == excpectedTile)
|
||||
assertTrue(civilianUnit.isEscorting())
|
||||
assertTrue(militaryUnit.isEscorting())
|
||||
assertTrue(militaryUnit.currentMovement == 2f)
|
||||
assertFalse("The unit should not be idle if it's escort has no movement points",militaryUnit.isIdle())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getDistanceToTiles when in formation`() {
|
||||
setUp(5)
|
||||
val centerTile = testGame.getTile(Vector2(0f,0f))
|
||||
val civilianUnit = testGame.addUnit("Worker", civInfo, centerTile)
|
||||
val militaryUnit = testGame.addUnit("Horseman", civInfo, centerTile) // 4 movement
|
||||
civilianUnit.startEscorting()
|
||||
var civilianDistanceToTiles = civilianUnit.movement.getDistanceToTiles()
|
||||
assertFalse(militaryUnit.movement.getDistanceToTiles().any { !civilianDistanceToTiles.contains(it.key) })
|
||||
|
||||
// Test again with caching
|
||||
civilianUnit.stopEscorting()
|
||||
militaryUnit.movement.getDistanceToTiles()
|
||||
civilianUnit.movement.getDistanceToTiles()
|
||||
civilianUnit.startEscorting()
|
||||
civilianDistanceToTiles = civilianUnit.movement.getDistanceToTiles()
|
||||
assertFalse(militaryUnit.movement.getDistanceToTiles().any { !civilianDistanceToTiles.contains(it.key) })
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user