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:
SimonCeder 2021-10-03 10:45:02 +02:00 committed by GitHub
parent 344c96319b
commit 9016385f30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 330 additions and 96 deletions

View File

@ -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,

View File

@ -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",

View File

@ -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 =

View 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
}
}

View File

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

View File

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

View File

@ -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 {

View File

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

View File

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

View File

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

View File

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

View File

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