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.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<Vector2>()
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 */

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!
// 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

View File

@ -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)
}

View File

@ -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 <for [Embarked] units>"), DeprecationLevel.ERROR)
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
-> 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

View File

@ -230,7 +230,7 @@ object UnitActions {
}
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()
actionList += UnitAction(UnitActionType.SetUp,
isCurrentAction = isSetUp,