Added the paratrooper unit (#4025)

* Added the paratrooper unit

* Reverted accidental reordering of action table

* Fixed Github build errors

* Hopefully actually fixed the build errors

* Added a Dutch translation, finally fixing the error

* Paratroopers can no longer actually paradrop on a tile they shouldn't be able to paradrop onto

* Removed double update action
This commit is contained in:
Xander Lenstra 2021-06-03 06:33:38 +02:00 committed by GitHub
parent a765caa97c
commit aa9dda2eea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 116 additions and 45 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -487,132 +487,139 @@ Panzer
orig: 100, 100
offset: 0, 0
index: -1
Persian Immortal
Paratrooper
rotate: false
xy: 1430, 308
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Pikeman
Persian Immortal
rotate: false
xy: 1532, 410
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Rifleman
Pikeman
rotate: false
xy: 1328, 105
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Rocket Artillery
Rifleman
rotate: false
xy: 1430, 206
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Samurai
Rocket Artillery
rotate: false
xy: 1532, 308
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Scout
Samurai
rotate: false
xy: 1634, 410
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Settler
Scout
rotate: false
xy: 1328, 3
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Ship of the Line
Settler
rotate: false
xy: 1430, 104
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Sipahi
Ship of the Line
rotate: false
xy: 1430, 2
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Slinger
Sipahi
rotate: false
xy: 1532, 206
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Spearman
Slinger
rotate: false
xy: 1634, 308
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Stealth Bomber
Spearman
rotate: false
xy: 1736, 410
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Submarine
Stealth Bomber
rotate: false
xy: 1532, 104
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Swordsman
Submarine
rotate: false
xy: 1532, 2
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Tank
Swordsman
rotate: false
xy: 1634, 206
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Tercio
Tank
rotate: false
xy: 1736, 308
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Trebuchet
Tercio
rotate: false
xy: 1838, 410
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Triplane
Trebuchet
rotate: false
xy: 1634, 104
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Triplane
rotate: false
xy: 1634, 2
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Trireme
rotate: false
xy: 1736, 205
@ -622,49 +629,49 @@ Trireme
index: -1
Turtle Ship
rotate: false
xy: 1634, 2
xy: 1838, 308
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
War Chariot
rotate: false
xy: 1838, 308
xy: 1940, 410
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
War Elephant
rotate: false
xy: 1940, 410
xy: 1736, 103
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Warrior
rotate: false
xy: 1736, 103
xy: 1838, 206
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Work Boats
rotate: false
xy: 1838, 206
xy: 1940, 308
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Worker
rotate: false
xy: 1940, 308
xy: 1838, 104
size: 100, 100
orig: 100, 100
offset: 0, 0
index: -1
Zero
rotate: false
xy: 1838, 104
xy: 1838, 2
size: 100, 100
orig: 100, 100
offset: 0, 0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 KiB

After

Width:  |  Height:  |  Size: 284 KiB

View File

@ -1152,7 +1152,6 @@
"uniques": ["+[20]% Strength in [Foreign Land]"],
"attackSound": "shot"
},
/*
{
"name": "Paratrooper",
"unitType": "Melee",
@ -1160,11 +1159,10 @@
"strength": 40,
"cost": 375,
"requiredTech": "Radar",
"uniques": ["May Paradrop"],
"uniques": ["May Paradrop up to [5] tiles from inside friendly territory"],
"attackSound": "shot"
// 65 strength in expansions, upgradesTo "XCOM Squad", "No Movement Cost to Pillage" in BNW
},
*/
{
"name": "Infantry",
"unitType": "Melee",

View File

@ -515,6 +515,7 @@ Sleep = Slaap
Sleep until healed = Slaap tot geheeld
Moving = Bewegen
Set up = Opstellen
Paradrop = Parachutelanding
Upgrade to [unitType] ([goldCost] gold) = Waardeer [unitType] op ([goldCost] goud)
Found city = Stad stichten
Promote = Promoveren
@ -522,7 +523,7 @@ Health = Gezondheid
Disband unit = Eenheid opheffen
Explore = Ontdekken
Stop exploration = Stop ontdekken
Pillage = Plundering
Pillage = Plunderen
Do you really want to disband this unit? = Wil je echt deze eenheid opheffen
Disband this unit for [goldAmount] gold? = Deze eenheid ontbinden voor [goldAmount] goud?
Create [improvement] = Maak [improvement]
@ -919,7 +920,7 @@ Land = Land
Wounded = Gewond
Marine = Marine
Mobile SAM = Mobiele grond-lucht raket
Paratrooper = Paracommando
Paratrooper = Parachutist
Helicopter Gunship = Helicopter Wapenschip
Atomic Bomb = Atoombom
Unbuildable = Onbouwbaar

View File

@ -504,6 +504,7 @@ Sleep =
Sleep until healed =
Moving =
Set up =
Paradrop =
Upgrade to [unitType] ([goldCost] gold) =
Found city =
Promote =

View File

@ -57,6 +57,8 @@ object Constants {
const val unitActionSleepUntilHealed = "Sleep until healed"
const val unitActionAutomation = "Automate"
const val unitActionExplore = "Explore"
const val unitActionParadrop = "Paradrop"
const val futureTech = "Future Tech"
const val cancelImprovementOrder = "Cancel improvement order"

View File

@ -71,6 +71,9 @@ class MapUnit {
@Transient
var cannotEnterOceanTilesUntilAstronomy = false
@Transient
var paradropRange = 0
lateinit var owner: String
/**
@ -547,6 +550,9 @@ class MapUnit {
action = null // wake up when healed
}
if (action == Constants.unitActionParadrop)
action = null
getCitadelDamage()
getTerrainDamage()
}

View File

@ -2,6 +2,7 @@ package com.unciv.logic.map
import com.badlogic.gdx.math.Vector2
import com.unciv.Constants
import com.unciv.logic.HexMath.getDistance
import com.unciv.logic.civilization.CivilizationInfo
class UnitMovementAlgorithms(val unit:MapUnit) {
@ -169,8 +170,8 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
val currentTile = unit.getTile()
if (currentTile == finalDestination) return currentTile
// head there directly
if (unit.type.isAirUnit()) return finalDestination
// If we can fly, head there directly
if (unit.type.isAirUnit() || unit.action == Constants.unitActionParadrop) return finalDestination
val distanceToTiles = getDistanceToTiles()
@ -209,6 +210,8 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
fun canReach(destination: TileInfo): Boolean {
if (unit.type.isAirUnit())
return unit.currentTile.aerialDistanceTo(destination) <= unit.getRange()*2
if (unit.action == Constants.unitActionParadrop)
return getDistance(unit.currentTile.position, destination.position) <= unit.paradropRange && canParadropOn(destination)
return getShortestPath(destination).any()
}
@ -239,13 +242,27 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
fun moveToTile(destination: TileInfo) {
if (destination == unit.getTile()) return // already here!
if (unit.type.isAirUnit()) { // they move differently from all other units
if (unit.type.isAirUnit()) { // air units move differently from all other units
unit.action = null
unit.removeFromTile()
unit.isTransported = false // it has left the carrier by own means
unit.putInTile(destination)
unit.currentMovement = 0f
return
} else if (unit.action == Constants.unitActionParadrop) { // paratrooping move differently
unit.action = null
unit.removeFromTile()
unit.putInTile(destination)
unit.currentMovement -= 1f
// Check if unit maintenance changed
// Is also done for other units, but because we skip everything else, we have to manually check it
// The reasong we skip everything, is that otherwise `getPathToTile()` throws an exception
// As we can not reach our destination in a single turn
if (unit.canGarrison()
&& (unit.getTile().isCityCenter() || destination.isCityCenter())
&& unit.civInfo.hasUnique("Units in cities cost no Maintenance")
) unit.civInfo.updateStatsForNextTurn()
return
}
val distanceToTiles = getDistanceToTiles()
@ -326,7 +343,14 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
}
return false
}
// Can a paratrooper land at this tile?
fun canParadropOn(destination: TileInfo): Boolean {
// Can only move to land tiles within range that are visible and not impassible
// Based on some testing done in the base game
if (!destination.isLand || destination.isImpassible() || !unit.civInfo.viewableTiles.contains(destination)) return false
return true
}
// This is the most called function in the entire game,
// so multiple callees of this function have been optimized,

View File

@ -10,17 +10,18 @@ data class UnitAction(
enum class UnitActionType(val value: String) {
Automate("Automate"),
StopMovement("Stop movement"),
StopAutomation("Stop automation"),
StopExploration("Stop exploration"),
StopMovement("Stop movement"),
Sleep("Sleep"),
SleepUntilHealed("Sleep until healed"),
Fortify("Fortify"),
FortifyUntilHealed("Fortify until healed"),
Explore("Explore"),
StopExploration("Stop exploration"),
Promote("Promote"),
Upgrade("Upgrade"),
Pillage("Pillage"),
Paradrop("Paradrop"),
SetUp("Set up"),
FoundCity("Found city"),
ConstructImprovement("Construct improvement"),

View File

@ -218,7 +218,11 @@ class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap
val turnsToGetThere = if (unit.type.isAirUnit()) {
if (unit.movement.canReach(tileInfo)) 1
else 0
} else unit.movement.getShortestPath(tileInfo).size // this is what takes the most time, tbh
} else if (unit.action == Constants.unitActionParadrop) {
if (unit.movement.canReach(tileInfo)) 1
else 0
} else
unit.movement.getShortestPath(tileInfo).size // this is what takes the most time, tbh
unitToTurnsToTile[unit] = turnsToGetThere
}
@ -393,11 +397,15 @@ class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap
}
val isAirUnit = unit.type.isAirUnit()
val tilesInMoveRange =
if (isAirUnit)
unit.getTile().getTilesInDistanceRange(IntRange(1, unit.getRange() * 2))
else
unit.movement.getDistanceToTiles().keys.asSequence()
val moveTileOverlayColor = if (unit.action == Constants.unitActionParadrop) Color.BLUE else Color.WHITE
val tilesInMoveRange =
if (isAirUnit)
unit.getTile().getTilesInDistanceRange(IntRange(1, unit.getRange() * 2))
else if (unit.action == Constants.unitActionParadrop)
unit.getTile().getTilesInDistance(unit.paradropRange)
.filter { unit.movement.canParadropOn(it) }
else
unit.movement.getDistanceToTiles().keys.asSequence()
for (tile in tilesInMoveRange) {
for (tileToColor in tileGroups[tile]!!) {
@ -411,7 +419,7 @@ class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap
}
if (unit.movement.canMoveTo(tile) ||
unit.movement.isUnknownTileWeShouldAssumeToBePassable(tile) && !unit.type.isAirUnit())
tileToColor.showCircle(Color.WHITE,
tileToColor.showCircle(moveTileOverlayColor,
if (UncivGame.Current.settings.singleTapMove || isAirUnit) 0.7f else 0.3f)
}
}

View File

@ -51,6 +51,7 @@ object UnitActions {
addPromoteAction(unit, actionList)
addUnitUpgradeAction(unit, actionList)
addPillageAction(unit, actionList, worldScreen)
addParadropAction(unit, actionList, worldScreen)
addSetupAction(unit, actionList)
addFoundCityAction(unit, actionList, tile)
addWorkerActions(unit, actionList, tile, worldScreen, unitTable)
@ -152,6 +153,26 @@ object UnitActions {
}.takeIf { unit.currentMovement > 0 && !isSetUp })
}
private fun addParadropAction(unit: MapUnit, actionList: ArrayList<UnitAction>, worldScreen: WorldScreen) {
val paradropUniques = unit.getMatchingUniques("May Paradrop up to [] tiles from inside friendly territory")
if (!paradropUniques.any() || unit.isEmbarked()) return
unit.paradropRange = paradropUniques.maxOfOrNull { it.params[0] }!!.toInt()
actionList += UnitAction(UnitActionType.Paradrop,
isCurrentAction = unit.action == Constants.unitActionParadrop,
action = {
if (unit.action != Constants.unitActionParadrop) {
unit.action = Constants.unitActionParadrop
} else {
unit.action = null
}
}.takeIf {
unit.currentMovement == unit.getMaxMovement().toFloat() &&
unit.currentTile.isFriendlyTerritory(unit.civInfo) &&
!unit.isEmbarked()
})
}
private fun addPillageAction(unit: MapUnit, actionList: ArrayList<UnitAction>, worldScreen: WorldScreen) {
val pillageAction = getPillageAction(unit)
if (pillageAction == null) return

View File

@ -49,12 +49,13 @@ class UnitActionsTable(val worldScreen: WorldScreen) : Table() {
"Start Golden Age" -> return UnitIconAndKey(ImageGetter.getUnitIcon("Great Artist"), 'g')
"Hurry Wonder" -> return UnitIconAndKey(ImageGetter.getUnitIcon("Great Engineer"), 'g')
"Conduct Trade Mission" -> return UnitIconAndKey(ImageGetter.getUnitIcon("Great Merchant"), 'g')
"Construct road" -> return UnitIconAndKey(ImageGetter.getImprovementIcon("Road"), 'r')
"Paradrop" -> return UnitIconAndKey(ImageGetter.getUnitIcon("Paratrooper"), 'p')
"Set up" -> return UnitIconAndKey(ImageGetter.getUnitIcon("Catapult"), 't')
"Disband unit" -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/DisbandUnit"))
"Explore" -> return UnitIconAndKey(ImageGetter.getUnitIcon("Scout"), 'x')
"Stop exploration" -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/Stop"), 'x')
"Pillage" -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/Pillage"), 'p')
"Construct road" -> return UnitIconAndKey(ImageGetter.getImprovementIcon("Road"), 'r')
"Disband unit" -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/DisbandUnit"))
else -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/Star"))
}
}

View File

@ -104,6 +104,7 @@ Unless otherwise specified, all the following are from [the Noun Project](https:
* [Nuclear Missile](https://thenounproject.com/marialuisa.iborra/collection/missiles-bombs/?i=1022574) By Lluisa Iborra, ES
* Icon for Carrier made by [JackRainy](https://github.com/JackRainy), based on [Aircraft Carrier](https://thenounproject.com/icolabs/collection/flat-icons-transport/?i=2332914) By IcoLabs, BR
* [Water Gun](https://thenounproject.com/term/water-gun/2121571) by ProSymbols for Marine
* [Parachute](https://thenounproject.com/term/parachute/2025018) by Nociconist for Paratrooper*
### Great People