diff --git a/android/assets/jsons/Civ V - Gods & Kings/Units.json b/android/assets/jsons/Civ V - Gods & Kings/Units.json index e9468a09cf..024a9c6723 100644 --- a/android/assets/jsons/Civ V - Gods & Kings/Units.json +++ b/android/assets/jsons/Civ V - Gods & Kings/Units.json @@ -1362,7 +1362,7 @@ "strength": 65, "cost": 375, "requiredTech": "Radar", - "uniques": ["May Paradrop up to [5] tiles from inside friendly territory", "Never appears as a Barbarian unit"], + "uniques": ["May Paradrop to [Land] tiles up to [5] tiles away ", "Never appears as a Barbarian unit"], "attackSound": "shot" // upgradesTo "XCOM Squad", "No Movement Cost to Pillage" in BNW }, diff --git a/android/assets/jsons/Civ V - Vanilla/Units.json b/android/assets/jsons/Civ V - Vanilla/Units.json index fbafb92f13..9f4c5fe161 100644 --- a/android/assets/jsons/Civ V - Vanilla/Units.json +++ b/android/assets/jsons/Civ V - Vanilla/Units.json @@ -1039,7 +1039,7 @@ "strength": 65, "cost": 375, "requiredTech": "Radar", - "uniques": ["May Paradrop up to [5] tiles from inside friendly territory", "Never appears as a Barbarian unit"], + "uniques": ["May Paradrop to [Land] tiles up to [5] tiles away ", "Never appears as a Barbarian unit"], "attackSound": "shot" // upgradesTo "XCOM Squad", "No Movement Cost to Pillage" in BNW }, diff --git a/core/src/com/unciv/logic/map/mapunit/MapUnitCache.kt b/core/src/com/unciv/logic/map/mapunit/MapUnitCache.kt index a921159ffd..0eb053deb0 100644 --- a/core/src/com/unciv/logic/map/mapunit/MapUnitCache.kt +++ b/core/src/com/unciv/logic/map/mapunit/MapUnitCache.kt @@ -59,7 +59,9 @@ class MapUnitCache(private val mapUnit: MapUnit) { var canEnterCityStates: Boolean = false var costToDisembark: Float? = null var costToEmbark: Float? = null - var paradropRange = 0 + + /** A hashmap where the key represents the tileFilter, and the value is how far away the tile could be */ + val paradropDestinationTileFilters = mutableMapOf() var hasUniqueToBuildImprovements = false // not canBuildImprovements to avoid confusion var hasUniqueToCreateWaterImprovements = false diff --git a/core/src/com/unciv/logic/map/mapunit/movement/UnitMovement.kt b/core/src/com/unciv/logic/map/mapunit/movement/UnitMovement.kt index b48a6f9254..958b1ef106 100644 --- a/core/src/com/unciv/logic/map/mapunit/movement/UnitMovement.kt +++ b/core/src/com/unciv/logic/map/mapunit/movement/UnitMovement.kt @@ -289,7 +289,7 @@ class UnitMovement(val unit: MapUnit) { unit.baseUnit.movesLikeAirUnits -> unit.currentTile.aerialDistanceTo(destination) <= unit.getMaxMovementForAirUnits() unit.isPreparingParadrop() -> - unit.currentTile.aerialDistanceTo(destination) <= unit.cache.paradropRange && canParadropOn(destination) + canParadropOn(destination, unit.currentTile.aerialDistanceTo(destination)) else -> specificFunction(destination) // Note: Could pass destination as implicit closure from outer fun to lambda, but explicit is clearer } @@ -303,9 +303,10 @@ class UnitMovement(val unit: MapUnit) { unit.cache.cannotMove -> sequenceOf(unit.getTile()) unit.baseUnit.movesLikeAirUnits -> unit.getTile().getTilesInDistanceRange(IntRange(1, unit.getMaxMovementForAirUnits())) - unit.isPreparingParadrop() -> - unit.getTile().getTilesInDistance(unit.cache.paradropRange) - .filter { unit.movement.canParadropOn(it) } + unit.isPreparingParadrop() -> { + unit.getTile().getTilesInDistance(unit.cache.paradropDestinationTileFilters.maxOf { it.value } ) + .filter { unit.movement.canParadropOn(it, it.aerialDistanceTo(unit.getTile())) } + } includeOtherEscortUnit && unit.isEscorting() -> { val otherUnitTiles = unit.getOtherEscortUnit()!!.movement.getReachableTilesInCurrentTurn(false).toSet() unit.movement.getDistanceToTiles().filter { otherUnitTiles.contains(it.key) }.keys.asSequence() @@ -636,12 +637,19 @@ class UnitMovement(val unit: MapUnit) { } // Can a paratrooper land at this tile? - private fun canParadropOn(destination: Tile): Boolean { + private fun canParadropOn(destination: Tile, distance: Int): Boolean { if (unit.cache.cannotMove) return false - // Can only move to land tiles within range that are visible and not impassible + + // Can only move to tiles within range that are visible and not impassible // Based on some testing done in the base game - if (!destination.isLand || destination.isImpassible() || !unit.civ.viewableTiles.contains(destination)) return false - return true + if (destination.isImpassible() || !unit.civ.viewableTiles.contains(destination)) return false + + // The destination is valid if any of the `tileFilters` match, and is within range + for ((tileFilter, distanceAllowed) in unit.cache.paradropDestinationTileFilters) { + if (distance <= distanceAllowed && destination.matchesFilter(tileFilter, unit.civ)) return true + } + + return false } /** diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index 6a5c58381c..ec826fd327 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -377,7 +377,9 @@ enum class UniqueType( PreventSpreadingReligion("Prevents spreading of religion to the city it is next to", UniqueTarget.Unit), RemoveOtherReligions("Removes other religions when spreading religion", UniqueTarget.Unit), - MayParadrop("May Paradrop up to [amount] tiles from inside friendly territory", UniqueTarget.Unit), + @Deprecated("As of 4.17.4", ReplaceWith("May Paradrop to [Land] tiles up to [positiveAmount] tiles away "), DeprecationLevel.WARNING) + MayParadropOld("May Paradrop up to [positiveAmount] tiles from inside friendly territory", UniqueTarget.Unit), + MayParadrop("May Paradrop to [tileFilter] tiles up to [positiveAmount] tiles away", UniqueTarget.Unit), CanAirsweep("Can perform Air Sweep", UniqueTarget.Unit), CanSpeedupConstruction("Can speed up construction of a building", UniqueTarget.Unit), diff --git a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt index 0795121068..e04f1017e2 100644 --- a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt +++ b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt @@ -545,6 +545,8 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction { UniqueType.MayParadrop // Paradrop - 25% bonus -> power += power / 4 + UniqueType.MayParadropOld // ParadropOld - 25% bonus + -> power += power / 4 UniqueType.MustSetUp // Must set up - 20 % penalty -> power -= power / 5 UniqueType.AdditionalAttacks // Extra attacks - 20% bonus per extra attack diff --git a/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt b/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt index 624abec324..981e575a95 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt @@ -142,10 +142,29 @@ object UnitActionsFromUniques { } internal fun getParadropActions(unit: MapUnit, tile: Tile): Sequence { - val paradropUniques = - unit.getMatchingUniques(UniqueType.MayParadrop) - if (!paradropUniques.any() || unit.isEmbarked()) return emptySequence() - unit.cache.paradropRange = paradropUniques.maxOfOrNull { it.params[0] }!!.toInt() + unit.cache.paradropDestinationTileFilters.clear() + + // Support the old paradrop unique, going from Friendly Land to any Land tile + val paradropOldUniques = unit.getMatchingUniques(UniqueType.MayParadropOld) + if (paradropOldUniques.any() && !unit.isEmbarked() && !unit.getTile().isWater && unit.getTile().isFriendlyTerritory(unit.civ)) { + unit.cache.paradropDestinationTileFilters["Land"] = paradropOldUniques.maxOf { it.params[0] }.toInt() + } + + // Retrieve all parardrop uniques, considering the state of the unit + val paradropUniques = unit.getMatchingUniques(UniqueType.MayParadrop, unit.cache.state) + + // Construct the list of possible destination tile filters, keeping the largest distance + for (unique in paradropUniques) { + val tileFilter = unique.params[0] + val distance = unique.params[1].toInt() + val existingDistance = unit.cache.paradropDestinationTileFilters[tileFilter] + if (existingDistance == null || distance > existingDistance) { + unit.cache.paradropDestinationTileFilters[tileFilter] = distance + } + } + + if (unit.cache.paradropDestinationTileFilters.isEmpty()) return emptySequence() + return sequenceOf(UnitAction(UnitActionType.Paradrop, isCurrentAction = unit.isPreparingParadrop(), useFrequency = 60f, // While it is important to see, it isn't nessesary used a lot @@ -153,9 +172,7 @@ object UnitActionsFromUniques { if (unit.isPreparingParadrop()) unit.action = null else unit.action = UnitActionType.Paradrop.value }.takeIf { - !unit.hasUnitMovedThisTurn() && - tile.isFriendlyTerritory(unit.civ) && - !tile.isWater + !unit.hasUnitMovedThisTurn() }) ) } diff --git a/docs/Modders/uniques.md b/docs/Modders/uniques.md index 51c18a9321..8e0c7fe77a 100644 --- a/docs/Modders/uniques.md +++ b/docs/Modders/uniques.md @@ -1861,8 +1861,8 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl ??? example "Removes other religions when spreading religion" Applicable to: Unit -??? example "May Paradrop up to [amount] tiles from inside friendly territory" - Example: "May Paradrop up to [3] tiles from inside friendly territory" +??? example "May Paradrop to [tileFilter] tiles up to [positiveAmount] tiles away" + Example: "May Paradrop to [Farm] tiles up to [3] tiles away" Applicable to: Unit