mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-26 13:27:22 -04:00
Barbarian spawning and camp placements (#5354)
* barbarian camp placement, spawn countdowns * separate file * raging barbarians * faster spawn when attacked * Barbarian AI * works on old saves * template.properties * fix percent * no improvements unique * fix test fail * reviews * reviews pt 2
This commit is contained in:
parent
344c96319b
commit
9016385f30
@ -11,6 +11,7 @@
|
||||
"policyCostModifier": 0.5,
|
||||
"unhappinessModifier": 0.4,
|
||||
"barbarianBonus": 0.75,
|
||||
"barbarianSpawnDelay": 8,
|
||||
"playerBonusStartingUnits": [], // Note that the units from Eras.json are added to this pool. This should only contain bonus starting units.
|
||||
"aiCityGrowthModifier": 1.6, // that is to say it'll take them 1.6 times as long to grow the city
|
||||
"aiUnitCostModifier": 1.75,
|
||||
@ -39,6 +40,7 @@
|
||||
"policyCostModifier": 0.67,
|
||||
"unhappinessModifier": 0.6,
|
||||
"barbarianBonus": 0.5,
|
||||
"barbarianSpawnDelay": 5,
|
||||
"playerBonusStartingUnits": [],
|
||||
"aiCityGrowthModifier": 1.3,
|
||||
"aiUnitCostModifier": 1.3,
|
||||
@ -67,6 +69,7 @@
|
||||
"policyCostModifier": 0.85,
|
||||
"unhappinessModifier": 0.75,
|
||||
"barbarianBonus": 0.4,
|
||||
"barbarianSpawnDelay": 3,
|
||||
"playerBonusStartingUnits": [],
|
||||
"aiCityGrowthModifier": 1.1,
|
||||
"aiUnitCostModifier": 1.1,
|
||||
@ -95,6 +98,7 @@
|
||||
"policyCostModifier": 1,
|
||||
"unhappinessModifier": 1,
|
||||
"barbarianBonus": 0.33,
|
||||
"barbarianSpawnDelay": 0,
|
||||
"playerBonusStartingUnits": [],
|
||||
"aiCityGrowthModifier": 1,
|
||||
"aiUnitCostModifier": 1,
|
||||
@ -123,6 +127,7 @@
|
||||
"policyCostModifier": 1,
|
||||
"unhappinessModifier": 1,
|
||||
"barbarianBonus": 0.25,
|
||||
"barbarianSpawnDelay": 0,
|
||||
"playerBonusStartingUnits": [],
|
||||
"aiCityGrowthModifier": 0.9,
|
||||
"aiUnitCostModifier": 0.85,
|
||||
@ -151,6 +156,7 @@
|
||||
"policyCostModifier": 1,
|
||||
"unhappinessModifier": 1,
|
||||
"barbarianBonus": 0.2,
|
||||
"barbarianSpawnDelay": 0,
|
||||
"playerBonusStartingUnits": [],
|
||||
"aiCityGrowthModifier": 0.85,
|
||||
"aiUnitCostModifier": 0.8,
|
||||
@ -179,6 +185,7 @@
|
||||
"policyCostModifier": 1,
|
||||
"unhappinessModifier": 1,
|
||||
"barbarianBonus": 0.1,
|
||||
"barbarianSpawnDelay": 0,
|
||||
"playerBonusStartingUnits": [],
|
||||
"aiCityGrowthModifier": 0.75,
|
||||
"aiUnitCostModifier": 0.65,
|
||||
@ -207,6 +214,7 @@
|
||||
"policyCostModifier": 1,
|
||||
"unhappinessModifier": 1,
|
||||
"barbarianBonus": 0,
|
||||
"barbarianSpawnDelay": 0,
|
||||
"playerBonusStartingUnits": [],
|
||||
"aiCityGrowthModifier": 0.6,
|
||||
"aiUnitCostModifier": 0.5,
|
||||
|
@ -157,7 +157,7 @@
|
||||
"gold": -3,
|
||||
"movementCost": 2,
|
||||
"unbuildable": true,
|
||||
"defenceBonus": -0.15
|
||||
"defenceBonus": -0.15,
|
||||
},
|
||||
{
|
||||
"name": "Oasis",
|
||||
@ -168,7 +168,7 @@
|
||||
"unbuildable": true,
|
||||
"defenceBonus": -0.1,
|
||||
"occursOn": ["Desert"],
|
||||
"uniques": ["Fresh water", "Rare feature"]
|
||||
"uniques": ["Fresh water", "Rare feature", "Only [All Road] improvements may be built on this tile"]
|
||||
},
|
||||
{
|
||||
"name": "Flood plains",
|
||||
|
@ -308,6 +308,7 @@ Archipelago =
|
||||
Number of City-States =
|
||||
One City Challenge =
|
||||
No Barbarians =
|
||||
Raging Barbarians =
|
||||
No Ancient Ruins =
|
||||
No Natural Wonders =
|
||||
Victory Conditions =
|
||||
@ -1037,6 +1038,7 @@ Building cost modifier =
|
||||
Policy cost modifier =
|
||||
Unhappiness modifier =
|
||||
Bonus vs. Barbarians =
|
||||
Barbarian spawning delay =
|
||||
Bonus starting units =
|
||||
|
||||
AI settings =
|
||||
|
271
core/src/com/unciv/logic/BarbarianManager.kt
Normal file
271
core/src/com/unciv/logic/BarbarianManager.kt
Normal file
@ -0,0 +1,271 @@
|
||||
package com.unciv.logic
|
||||
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.unciv.Constants
|
||||
import com.unciv.logic.civilization.NotificationIcon
|
||||
import com.unciv.logic.map.TileInfo
|
||||
import com.unciv.logic.map.TileMap
|
||||
import com.unciv.models.metadata.GameSpeed
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
import kotlin.system.measureNanoTime
|
||||
|
||||
class BarbarianManager {
|
||||
val camps = HashMap<Vector2, Encampment>()
|
||||
|
||||
@Transient
|
||||
lateinit var gameInfo: GameInfo
|
||||
|
||||
@Transient
|
||||
lateinit var tileMap: TileMap
|
||||
|
||||
fun clone(): BarbarianManager {
|
||||
val toReturn = BarbarianManager()
|
||||
for (camp in camps.values.map { it.clone() })
|
||||
toReturn.camps[camp.position] = camp
|
||||
return toReturn
|
||||
}
|
||||
|
||||
fun setTransients(gameInfo: GameInfo) {
|
||||
this.gameInfo = gameInfo
|
||||
this.tileMap = gameInfo.tileMap
|
||||
|
||||
// Add any preexisting camps as Encampment objects
|
||||
for (tile in tileMap.values) {
|
||||
if (tile.improvement == Constants.barbarianEncampment
|
||||
&& camps[tile.position] == null) {
|
||||
val newCamp = Encampment()
|
||||
newCamp.position = tile.position
|
||||
camps[newCamp.position] = newCamp
|
||||
}
|
||||
}
|
||||
|
||||
for (camp in camps.values)
|
||||
camp.gameInfo = gameInfo
|
||||
}
|
||||
|
||||
fun updateEncampments() {
|
||||
// Check if camps were destroyed
|
||||
for (position in camps.keys.toList()) {
|
||||
if (tileMap[position].improvement != Constants.barbarianEncampment) {
|
||||
camps.remove(position)
|
||||
}
|
||||
}
|
||||
|
||||
// Possibly place a new encampment
|
||||
placeBarbarianEncampment()
|
||||
|
||||
// Update all existing camps
|
||||
for (camp in camps.values) {
|
||||
camp.update()
|
||||
}
|
||||
}
|
||||
|
||||
/** Called when an encampment was attacked, will speed up time to next spawn */
|
||||
fun campAttacked(position: Vector2) {
|
||||
camps[position]?.wasAttacked()
|
||||
}
|
||||
|
||||
fun placeBarbarianEncampment() {
|
||||
// Before we do the expensive stuff, do a roll to see if we will place a camp at all
|
||||
if (gameInfo.turns > 1 && Random().nextBoolean())
|
||||
return
|
||||
|
||||
// Barbarians will only spawn in places that no one can see
|
||||
val allViewableTiles = gameInfo.civilizations.asSequence().filterNot { it.isBarbarian() || it.isSpectator() }
|
||||
.flatMap { it.viewableTiles }.toHashSet()
|
||||
val fogTiles = tileMap.values.filter { it.isLand && it !in allViewableTiles }
|
||||
|
||||
val fogTilesPerCamp = (tileMap.values.size.toFloat().pow(0.4f)).toInt() // Approximately
|
||||
|
||||
// Check if we have more room
|
||||
var campsToAdd = (fogTiles.size / fogTilesPerCamp) - camps.size
|
||||
|
||||
// First turn of the game add 1/3 of all possible camps
|
||||
if (gameInfo.turns == 1) {
|
||||
campsToAdd /= 3
|
||||
campsToAdd = max(campsToAdd, 1) // At least 1 on first turn
|
||||
} else if (campsToAdd > 0)
|
||||
campsToAdd = 1
|
||||
|
||||
if (campsToAdd <= 0) return
|
||||
|
||||
// Camps can't spawn within 7 tiles of each other or within 4 tiles of major civ capitals
|
||||
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()
|
||||
|
||||
val viableTiles = fogTiles.filter {
|
||||
!it.isImpassible()
|
||||
&& it.resource == null
|
||||
&& it.terrainFeatures.none { feature -> gameInfo.ruleSet.terrains[feature]!!.hasUnique("Only [] improvements may be built on this tile") }
|
||||
&& it.neighbors.any { neighbor -> neighbor.isLand }
|
||||
&& it !in tooCloseToCapitals
|
||||
&& it !in tooCloseToCamps
|
||||
}.toMutableList()
|
||||
|
||||
var tile: TileInfo?
|
||||
var addedCamps = 0
|
||||
var biasCoast = Random().nextInt(6) == 0
|
||||
|
||||
// Add the camps
|
||||
while (addedCamps < campsToAdd) {
|
||||
if (viableTiles.isEmpty())
|
||||
break
|
||||
|
||||
// If we're biasing for coast, get a coast tile if possible
|
||||
if (biasCoast) {
|
||||
tile = viableTiles.filter { it.isCoastalTile() }.randomOrNull()
|
||||
if (tile == null)
|
||||
tile = viableTiles.random()
|
||||
} else
|
||||
tile = viableTiles.random()
|
||||
|
||||
tile.improvement = Constants.barbarianEncampment
|
||||
val newCamp = Encampment()
|
||||
newCamp.position = tile.position
|
||||
newCamp.gameInfo = gameInfo
|
||||
camps[newCamp.position] = newCamp
|
||||
notifyCivsOfBarbarianEncampment(tile)
|
||||
addedCamps++
|
||||
|
||||
// Still more camps to add?
|
||||
if (addedCamps < campsToAdd) {
|
||||
// Remove some newly non-viable tiles
|
||||
viableTiles.removeAll( tile.getTilesInDistance(7) )
|
||||
// Reroll bias
|
||||
biasCoast = Random().nextInt(6) == 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [CivilizationInfo.addNotification][Add a notification] to every civilization that have
|
||||
* adopted Honor policy and have explored the [tile] where the Barbarian Encampment has spawned.
|
||||
*/
|
||||
private fun notifyCivsOfBarbarianEncampment(tile: TileInfo) {
|
||||
gameInfo.civilizations.filter {
|
||||
it.hasUnique("Notified of new Barbarian encampments")
|
||||
&& it.exploredTiles.contains(tile.position)
|
||||
}
|
||||
.forEach { it.addNotification("A new barbarian encampment has spawned!", tile.position, NotificationIcon.War) }
|
||||
}
|
||||
}
|
||||
|
||||
class Encampment {
|
||||
var countdown = 0
|
||||
var spawnedUnits = -1
|
||||
lateinit var position: Vector2
|
||||
|
||||
@Transient
|
||||
lateinit var gameInfo: GameInfo
|
||||
|
||||
fun clone(): Encampment {
|
||||
val toReturn = Encampment()
|
||||
toReturn.position = position
|
||||
toReturn.countdown = countdown
|
||||
toReturn.spawnedUnits = spawnedUnits
|
||||
return toReturn
|
||||
}
|
||||
|
||||
fun update() {
|
||||
if (countdown > 0) // Not yet
|
||||
countdown--
|
||||
else if (spawnBarbarian()) { // Countdown at 0, try to spawn a barbarian
|
||||
// Successful
|
||||
spawnedUnits++
|
||||
resetCountdown()
|
||||
}
|
||||
}
|
||||
|
||||
fun wasAttacked() {
|
||||
countdown /= 2
|
||||
}
|
||||
|
||||
/** Attempts to spawn a Barbarian from this encampment. Returns true if a unit was spawned. */
|
||||
private fun spawnBarbarian(): Boolean {
|
||||
val tile = gameInfo.tileMap[position]
|
||||
|
||||
// Empty camp - spawn a defender
|
||||
if (tile.militaryUnit == null) {
|
||||
return spawnOnTile(tile) // Try spawning a unit on this tile, return false if unsuccessful
|
||||
}
|
||||
|
||||
// Don't spawn wandering barbs too early
|
||||
if (gameInfo.turns < 10)
|
||||
return false
|
||||
|
||||
// Too many barbarians around already?
|
||||
val barbarianCiv = gameInfo.getBarbarianCivilization()
|
||||
if (tile.getTilesInDistance(4).count { it.militaryUnit?.civInfo == barbarianCiv } > 2)
|
||||
return false
|
||||
|
||||
val canSpawnBoats = gameInfo.turns > 30
|
||||
val validTiles = tile.neighbors.toList().filterNot {
|
||||
it.isImpassible()
|
||||
|| it.isCityCenter()
|
||||
|| it.getFirstUnit() != null
|
||||
|| (it.isWater && !canSpawnBoats)
|
||||
|| (it.hasUnique("Fresh water") && it.isWater) // No Lakes
|
||||
}
|
||||
if (validTiles.isEmpty()) return false
|
||||
|
||||
return spawnOnTile(validTiles.random()) // Attempt to spawn a barbarian on a valid tile
|
||||
}
|
||||
|
||||
/** Attempts to spawn a barbarian on [tile], returns true if successful and false if unsuccessful. */
|
||||
private fun spawnOnTile(tile: TileInfo): Boolean {
|
||||
val unitToSpawn = chooseBarbarianUnit(tile.isWater) ?: return false // return false if we didn't find a unit
|
||||
val spawnedUnit = gameInfo.tileMap.placeUnitNearTile(tile.position, unitToSpawn, gameInfo.getBarbarianCivilization())
|
||||
return (spawnedUnit != null)
|
||||
}
|
||||
|
||||
private fun chooseBarbarianUnit(naval: Boolean): String? {
|
||||
// if we don't make this into a separate list then the retain() will happen on the Tech keys,
|
||||
// which effectively removes those techs from the game and causes all sorts of problems
|
||||
val allResearchedTechs = gameInfo.ruleSet.technologies.keys.toMutableList()
|
||||
for (civ in gameInfo.civilizations.filter { !it.isBarbarian() && !it.isDefeated() }) {
|
||||
allResearchedTechs.retainAll(civ.tech.techsResearched)
|
||||
}
|
||||
val barbarianCiv = gameInfo.getBarbarianCivilization()
|
||||
barbarianCiv.tech.techsResearched = allResearchedTechs.toHashSet()
|
||||
val unitList = gameInfo.ruleSet.units.values
|
||||
.filter { it.isMilitary() }
|
||||
.filter { it.isBuildable(barbarianCiv) }
|
||||
|
||||
var unit = if (naval)
|
||||
unitList.filter { it.isWaterUnit() }.randomOrNull()
|
||||
else
|
||||
unitList.filter { it.isLandUnit() }.randomOrNull()
|
||||
|
||||
if (unit == null) // Didn't find a unit for preferred domain
|
||||
unit = unitList.randomOrNull() // Try picking another
|
||||
|
||||
return unit?.name // Could still be null in case of mad modders
|
||||
}
|
||||
|
||||
/** When a barbarian is spawned, seed the counter for next spawn */
|
||||
private fun resetCountdown() {
|
||||
// Base 8-12 turns
|
||||
countdown = 8 + Random().nextInt(5)
|
||||
// Quicker on Raging Barbarians
|
||||
if (gameInfo.gameParameters.ragingBarbarians)
|
||||
countdown /= 2
|
||||
// Higher on low difficulties
|
||||
countdown += gameInfo.ruleSet.difficulties[gameInfo.gameParameters.difficulty]!!.barbarianSpawnDelay
|
||||
// Quicker if this camp has already spawned units
|
||||
countdown -= min(3, spawnedUnits)
|
||||
|
||||
countdown *= when (gameInfo.gameParameters.gameSpeed) {
|
||||
GameSpeed.Quick -> 67
|
||||
GameSpeed.Standard -> 100
|
||||
GameSpeed.Epic -> 150
|
||||
GameSpeed.Marathon -> 400 // sic!
|
||||
}
|
||||
countdown /= 100
|
||||
}
|
||||
}
|
@ -11,17 +11,22 @@ import com.unciv.logic.map.TileInfo
|
||||
import com.unciv.logic.map.TileMap
|
||||
import com.unciv.models.Religion
|
||||
import com.unciv.models.metadata.GameParameters
|
||||
import com.unciv.models.metadata.GameSpeed
|
||||
import com.unciv.models.ruleset.Difficulty
|
||||
import com.unciv.models.ruleset.ModOptionsConstants
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
import com.unciv.models.ruleset.unit.BaseUnit
|
||||
import java.util.*
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
|
||||
class UncivShowableException(missingMods: String) : Exception(missingMods)
|
||||
|
||||
class GameInfo {
|
||||
//region Fields - Serialized
|
||||
var civilizations = mutableListOf<CivilizationInfo>()
|
||||
var barbarians = BarbarianManager()
|
||||
var religions: HashMap<String, Religion> = hashMapOf()
|
||||
var difficulty = "Chieftain" // difficulty is game-wide, think what would happen if 2 human players could play on different difficulties?
|
||||
var tileMap: TileMap = TileMap()
|
||||
@ -77,6 +82,7 @@ class GameInfo {
|
||||
val toReturn = GameInfo()
|
||||
toReturn.tileMap = tileMap.clone()
|
||||
toReturn.civilizations.addAll(civilizations.map { it.clone() })
|
||||
toReturn.barbarians = barbarians.clone()
|
||||
toReturn.religions.putAll(religions.map { Pair(it.key, it.value.clone()) })
|
||||
toReturn.currentPlayer = currentPlayer
|
||||
toReturn.turns = turns
|
||||
@ -112,7 +118,7 @@ class GameInfo {
|
||||
fun getCurrentPlayerCivilization() = currentPlayerCiv
|
||||
/** Get barbarian civ
|
||||
* @throws NoSuchElementException in no-barbarians games! */
|
||||
private fun getBarbarianCivilization() = getCivilization(Constants.barbarians)
|
||||
fun getBarbarianCivilization() = getCivilization(Constants.barbarians)
|
||||
fun getDifficulty() = difficultyObject
|
||||
fun getCities() = civilizations.asSequence().flatMap { it.cities }
|
||||
fun getAliveCityStates() = civilizations.filter { it.isAlive() && it.isCityState() }
|
||||
@ -176,9 +182,8 @@ class GameInfo {
|
||||
NextTurnAutomation.automateCivMoves(thisPlayer)
|
||||
|
||||
// Placing barbarians after their turn
|
||||
if (thisPlayer.isBarbarian()
|
||||
&& !gameParameters.noBarbarians
|
||||
&& turns % 10 == 0) placeBarbarians()
|
||||
if (thisPlayer.isBarbarian() && !gameParameters.noBarbarians)
|
||||
barbarians.updateEncampments()
|
||||
|
||||
// exit simulation mode when player wins
|
||||
if (thisPlayer.victoryManager.hasWon() && simulateUntilWin) {
|
||||
@ -234,80 +239,6 @@ class GameInfo {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun placeBarbarians() {
|
||||
val encampments = tileMap.values.filter { it.improvement == Constants.barbarianEncampment }
|
||||
|
||||
if (encampments.size < civilizations.filter { it.isMajorCiv() }.size) {
|
||||
val newEncampmentTile = placeBarbarianEncampment(encampments)
|
||||
if (newEncampmentTile != null)
|
||||
placeBarbarianUnit(newEncampmentTile)
|
||||
}
|
||||
|
||||
val totalBarbariansAllowedOnMap = encampments.size * 3
|
||||
var extraBarbarians = totalBarbariansAllowedOnMap - getBarbarianCivilization().getCivUnits().count()
|
||||
|
||||
for (tile in tileMap.values.filter { it.improvement == Constants.barbarianEncampment }) {
|
||||
if (extraBarbarians <= 0) break
|
||||
extraBarbarians--
|
||||
placeBarbarianUnit(tile)
|
||||
}
|
||||
}
|
||||
|
||||
fun placeBarbarianEncampment(existingEncampments: List<TileInfo>): TileInfo? {
|
||||
// Barbarians will only spawn in places that no one can see
|
||||
val allViewableTiles = civilizations.filterNot { it.isBarbarian() || it.isSpectator() }
|
||||
.flatMap { it.viewableTiles }.toHashSet()
|
||||
val tilesWithin3ofExistingEncampment = existingEncampments.asSequence()
|
||||
.flatMap { it.getTilesInDistance(3) }.toSet()
|
||||
val viableTiles = tileMap.values.filter {
|
||||
it.isLand && it.terrainFeatures.isEmpty()
|
||||
&& !it.isImpassible()
|
||||
&& it !in tilesWithin3ofExistingEncampment
|
||||
&& it !in allViewableTiles
|
||||
}
|
||||
if (viableTiles.isEmpty()) return null // no place for more barbs =(
|
||||
val tile = viableTiles.random()
|
||||
tile.improvement = Constants.barbarianEncampment
|
||||
notifyCivsOfBarbarianEncampment(tile)
|
||||
return tile
|
||||
}
|
||||
|
||||
private fun placeBarbarianUnit(tileToPlace: TileInfo) {
|
||||
// if we don't make this into a separate list then the retain() will happen on the Tech keys,
|
||||
// which effectively removes those techs from the game and causes all sorts of problems
|
||||
val allResearchedTechs = ruleSet.technologies.keys.toMutableList()
|
||||
for (civ in civilizations.filter { !it.isBarbarian() && !it.isDefeated() }) {
|
||||
allResearchedTechs.retainAll(civ.tech.techsResearched)
|
||||
}
|
||||
val barbarianCiv = getBarbarianCivilization()
|
||||
barbarianCiv.tech.techsResearched = allResearchedTechs.toHashSet()
|
||||
val unitList = ruleSet.units.values
|
||||
.filter { it.isMilitary() }
|
||||
.filter { it.isBuildable(barbarianCiv) }
|
||||
|
||||
val landUnits = unitList.filter { it.isLandUnit() }
|
||||
val waterUnits = unitList.filter { it.isWaterUnit() }
|
||||
|
||||
val unit: String = if (waterUnits.isNotEmpty() && tileToPlace.isCoastalTile() && Random().nextBoolean())
|
||||
waterUnits.random().name
|
||||
else landUnits.random().name
|
||||
|
||||
tileMap.placeUnitNearTile(tileToPlace.position, unit, getBarbarianCivilization())
|
||||
}
|
||||
|
||||
/**
|
||||
* [CivilizationInfo.addNotification][Add a notification] to every civilization that have
|
||||
* adopted Honor policy and have explored the [tile] where the Barbarian Encampment has spawned.
|
||||
*/
|
||||
private fun notifyCivsOfBarbarianEncampment(tile: TileInfo) {
|
||||
civilizations.filter {
|
||||
it.hasUnique("Notified of new Barbarian encampments")
|
||||
&& it.exploredTiles.contains(tile.position)
|
||||
}
|
||||
.forEach { it.addNotification("A new barbarian encampment has spawned!", tile.position, NotificationIcon.War) }
|
||||
}
|
||||
|
||||
// All cross-game data which needs to be altered (e.g. when removing or changing a name of a building/tech)
|
||||
// will be done here, and not in CivInfo.setTransients or CityInfo
|
||||
fun setTransients() {
|
||||
@ -374,6 +305,8 @@ class GameInfo {
|
||||
civInfo.hasEverOwnedOriginalCapital = civInfo.cities.any { it.isOriginalCapital }
|
||||
}
|
||||
}
|
||||
|
||||
barbarians.setTransients(this)
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
@ -22,8 +22,8 @@ class BarbarianAutomation(val civInfo: CivilizationInfo) {
|
||||
// 1 - trying to upgrade
|
||||
if (UnitAutomation.tryUpgradeUnit(unit)) return
|
||||
|
||||
// 2 - trying to attack somebody
|
||||
if (BattleHelper.tryAttackNearbyEnemy(unit)) return
|
||||
// 2 - trying to attack somebody - but don't leave the encampment
|
||||
if (BattleHelper.tryAttackNearbyEnemy(unit, stayOnTile = true)) return
|
||||
|
||||
// 3 - at least fortifying
|
||||
unit.fortifyIfCan()
|
||||
|
@ -11,9 +11,9 @@ import com.unciv.models.AttackableTile
|
||||
|
||||
object BattleHelper {
|
||||
|
||||
fun tryAttackNearbyEnemy(unit: MapUnit): Boolean {
|
||||
fun tryAttackNearbyEnemy(unit: MapUnit, stayOnTile: Boolean = false): Boolean {
|
||||
if (unit.hasUnique("Cannot attack")) return false
|
||||
val attackableEnemies = getAttackableEnemies(unit, unit.movement.getDistanceToTiles())
|
||||
val attackableEnemies = getAttackableEnemies(unit, unit.movement.getDistanceToTiles(), stayOnTile=stayOnTile)
|
||||
// Only take enemies we can fight without dying
|
||||
.filter {
|
||||
BattleDamage.calculateDamageToAttacker(MapUnitCombatant(unit),
|
||||
@ -33,7 +33,8 @@ object BattleHelper {
|
||||
fun getAttackableEnemies(
|
||||
unit: MapUnit,
|
||||
unitDistanceToTiles: PathsToTilesWithinTurn,
|
||||
tilesToCheck: List<TileInfo>? = null
|
||||
tilesToCheck: List<TileInfo>? = null,
|
||||
stayOnTile: Boolean = false
|
||||
): ArrayList<AttackableTile> {
|
||||
val tilesWithEnemies = (tilesToCheck ?: unit.civInfo.viewableTiles)
|
||||
.filter { containsAttackableEnemy(it, MapUnitCombatant(unit)) }
|
||||
@ -48,7 +49,7 @@ object BattleHelper {
|
||||
// Silly floats, basically
|
||||
|
||||
val unitMustBeSetUp = unit.hasUnique("Must set up to ranged attack")
|
||||
val tilesToAttackFrom = if (unit.baseUnit.movesLikeAirUnits()) sequenceOf(unit.currentTile)
|
||||
val tilesToAttackFrom = if (stayOnTile || unit.baseUnit.movesLikeAirUnits()) sequenceOf(unit.currentTile)
|
||||
else
|
||||
unitDistanceToTiles.asSequence()
|
||||
.filter {
|
||||
|
@ -304,14 +304,19 @@ object Battle {
|
||||
}
|
||||
|
||||
private fun postBattleNationUniques(defender: ICombatant, attackedTile: TileInfo, attacker: ICombatant) {
|
||||
// German unique - needs to be checked before we try to move to the enemy tile, since the encampment disappears after we move in
|
||||
if (defender.isDefeated() && defender.getCivInfo().isBarbarian()
|
||||
&& attackedTile.improvement == Constants.barbarianEncampment
|
||||
&& attacker.getCivInfo().hasUnique("67% chance to earn 25 Gold and recruit a Barbarian unit from a conquered encampment")
|
||||
&& Random().nextDouble() < 0.67) {
|
||||
attacker.getCivInfo().placeUnitNearTile(attackedTile.position, defender.getName())
|
||||
attacker.getCivInfo().addGold(25)
|
||||
attacker.getCivInfo().addNotification("A barbarian [${defender.getName()}] has joined us!", attackedTile.position, defender.getName())
|
||||
|
||||
// Barbarians reduce spawn countdown after their camp was attacked "kicking the hornet's nest"
|
||||
if (defender.getCivInfo().isBarbarian() && attackedTile.improvement == Constants.barbarianEncampment) {
|
||||
defender.getCivInfo().gameInfo.barbarians.campAttacked(attackedTile.position)
|
||||
|
||||
// German unique - needs to be checked before we try to move to the enemy tile, since the encampment disappears after we move in
|
||||
if (defender.isDefeated()
|
||||
&& attacker.getCivInfo().hasUnique("67% chance to earn 25 Gold and recruit a Barbarian unit from a conquered encampment")
|
||||
&& Random().nextDouble() < 0.67) {
|
||||
attacker.getCivInfo().placeUnitNearTile(attackedTile.position, defender.getName())
|
||||
attacker.getCivInfo().addGold(25)
|
||||
attacker.getCivInfo().addNotification("A barbarian [${defender.getName()}] has joined us!", attackedTile.position, defender.getName())
|
||||
}
|
||||
}
|
||||
|
||||
// Similarly, Ottoman unique
|
||||
|
@ -419,6 +419,10 @@ open class TileInfo {
|
||||
RoadStatus.values().none { it.name == improvement.name || it.removeAction == improvement.name }
|
||||
&& getTileImprovement().let { it != null && it.hasUnique("Irremovable") } -> false
|
||||
|
||||
// Terrain blocks most improvements
|
||||
getAllTerrains().any { it.getMatchingUniques("Only [] improvements may be built on this tile")
|
||||
.any { unique -> !improvement.matchesFilter(unique.params[0]) } } -> false
|
||||
|
||||
// Decide cancelImprovementOrder earlier, otherwise next check breaks it
|
||||
improvement.name == Constants.cancelImprovementOrder -> (this.improvementInProgress != null)
|
||||
// Tiles with no terrains, and no turns to build, are like great improvements - they're placeable
|
||||
|
@ -18,6 +18,7 @@ class GameParameters { // Default values are the default new game
|
||||
var numberOfCityStates = 6
|
||||
|
||||
var noBarbarians = false
|
||||
var ragingBarbarians = false
|
||||
var oneCityChallenge = false
|
||||
var godMode = false
|
||||
var nuclearWeaponsEnabled = true
|
||||
@ -38,6 +39,7 @@ class GameParameters { // Default values are the default new game
|
||||
parameters.players = ArrayList(players)
|
||||
parameters.numberOfCityStates = numberOfCityStates
|
||||
parameters.noBarbarians = noBarbarians
|
||||
parameters.ragingBarbarians = ragingBarbarians
|
||||
parameters.oneCityChallenge = oneCityChallenge
|
||||
parameters.nuclearWeaponsEnabled = nuclearWeaponsEnabled
|
||||
parameters.religionEnabled = religionEnabled
|
||||
@ -57,6 +59,7 @@ class GameParameters { // Default values are the default new game
|
||||
yield("$numberOfCityStates CS")
|
||||
if (isOnlineMultiplayer) yield("Online Multiplayer")
|
||||
if (noBarbarians) yield("No barbs")
|
||||
if (ragingBarbarians) yield("Raging barbs")
|
||||
if (oneCityChallenge) yield("OCC")
|
||||
if (!nuclearWeaponsEnabled) yield("No nukes")
|
||||
if (religionEnabled) yield("Religion")
|
||||
|
@ -18,6 +18,7 @@ class Difficulty: INamed, ICivilopediaText {
|
||||
var policyCostModifier:Float = 1f
|
||||
var unhappinessModifier:Float = 1f
|
||||
var barbarianBonus:Float = 0f
|
||||
var barbarianSpawnDelay: Int = 0
|
||||
var playerBonusStartingUnits = ArrayList<String>()
|
||||
|
||||
var aiCityGrowthModifier:Float = 1f
|
||||
@ -54,6 +55,7 @@ class Difficulty: INamed, ICivilopediaText {
|
||||
lines += FormattedLine("{Policy cost modifier}: ${policyCostModifier.toPercent()}% ${Fonts.culture}", indent = 1)
|
||||
lines += FormattedLine("{Unhappiness modifier}: ${unhappinessModifier.toPercent()}%", indent = 1)
|
||||
lines += FormattedLine("{Bonus vs. Barbarians}: ${barbarianBonus.toPercent()}% ${Fonts.strength}", indent = 1)
|
||||
lines += FormattedLine("{Barbarian spawning delay}: ${barbarianSpawnDelay}", indent = 1)
|
||||
|
||||
if (playerBonusStartingUnits.isNotEmpty()) {
|
||||
lines += FormattedLine()
|
||||
|
@ -44,7 +44,8 @@ class GameOptionsTable(
|
||||
addVictoryTypeCheckboxes()
|
||||
|
||||
val checkboxTable = Table().apply { defaults().left().pad(2.5f) }
|
||||
checkboxTable.addBarbariansCheckbox()
|
||||
checkboxTable.addNoBarbariansCheckbox()
|
||||
checkboxTable.addRagingBarbariansCheckbox()
|
||||
checkboxTable.addOneCityChallengeCheckbox()
|
||||
checkboxTable.addNuclearWeaponsCheckbox()
|
||||
checkboxTable.addIsOnlineMultiplayerCheckbox()
|
||||
@ -63,10 +64,14 @@ class GameOptionsTable(
|
||||
add(checkbox).colspan(2).row()
|
||||
}
|
||||
|
||||
private fun Table.addBarbariansCheckbox() =
|
||||
private fun Table.addNoBarbariansCheckbox() =
|
||||
addCheckbox("No Barbarians", gameParameters.noBarbarians)
|
||||
{ gameParameters.noBarbarians = it }
|
||||
|
||||
private fun Table.addRagingBarbariansCheckbox() =
|
||||
addCheckbox("Raging Barbarians", gameParameters.ragingBarbarians)
|
||||
{ gameParameters.ragingBarbarians = it }
|
||||
|
||||
private fun Table.addOneCityChallengeCheckbox() =
|
||||
addCheckbox("One City Challenge", gameParameters.oneCityChallenge)
|
||||
{ gameParameters.oneCityChallenge = it }
|
||||
|
Loading…
x
Reference in New Issue
Block a user