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
This commit is contained in:
SimonCeder 2021-10-28 16:59:27 +02:00 committed by GitHub
parent 87d24e89c4
commit d8bb60f06c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 45 additions and 27 deletions

View File

@ -7,6 +7,7 @@ import com.unciv.logic.map.TileInfo
import com.unciv.logic.map.TileMap import com.unciv.logic.map.TileMap
import com.unciv.models.metadata.GameSpeed import com.unciv.models.metadata.GameSpeed
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.ui.utils.randomWeighted
import java.util.* import java.util.*
import kotlin.collections.HashMap import kotlin.collections.HashMap
import kotlin.math.max import kotlin.math.max
@ -38,8 +39,7 @@ class BarbarianManager {
for (tile in tileMap.values) { for (tile in tileMap.values) {
if (tile.improvement == Constants.barbarianEncampment if (tile.improvement == Constants.barbarianEncampment
&& camps[tile.position] == null) { && camps[tile.position] == null) {
val newCamp = Encampment() val newCamp = Encampment(tile.position)
newCamp.position = tile.position
camps[newCamp.position] = newCamp camps[newCamp.position] = newCamp
} }
} }
@ -50,11 +50,17 @@ class BarbarianManager {
fun updateEncampments() { fun updateEncampments() {
// Check if camps were destroyed // Check if camps were destroyed
for (position in camps.keys.toList()) { val positionsToRemove = ArrayList<Vector2>()
for ((position, camp) in camps) {
if (tileMap[position].improvement != Constants.barbarianEncampment) { 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 // Possibly place a new encampment
placeBarbarianEncampment() placeBarbarianEncampment()
@ -83,7 +89,7 @@ class BarbarianManager {
val fogTilesPerCamp = (tileMap.values.size.toFloat().pow(0.4f)).toInt() // Approximately val fogTilesPerCamp = (tileMap.values.size.toFloat().pow(0.4f)).toInt() // Approximately
// Check if we have more room // 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 // First turn of the game add 1/3 of all possible camps
if (gameInfo.turns == 1) { if (gameInfo.turns == 1) {
@ -98,7 +104,9 @@ class BarbarianManager {
val tooCloseToCapitals = gameInfo.civilizations.filterNot { it.isBarbarian() || it.isSpectator() || it.cities.isEmpty() || it.isCityState() } val tooCloseToCapitals = gameInfo.civilizations.filterNot { it.isBarbarian() || it.isSpectator() || it.cities.isEmpty() || it.isCityState() }
.flatMap { it.getCapital().getCenterTile().getTilesInDistance(4) }.toSet() .flatMap { it.getCapital().getCenterTile().getTilesInDistance(4) }.toSet()
val tooCloseToCamps = camps 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 { val viableTiles = fogTiles.filter {
!it.isImpassible() !it.isImpassible()
@ -127,8 +135,7 @@ class BarbarianManager {
tile = viableTiles.random() tile = viableTiles.random()
tile.improvement = Constants.barbarianEncampment tile.improvement = Constants.barbarianEncampment
val newCamp = Encampment() val newCamp = Encampment(tile.position)
newCamp.position = tile.position
newCamp.gameInfo = gameInfo newCamp.gameInfo = gameInfo
camps[newCamp.position] = newCamp camps[newCamp.position] = newCamp
notifyCivsOfBarbarianEncampment(tile) notifyCivsOfBarbarianEncampment(tile)
@ -160,26 +167,26 @@ class BarbarianManager {
} }
} }
class Encampment { class Encampment (val position: Vector2) {
var countdown = 0 var countdown = 0
var spawnedUnits = -1 var spawnedUnits = -1
lateinit var position: Vector2 var destroyed = false // destroyed encampments haunt the vicinity for 15 turns preventing new spawns
@Transient @Transient
lateinit var gameInfo: GameInfo lateinit var gameInfo: GameInfo
fun clone(): Encampment { fun clone(): Encampment {
val toReturn = Encampment() val toReturn = Encampment(position)
toReturn.position = position
toReturn.countdown = countdown toReturn.countdown = countdown
toReturn.spawnedUnits = spawnedUnits toReturn.spawnedUnits = spawnedUnits
toReturn.destroyed = destroyed
return toReturn return toReturn
} }
fun update() { fun update() {
if (countdown > 0) // Not yet if (countdown > 0) // Not yet
countdown-- 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 // Successful
spawnedUnits++ spawnedUnits++
resetCountdown() resetCountdown()
@ -187,7 +194,15 @@ class Encampment {
} }
fun wasAttacked() { 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. */ /** 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() val barbarianCiv = gameInfo.getBarbarianCivilization()
barbarianCiv.tech.techsResearched = allResearchedTechs.toHashSet() barbarianCiv.tech.techsResearched = allResearchedTechs.toHashSet()
val unitList = gameInfo.ruleSet.units.values val unitList = gameInfo.ruleSet.units.values
.filter { it.isMilitary() } .filter { it.isMilitary() &&
.filter { it.isBuildable(barbarianCiv) } it.isBuildable(barbarianCiv) &&
!(it.hasUnique(UniqueType.MustSetUp) || it.hasUnique(UniqueType.CannotAttack)) &&
(if (naval) it.isWaterUnit() else it.isLandUnit()) }
var unit = if (naval) if (unitList.isEmpty()) return null // No naval tech yet? Mad modders?
unitList.filter { it.isWaterUnit() }.randomOrNull()
else
unitList.filter { it.isLandUnit() }.randomOrNull()
if (unit == null) // Didn't find a unit for preferred domain // Civ V weights its list by FAST_ATTACK or ATTACK_SEA AI types, we'll do it a bit differently
unit = unitList.randomOrNull() // Try picking another // 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 */ /** When a barbarian is spawned, seed the counter for next spawn */

View File

@ -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! // 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 // 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()) val tilesToAttackFrom = if (stayOnTile || unit.baseUnit.movesLikeAirUnits())
sequenceOf(Pair(unit.currentTile, unit.currentMovement)) sequenceOf(Pair(unit.currentTile, unit.currentMovement))
else else

View File

@ -36,7 +36,7 @@ object Battle {
* but the hidden tile is actually IMPASSIBLE so you stop halfway! * but the hidden tile is actually IMPASSIBLE so you stop halfway!
*/ */
if (attacker.getTile() != attackableTile.tileToAttackFrom) return 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.action = UnitActionType.SetUp.value
attacker.unit.useMovementPoints(1f) attacker.unit.useMovementPoints(1f)
} }

View File

@ -222,6 +222,7 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget) {
MayEnhanceReligion("May enhance a religion", UniqueTarget.Unit), MayEnhanceReligion("May enhance a religion", UniqueTarget.Unit),
NormalVisionWhenEmbarked("Normal vision when embarked", UniqueTarget.Unit, UniqueTarget.Global), NormalVisionWhenEmbarked("Normal vision when embarked", UniqueTarget.Unit, UniqueTarget.Global),
CannotAttack("Cannot attack", UniqueTarget.Unit), 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 <for [Embarked] units>"), DeprecationLevel.ERROR) @Deprecated("As of 3.16.11 - removed as of 3.17.11", ReplaceWith("[+1] Movement <for [Embarked] units>"), DeprecationLevel.ERROR)
EmbarkedUnitMovement1("Increases embarked movement +1", UniqueTarget.Global), EmbarkedUnitMovement1("Increases embarked movement +1", UniqueTarget.Global),

View File

@ -659,7 +659,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
// //
unique.placeholderText == "May Paradrop up to [] tiles from inside friendly territory" // Paradrop - 25% bonus unique.placeholderText == "May Paradrop up to [] tiles from inside friendly territory" // Paradrop - 25% bonus
-> power += power / 4 -> 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 -> power -= power / 5
unique.placeholderText == "[] additional attacks per turn" // Extra attacks - 20% bonus per extra attack unique.placeholderText == "[] additional attacks per turn" // Extra attacks - 20% bonus per extra attack
-> power += (power * unique.params[0].toInt()) / 5 -> power += (power * unique.params[0].toInt()) / 5

View File

@ -230,7 +230,7 @@ object UnitActions {
} }
private fun addSetupAction(unit: MapUnit, actionList: ArrayList<UnitAction>) { private fun addSetupAction(unit: MapUnit, actionList: ArrayList<UnitAction>) {
if (!unit.hasUnique("Must set up to ranged attack") || unit.isEmbarked()) return if (!unit.hasUnique(UniqueType.MustSetUp) || unit.isEmbarked()) return
val isSetUp = unit.isSetUpForSiege() val isSetUp = unit.isSetUpForSiege()
actionList += UnitAction(UnitActionType.SetUp, actionList += UnitAction(UnitActionType.SetUp,
isCurrentAction = isSetUp, isCurrentAction = isSetUp,