From d8bb60f06cfd6aee7737703ba02062d2fa7c8111 Mon Sep 17 00:00:00 2001 From: SimonCeder <63475501+SimonCeder@users.noreply.github.com> Date: Thu, 28 Oct 2021 16:59:27 +0200 Subject: [PATCH] Barbarian fixes (#5573) * no new camps in 4 tiles for 15 turns after cleared * can't spawn land units on water or vice versa, unit choice * UniqueType.MustSetUp --- core/src/com/unciv/logic/BarbarianManager.kt | 63 ++++++++++++------- .../unciv/logic/automation/BattleHelper.kt | 2 +- core/src/com/unciv/logic/battle/Battle.kt | 2 +- .../unciv/models/ruleset/unique/UniqueType.kt | 1 + .../com/unciv/models/ruleset/unit/BaseUnit.kt | 2 +- .../unciv/ui/worldscreen/unit/UnitActions.kt | 2 +- 6 files changed, 45 insertions(+), 27 deletions(-) diff --git a/core/src/com/unciv/logic/BarbarianManager.kt b/core/src/com/unciv/logic/BarbarianManager.kt index a3644b6687..8e7d00db27 100644 --- a/core/src/com/unciv/logic/BarbarianManager.kt +++ b/core/src/com/unciv/logic/BarbarianManager.kt @@ -7,6 +7,7 @@ import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileMap import com.unciv.models.metadata.GameSpeed import com.unciv.models.ruleset.unique.UniqueType +import com.unciv.ui.utils.randomWeighted import java.util.* import kotlin.collections.HashMap import kotlin.math.max @@ -38,8 +39,7 @@ class BarbarianManager { for (tile in tileMap.values) { if (tile.improvement == Constants.barbarianEncampment && camps[tile.position] == null) { - val newCamp = Encampment() - newCamp.position = tile.position + val newCamp = Encampment(tile.position) camps[newCamp.position] = newCamp } } @@ -50,11 +50,17 @@ class BarbarianManager { fun updateEncampments() { // Check if camps were destroyed - for (position in camps.keys.toList()) { + val positionsToRemove = ArrayList() + for ((position, camp) in camps) { if (tileMap[position].improvement != Constants.barbarianEncampment) { - camps.remove(position) + camp.wasDestroyed() } + // Check if the ghosts are ready to depart + if (camp.destroyed && camp.countdown == 0) + positionsToRemove.add(position) } + for (position in positionsToRemove) + camps.remove(position) // Possibly place a new encampment placeBarbarianEncampment() @@ -83,7 +89,7 @@ class BarbarianManager { val fogTilesPerCamp = (tileMap.values.size.toFloat().pow(0.4f)).toInt() // Approximately // Check if we have more room - var campsToAdd = (fogTiles.size / fogTilesPerCamp) - camps.size + var campsToAdd = (fogTiles.size / fogTilesPerCamp) - camps.count { !it.value.destroyed } // First turn of the game add 1/3 of all possible camps if (gameInfo.turns == 1) { @@ -98,7 +104,9 @@ class BarbarianManager { val tooCloseToCapitals = gameInfo.civilizations.filterNot { it.isBarbarian() || it.isSpectator() || it.cities.isEmpty() || it.isCityState() } .flatMap { it.getCapital().getCenterTile().getTilesInDistance(4) }.toSet() val tooCloseToCamps = camps - .flatMap { tileMap[it.key].getTilesInDistance(7) }.toSet() + .flatMap { tileMap[it.key].getTilesInDistance( + if (it.value.destroyed) 4 else 7 + ) }.toSet() val viableTiles = fogTiles.filter { !it.isImpassible() @@ -127,8 +135,7 @@ class BarbarianManager { tile = viableTiles.random() tile.improvement = Constants.barbarianEncampment - val newCamp = Encampment() - newCamp.position = tile.position + val newCamp = Encampment(tile.position) newCamp.gameInfo = gameInfo camps[newCamp.position] = newCamp notifyCivsOfBarbarianEncampment(tile) @@ -160,26 +167,26 @@ class BarbarianManager { } } -class Encampment { +class Encampment (val position: Vector2) { var countdown = 0 var spawnedUnits = -1 - lateinit var position: Vector2 + var destroyed = false // destroyed encampments haunt the vicinity for 15 turns preventing new spawns @Transient lateinit var gameInfo: GameInfo fun clone(): Encampment { - val toReturn = Encampment() - toReturn.position = position + val toReturn = Encampment(position) toReturn.countdown = countdown toReturn.spawnedUnits = spawnedUnits + toReturn.destroyed = destroyed return toReturn } fun update() { if (countdown > 0) // Not yet countdown-- - else if (spawnBarbarian()) { // Countdown at 0, try to spawn a barbarian + else if (!destroyed && spawnBarbarian()) { // Countdown at 0, try to spawn a barbarian // Successful spawnedUnits++ resetCountdown() @@ -187,7 +194,15 @@ class Encampment { } fun wasAttacked() { - countdown /= 2 + if (!destroyed) + countdown /= 2 + } + + fun wasDestroyed() { + if (!destroyed) { + countdown = 15 + destroyed = true + } } /** Attempts to spawn a Barbarian from this encampment. Returns true if a unit was spawned. */ @@ -238,18 +253,20 @@ class Encampment { val barbarianCiv = gameInfo.getBarbarianCivilization() barbarianCiv.tech.techsResearched = allResearchedTechs.toHashSet() val unitList = gameInfo.ruleSet.units.values - .filter { it.isMilitary() } - .filter { it.isBuildable(barbarianCiv) } + .filter { it.isMilitary() && + it.isBuildable(barbarianCiv) && + !(it.hasUnique(UniqueType.MustSetUp) || it.hasUnique(UniqueType.CannotAttack)) && + (if (naval) it.isWaterUnit() else it.isLandUnit()) } - var unit = if (naval) - unitList.filter { it.isWaterUnit() }.randomOrNull() - else - unitList.filter { it.isLandUnit() }.randomOrNull() + if (unitList.isEmpty()) return null // No naval tech yet? Mad modders? - if (unit == null) // Didn't find a unit for preferred domain - unit = unitList.randomOrNull() // Try picking another + // Civ V weights its list by FAST_ATTACK or ATTACK_SEA AI types, we'll do it a bit differently + // getForceEvaluation is already conveniently biased towards fast units and against ranged naval + val weightings = unitList.map { it.getForceEvaluation().toFloat() } - return unit?.name // Could still be null in case of mad modders + val unit = unitList.randomWeighted(weightings) + + return unit.name } /** When a barbarian is spawned, seed the counter for next spawn */ diff --git a/core/src/com/unciv/logic/automation/BattleHelper.kt b/core/src/com/unciv/logic/automation/BattleHelper.kt index 24e3335fbf..1b69aa2ee4 100644 --- a/core/src/com/unciv/logic/automation/BattleHelper.kt +++ b/core/src/com/unciv/logic/automation/BattleHelper.kt @@ -49,7 +49,7 @@ object BattleHelper { // So the poor unit thought it could attack from the tile, but when it comes to do so it has no movement points! // Silly floats, basically - val unitMustBeSetUp = unit.hasUnique("Must set up to ranged attack") + val unitMustBeSetUp = unit.hasUnique(UniqueType.MustSetUp) val tilesToAttackFrom = if (stayOnTile || unit.baseUnit.movesLikeAirUnits()) sequenceOf(Pair(unit.currentTile, unit.currentMovement)) else diff --git a/core/src/com/unciv/logic/battle/Battle.kt b/core/src/com/unciv/logic/battle/Battle.kt index e8d90905e1..51e17bd34b 100644 --- a/core/src/com/unciv/logic/battle/Battle.kt +++ b/core/src/com/unciv/logic/battle/Battle.kt @@ -36,7 +36,7 @@ object Battle { * but the hidden tile is actually IMPASSIBLE so you stop halfway! */ if (attacker.getTile() != attackableTile.tileToAttackFrom) return - if (attacker.unit.hasUnique("Must set up to ranged attack") && !attacker.unit.isSetUpForSiege()) { + if (attacker.unit.hasUnique(UniqueType.MustSetUp) && !attacker.unit.isSetUpForSiege()) { attacker.unit.action = UnitActionType.SetUp.value attacker.unit.useMovementPoints(1f) } diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index 74d04e6955..360077d178 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -222,6 +222,7 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget) { MayEnhanceReligion("May enhance a religion", UniqueTarget.Unit), NormalVisionWhenEmbarked("Normal vision when embarked", UniqueTarget.Unit, UniqueTarget.Global), CannotAttack("Cannot attack", UniqueTarget.Unit), + MustSetUp("Must set up to ranged attack", UniqueTarget.Unit), @Deprecated("As of 3.16.11 - removed as of 3.17.11", ReplaceWith("[+1] Movement "), DeprecationLevel.ERROR) EmbarkedUnitMovement1("Increases embarked movement +1", UniqueTarget.Global), diff --git a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt index 2d6d760bd2..ff89d6c9ee 100644 --- a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt +++ b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt @@ -659,7 +659,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction { // unique.placeholderText == "May Paradrop up to [] tiles from inside friendly territory" // Paradrop - 25% bonus -> power += power / 4 - unique.placeholderText == "Must set up to ranged attack" // Must set up - 20 % penalty + unique.isOfType(UniqueType.MustSetUp) // Must set up - 20 % penalty -> power -= power / 5 unique.placeholderText == "[] additional attacks per turn" // Extra attacks - 20% bonus per extra attack -> power += (power * unique.params[0].toInt()) / 5 diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt index 188f4c1260..c0ea110afe 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt @@ -230,7 +230,7 @@ object UnitActions { } private fun addSetupAction(unit: MapUnit, actionList: ArrayList) { - if (!unit.hasUnique("Must set up to ranged attack") || unit.isEmbarked()) return + if (!unit.hasUnique(UniqueType.MustSetUp) || unit.isEmbarked()) return val isSetUp = unit.isSetUpForSiege() actionList += UnitAction(UnitActionType.SetUp, isCurrentAction = isSetUp,