mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-27 13:55:54 -04:00
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:
parent
87d24e89c4
commit
d8bb60f06c
@ -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 */
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user