Ruins now have their own file (#4771)

* Ruins now have their own file

* Added religious rewards

* Added an option for only enabling rewards after a certain amount of turns

* You can now weigh rewards making some more likely than others

* Cleaned up some code

* Make new changes compatible with old mods

* Implemented proposed changes

* Implemented requested changes

* Implemented requested changes
This commit is contained in:
Xander Lenstra 2021-08-08 16:31:08 +02:00 committed by GitHub
parent 4d0b2405e1
commit 92d3fa65e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 754 additions and 222 deletions

View File

@ -0,0 +1,64 @@
[
{
"name": "freeCulture",
"notification": "We have discovered cultural artifacts in the ruins! (+20 culture)",
"uniques": ["Gain [20] [Culture]"]
},
{
"name": "joinWorker",
"notification": "A [Worker] has joined us!",
"uniques": ["Free [Worker] found in the ruins"],
"excludedDifficulties": ["Prince", "King", "Emperor", "Immortal", "Deity"]
},
{
"name": "joinSettler",
"notification": "A [Settler] has joined us!",
"uniques": ["Free [Settler] found in the ruins"],
"excludedDifficulties": ["Warlord","Prince","King","Emperor","Immortal","Deity"]
},
{
"name": "freeXP",
"notification": "An ancient tribe trained us in their ways of combat!",
"uniques": ["This Unit gains [10] XP"]
},
{
"name": "freePop",
"notification": "We have found survivors in the ruins! Population added to [cityName].",
"uniques": ["[+1] population in a random city"] // This can't be easily added to cityFilter, as it is non-deterministic
},
{
"name": "freeGold",
"notification": "We have found a stash of [goldAmount] Gold in the ruins!",
"uniques": ["Gain [50]-[100] [Gold]"]
},
{
"name": "freeAncientTech",
"notification": "We have discovered the lost technology of [techName] in the ruins!",
"uniques": ["[1] free random researchable Tech(s) from the [Ancient era]"]
},
{
"name": "unitUpgrade",
"notification": "Our unit finds advanced weaponry hidden in the ruins!",
"uniques": ["This Unit upgrades for free including special upgrades"]
},
{
"name": "barbCampsRevealed",
"notification": "You find evidence of Barbarian activity. Nearby Barbarian camps are revealed!",
"uniques": ["Reveal up to [All] [Barbarian encampment] within a [10] tile radius"]
},
{
"name": "crudelyDrawnMap",
"notification": "We have found a crudely-drawn map in the ruins!",
"uniques": ["From a randomly chosen tile [4] tiles away, reveal tiles up to [4] tiles away with [80]% chance"]
},
{
"name": "holySymbols",
"notification": "We have found holy symbols in the ruins, giving us a deeper understanding of religion! (+[faithAmount] Faith)",
"uniques": ["Hidden when religion is disabled", "Gain enough Faith for a Pantheon"]
},
{
"name": "prophecy",
"notification": "We have found an ancient prophecy in the ruins, greatly increasing our spiritual connection! (+[faithAmount] Faith)",
"uniques": ["Hidden when religion is disabled", "Gain enough Faith for [33]% of a Great Prophet", "Hidden after generating a Great Prophet"]
}
]

View File

@ -234,7 +234,9 @@
"shortcutKey": "F"
},
{ "name": "Ancient ruins", "uniques": ["Unpillagable"],
{
"name": "Ancient ruins",
"uniques": ["Unpillagable", "Provides a random bonus when entered"],
"civilopediaText": [
{"text":"Ancient ruins provide a one-time random bonus when explored"},
{},
@ -248,14 +250,22 @@
{"text":"find a crudely-drawn map", "indent":1, "starred":true}
]
},
{ "name": "City ruins", "uniques": ["Unpillagable"],
"civilopediaText": [{"text":"A bleak reminder of the destruction wreaked by War"}] },
{ "name": "City center", "uniques": ["Unpillagable", "Indestructible"],
{
"name": "City ruins",
"uniques": ["Unpillagable"],
"civilopediaText": [{"text":"A bleak reminder of the destruction wreaked by War"}]
},
{
"name": "City center",
"uniques": ["Unpillagable", "Indestructible"],
"civilopediaText": [
{"text":"Marks the center of a city"},
{"text":"Appearance changes with the technological era of the owning civilization"}
]
},
{ "name": "Barbarian encampment", "uniques": ["Unpillagable"],
"civilopediaText": [{"text":"Home to uncivilized barbarians, will spawn a hostile unit from time to time"}] }
{
"name": "Barbarian encampment",
"uniques": ["Unpillagable"],
"civilopediaText": [{"text":"Home to uncivilized barbarians, will spawn a hostile unit from time to time"}]
}
]

View File

@ -26,7 +26,7 @@
"strength": 5,
"cost": 25,
"obsoleteTech": "Scientific Theory",
"uniques": ["Ignores terrain cost"],
"uniques": ["Ignores terrain cost", "May upgrade to [Archer] through ruins-like effects"],
"attackSound": "nonmetalhit"
},
{
@ -37,6 +37,7 @@
"cost": 40,
"obsoleteTech": "Metal Casting",
"upgradesTo": "Swordsman",
"uniques" : ["May upgrade to [Spearman] through ruins-like effects"],
"attackSound": "nonmetalhit",
"civilopediaText": [
{"text": "This is your basic, club-swinging fighter."}

View File

@ -1141,4 +1141,8 @@ in all cities with a world wonder =
in all cities connected to capital =
in all cities with a garrison =
# Uniques not found in JSON files
Only available after [] turns =
This Unit upgrades for free =

View File

@ -45,7 +45,6 @@ object Constants {
const val fountainOfYouth = "Fountain of Youth"
const val barbarianEncampment = "Barbarian encampment"
const val ancientRuins = "Ancient ruins"
const val peaceTreaty = "Peace Treaty"
const val researchAgreement = "Research Agreement"

View File

@ -240,9 +240,14 @@ object GameStarter {
if(civ.isCityState())
addCityStateLuxury(gameInfo, startingLocation)
for (tile in startingLocation.getTilesInDistance(3))
if (tile.improvement == Constants.ancientRuins)
for (tile in startingLocation.getTilesInDistance(3)) {
if (tile.improvement != null
&& !tile.improvement!!.startsWith("StartingLocation")
&& tile.getTileImprovement()!!.isAncientRuinsEquivalent()
) {
tile.improvement = null // Remove ancient ruins in immediate vicinity
}
}
fun placeNearStartingPosition(unitName: String) {
civ.placeUnitNearTile(startingLocation.position, unitName)

View File

@ -48,8 +48,11 @@ object UnitAutomation {
val unitDistanceToTiles = unit.movement.getDistanceToTiles()
val tileWithRuinOrEncampment = unitDistanceToTiles.keys
.firstOrNull {
(it.improvement == Constants.ancientRuins || it.improvement == Constants.barbarianEncampment)
&& unit.movement.canMoveTo(it)
(
(it.improvement != null && it.getTileImprovement()!!.isAncientRuinsEquivalent())
|| it.improvement == Constants.barbarianEncampment
)
&& unit.movement.canMoveTo(it)
}
if (tileWithRuinOrEncampment == null)
return false

View File

@ -7,6 +7,7 @@ import com.unciv.logic.GameInfo
import com.unciv.logic.UncivShowableException
import com.unciv.logic.automation.NextTurnAutomation
import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.RuinsManager.RuinsManager
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
import com.unciv.logic.civilization.diplomacy.DiplomacyManager
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
@ -86,6 +87,7 @@ class CivilizationInfo {
var goldenAges = GoldenAgeManager()
var greatPeople = GreatPersonManager()
var victoryManager = VictoryManager()
var ruinsManager = RuinsManager()
var diplomacy = HashMap<String, DiplomacyManager>()
var notifications = ArrayList<Notification>()
val popupAlerts = ArrayList<PopupAlert>()
@ -133,6 +135,7 @@ class CivilizationInfo {
toReturn.questManager = questManager.clone()
toReturn.goldenAges = goldenAges.clone()
toReturn.greatPeople = greatPeople.clone()
toReturn.ruinsManager = ruinsManager.clone()
toReturn.victoryManager = victoryManager.clone()
toReturn.allyCivName = allyCivName
for (diplomacyManager in diplomacy.values.map { it.clone() })
@ -510,6 +513,7 @@ class CivilizationInfo {
tech.civInfo = this
tech.setTransients()
ruinsManager.setTransients(this)
for (diplomacyManager in diplomacy.values) {
diplomacyManager.civInfo = this

View File

@ -1,6 +1,7 @@
package com.unciv.logic.civilization
import com.badlogic.gdx.math.Vector2
import com.unciv.models.stats.Stat
import com.unciv.ui.cityscreen.CityScreen
import com.unciv.ui.pickerscreens.TechPickerScreen
import com.unciv.ui.trade.DiplomacyScreen
@ -18,6 +19,12 @@ object NotificationIcon {
const val Diplomacy = "OtherIcons/Diplomacy"
const val City = "ImprovementIcons/City center"
const val Citadel = "ImprovementIcons/Citadel"
const val Happiness = "StatIcons/Happiness"
const val Population = "StatIcons/Population"
const val CityState = "NationIcons/CityState"
const val Production = "StatIcons/Production"
const val Food = "StatIcons/Food"
const val Faith = "StatIcons/Faith"
}
/**

View File

@ -24,7 +24,8 @@ class ReligionManager {
// But the other one should still be _somewhere_. So our only option is to have the GameInfo
// contain the master list, and the ReligionManagers retrieve it from there every time the game loads.
private var greatProphetsEarned = 0
var greatProphetsEarned = 0
private set
var religionState = ReligionState.None
private set
@ -62,8 +63,9 @@ class ReligionManager {
storedFaith += faithFromNewTurn
}
private fun faithForPantheon() = 10 + civInfo.gameInfo.civilizations.count { it.isMajorCiv() && it.religionManager.religion != null } * 5
fun faithForPantheon(additionalCivs: Int = 0) =
10 + (civInfo.gameInfo.civilizations.count { it.isMajorCiv() && it.religionManager.religion != null } + additionalCivs) * 5
fun canFoundPantheon(): Boolean {
if (!civInfo.gameInfo.hasReligionEnabled()) return false
if (religionState != ReligionState.None) return false
@ -92,7 +94,7 @@ class ReligionManager {
// https://www.reddit.com/r/civ/comments/2m82wu/can_anyone_detail_the_finer_points_of_great/
// Game files (globaldefines.xml)
private fun faithForNextGreatProphet() = (
fun faithForNextGreatProphet() = (
(200 + 100 * greatProphetsEarned * (greatProphetsEarned + 1) / 2) *
civInfo.gameInfo.gameParameters.gameSpeed.modifier
).toInt()

View File

@ -0,0 +1,63 @@
package com.unciv.logic.civilization.RuinsManager
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.MapUnit
import com.unciv.models.ruleset.RuinReward
import com.unciv.models.ruleset.UniqueTriggerActivation
import kotlin.random.Random
class RuinsManager {
var lastChosenRewards: MutableList<String> = mutableListOf("", "")
private fun rememberReward(reward: String) {
lastChosenRewards[0] = lastChosenRewards[1]
lastChosenRewards[1] = reward
}
@Transient
lateinit var civInfo: CivilizationInfo
@Transient
lateinit var validRewards: List<RuinReward>
fun clone(): RuinsManager {
val toReturn = RuinsManager()
toReturn.lastChosenRewards = lastChosenRewards
return toReturn
}
fun setTransients(civInfo: CivilizationInfo) {
this.civInfo = civInfo
validRewards = civInfo.gameInfo.ruleSet.ruinRewards.values.toList()
}
fun selectNextRuinsReward(triggeringUnit: MapUnit) {
val tileBasedRandom = Random(triggeringUnit.getTile().position.toString().hashCode())
val availableRewards = validRewards.filter { it.name !in lastChosenRewards }
// This might be a dirty way to do this, but it works.
// For each possible reward, this creates a list with reward.weight amount of copies of this reward
// These lists are then combined into a single list, and the result is shuffled.
val possibleRewards = availableRewards.flatMap { reward -> List(reward.weight) { reward } }.shuffled(tileBasedRandom)
for (possibleReward in possibleRewards) {
if (civInfo.gameInfo.difficulty in possibleReward.excludedDifficulties) continue
if ("Hidden when religion is disabled" in possibleReward.uniques && !civInfo.gameInfo.hasReligionEnabled()) continue
if ("Hidden after generating a Great Prophet" in possibleReward.uniques && civInfo.religionManager.greatProphetsEarned > 0) continue
if (possibleReward.uniqueObjects.any { unique ->
unique.placeholderText == "Only available after [] turns"
&& unique.params[0].toInt() < civInfo.gameInfo.turns
}) continue
var atLeastOneUniqueHadEffect = false
for (unique in possibleReward.uniqueObjects) {
atLeastOneUniqueHadEffect =
atLeastOneUniqueHadEffect
|| UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo, tile = triggeringUnit.getTile(), notification = possibleReward.notification)
|| UniqueTriggerActivation.triggerUnitwideUnique(unique, triggeringUnit, notification = possibleReward.notification)
}
if (atLeastOneUniqueHadEffect) {
rememberReward(possibleReward.name)
break
}
}
}
}

View File

@ -14,7 +14,6 @@ import com.unciv.models.ruleset.tile.TileImprovement
import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.ruleset.unit.UnitType
import java.text.DecimalFormat
import kotlin.random.Random
/**
* The immutable properties and mutable game state of an individual unit present on the map
@ -114,13 +113,7 @@ class MapUnit {
var abilityUsedCount: HashMap<String, Int> = hashMapOf()
var religion: String? = null
companion object {
private const val ANCIENT_RUIN_MAP_REVEAL_OFFSET = 4
private const val ANCIENT_RUIN_MAP_REVEAL_RANGE = 4
private const val ANCIENT_RUIN_MAP_REVEAL_CHANCE = 0.8f
}
//region pure functions
fun clone(): MapUnit {
val toReturn = MapUnit()
@ -212,6 +205,18 @@ class MapUnit {
return getUniques().any { it.placeholderText == unique }
}
fun copyStatisticsTo(newUnit: MapUnit) {
newUnit.health = health
newUnit.instanceName = instanceName
newUnit.currentMovement = currentMovement
newUnit.attacksThisTurn = attacksThisTurn
newUnit.isTransported = isTransported
newUnit.promotions = promotions.clone()
newUnit.updateUniques()
newUnit.updateVisibleTiles()
}
/**
* Determines this (land or sea) unit's current maximum vision range from unit properties, civ uniques and terrain.
* @return Maximum distance of tiles this unit may possibly see
@ -346,16 +351,20 @@ class MapUnit {
return unit
}
fun canUpgrade(): Boolean {
/** @param ignoreRequired: Ignore possible tech/policy/building requirements.
* Used for upgrading units via ancient ruins.
*/
fun canUpgrade(unitToUpgradeTo: BaseUnit = getUnitToUpgradeTo(), ignoreRequired: Boolean = false): Boolean {
// We need to remove the unit from the civ for this check,
// because if the unit requires, say, horses, and so does its upgrade,
// and the civ currently has 0 horses,
// if we don't remove the unit before the check it's return false!
val unitToUpgradeTo = getUnitToUpgradeTo()
if (name == unitToUpgradeTo.name) return false
civInfo.removeUnit(this)
val canUpgrade = unitToUpgradeTo.isBuildable(civInfo)
val canUpgrade =
if (ignoreRequired) unitToUpgradeTo.isBuildableIgnoringTechs(civInfo)
else unitToUpgradeTo.isBuildable(civInfo)
civInfo.addUnit(this)
return canUpgrade
}
@ -678,7 +687,11 @@ class MapUnit {
// getAncientRuinBonus, if it places a new unit, does too
currentTile = tile
if (tile.improvement == Constants.ancientRuins && civInfo.isMajorCiv())
if (civInfo.isMajorCiv()
&& tile.improvement != null
&& !tile.improvement!!.startsWith("StartingLocation ")
&& tile.getTileImprovement()!!.isAncientRuinsEquivalent()
)
getAncientRuinBonus(tile)
if (tile.improvement == Constants.barbarianEncampment && !civInfo.isBarbarian())
clearEncampment(tile)
@ -754,114 +767,7 @@ class MapUnit {
private fun getAncientRuinBonus(tile: TileInfo) {
tile.improvement = null
val tileBasedRandom = Random(tile.position.toString().hashCode())
val actions: ArrayList<() -> Unit> = ArrayList()
fun goldBonus() {
val amount = listOf(25, 60, 100).random(tileBasedRandom)
civInfo.addGold(amount)
civInfo.addNotification(
"We have found a stash of [$amount] gold in the ruins!",
tile.position,
NotificationIcon.Gold
)
}
if (civInfo.cities.isNotEmpty()) actions.add {
val city = civInfo.cities.random(tileBasedRandom)
city.population.addPopulation(1)
val locations = LocationAction(listOf(tile.position, city.location))
civInfo.addNotification(
"We have found survivors in the ruins - population added to [" + city.name + "]",
locations,
NotificationIcon.Growth
)
}
val researchableFirstEraTechs = tile.tileMap.gameInfo.ruleSet.technologies.values
.filter {
!civInfo.tech.isResearched(it.name)
&& civInfo.tech.canBeResearched(it.name)
&& civInfo.gameInfo.ruleSet.getEraNumber(it.era()) == 1
}
if (researchableFirstEraTechs.isNotEmpty())
actions.add {
val tech = researchableFirstEraTechs.random(tileBasedRandom).name
civInfo.tech.addTechnology(tech)
civInfo.addNotification(
"We have discovered the lost technology of [$tech] in the ruins!",
tile.position,
NotificationIcon.Science,
tech
)
}
val militaryUnit =
if (civInfo.gameInfo.gameParameters.startingEra !in civInfo.gameInfo.ruleSet.eras) "Warrior"
else civInfo.gameInfo.ruleSet.eras[civInfo.gameInfo.gameParameters.startingEra]!!.startingMilitaryUnit
val possibleUnits = (
//City-States and OCC don't get settler from ruins
listOf(Constants.settler).filterNot { civInfo.isCityState() || civInfo.isOneCityChallenger() }
+ listOf(Constants.worker, militaryUnit)
).filter { civInfo.gameInfo.ruleSet.units.containsKey(it) }
if (possibleUnits.isNotEmpty())
actions.add {
val chosenUnit = possibleUnits.random(tileBasedRandom)
// placeUnitNearTile _can_ fail, and since this code can run behind a try with empty
// catch inside nested thread switches - petter play it safe
if (civInfo.placeUnitNearTile(tile.position, chosenUnit) == null) {
goldBonus()
} else {
civInfo.addNotification(
"A [$chosenUnit] has joined us!",
tile.position,
chosenUnit
)
}
}
if (!isCivilian())
actions.add {
promotions.XP += 10
civInfo.addNotification(
"An ancient tribe trains our [$name] in their ways of combat!",
tile.position,
name
)
}
actions.add { goldBonus() }
actions.add {
civInfo.policies.addCulture(20)
civInfo.addNotification(
"We have discovered cultural artifacts in the ruins! (+20 Culture)",
tile.position,
NotificationIcon.Culture
)
}
// Map of the surrounding area
val revealCenter = tile.getTilesAtDistance(ANCIENT_RUIN_MAP_REVEAL_OFFSET)
.filter { it.position !in civInfo.exploredTiles }
.toList()
.randomOrNull(tileBasedRandom)
if (revealCenter != null)
actions.add {
val tilesToReveal = revealCenter
.getTilesInDistance(ANCIENT_RUIN_MAP_REVEAL_RANGE)
.filter { Random.nextFloat() < ANCIENT_RUIN_MAP_REVEAL_CHANCE }
.map { it.position }
civInfo.exploredTiles.addAll(tilesToReveal)
civInfo.updateViewableTiles()
civInfo.addNotification(
"We have found a crudely-drawn map in the ruins!",
tile.position,
"ImprovementIcons/Ancient ruins"
)
}
(actions.random(tileBasedRandom))()
civInfo.ruinsManager.selectNextRuinsReward(this)
}
fun assignOwner(civInfo: CivilizationInfo, updateCivInfo: Boolean = true) {

View File

@ -1,10 +1,12 @@
package com.unciv.logic.map
import com.unciv.models.ruleset.UniqueTriggerActivation
import com.unciv.models.ruleset.unit.Promotion
class UnitPromotions{
@Transient lateinit var unit:MapUnit
var XP=0
@Suppress("PropertyName")
var XP = 0
var promotions = HashSet<String>()
// The number of times this unit has been promoted
// some promotions don't come from being promoted but from other things,
@ -40,8 +42,9 @@ class UnitPromotions{
}
fun doDirectPromotionEffects(promotion: Promotion) {
for (unique in promotion.uniqueObjects.filter { it.placeholderText == "Heal this unit by [] HP"})
unit.healBy(unique.params[0].toInt())
for (unique in promotion.uniqueObjects) {
UniqueTriggerActivation.triggerUnitwideUnique(unique, unit)
}
}
fun getAvailablePromotions(): List<Promotion> {
@ -52,9 +55,9 @@ class UnitPromotions{
fun clone(): UnitPromotions {
val toReturn = UnitPromotions()
toReturn.XP=XP
toReturn.XP = XP
toReturn.promotions.addAll(promotions)
toReturn.numberOfPromotions=numberOfPromotions
toReturn.numberOfPromotions = numberOfPromotions
return toReturn
}

View File

@ -105,13 +105,14 @@ class MapGenerator(val ruleset: Ruleset) {
}
private fun spreadAncientRuins(map: TileMap) {
if (map.mapParameters.noRuins || !ruleset.tileImprovements.containsKey(Constants.ancientRuins))
val ruinsEquivalents = ruleset.tileImprovements.filter { it.value.isAncientRuinsEquivalent() }
if (map.mapParameters.noRuins || ruinsEquivalents.isEmpty() )
return
val suitableTiles = map.values.filter { it.isLand && !it.isImpassible() }
val locations = randomness.chooseSpreadOutLocations(suitableTiles.size / 50,
suitableTiles, 10)
for (tile in locations)
tile.improvement = Constants.ancientRuins
tile.improvement = ruinsEquivalents.keys.random()
}
private fun spreadResources(tileMap: TileMap) {

View File

@ -0,0 +1,12 @@
package com.unciv.models.ruleset
import com.unciv.models.stats.INamed
class RuinReward : INamed {
override lateinit var name: String
val notification: String = ""
val uniques: List<String> = listOf()
val uniqueObjects: List<Unique> by lazy { uniques.map { Unique(it) } }
val excludedDifficulties: List<String> = listOf()
val weight: Int = 1
}

View File

@ -52,13 +52,14 @@ class Ruleset {
var name = ""
val beliefs = LinkedHashMap<String, Belief>()
val religions = ArrayList<String>()
val buildings = LinkedHashMap<String, Building>()
val difficulties = LinkedHashMap<String, Difficulty>()
val eras = LinkedHashMap<String, Era>()
val nations = LinkedHashMap<String, Nation>()
val policies = LinkedHashMap<String, Policy>()
val policyBranches = LinkedHashMap<String, PolicyBranch>()
val religions = ArrayList<String>()
val ruinRewards = LinkedHashMap<String, RuinReward>()
val quests = LinkedHashMap<String, Quest>()
val specialists = LinkedHashMap<String, Specialist>()
val technologies = LinkedHashMap<String, Technology>()
@ -97,6 +98,7 @@ class Ruleset {
beliefs.putAll(ruleset.beliefs)
quests.putAll(ruleset.quests)
religions.addAll(ruleset.religions)
ruinRewards.putAll(ruleset.ruinRewards)
specialists.putAll(ruleset.specialists)
technologies.putAll(ruleset.technologies)
for (techToRemove in ruleset.modOptions.techsToRemove) technologies.remove(techToRemove)
@ -122,6 +124,7 @@ class Ruleset {
nations.clear()
policies.clear()
religions.clear()
ruinRewards.clear()
quests.clear()
technologies.clear()
terrains.clear()
@ -210,6 +213,10 @@ class Ruleset {
if (religionsFile.exists())
religions += jsonParser.getFromJson(Array<String>::class.java, religionsFile).toList()
val ruinRewardsFile = folderHandle.child("Ruins.json")
if (ruinRewardsFile.exists())
ruinRewards += createHashmap(jsonParser.getFromJson(Array<RuinReward>::class.java, ruinRewardsFile))
val nationsFile = folderHandle.child("Nations.json")
if (nationsFile.exists()) {
nations += createHashmap(jsonParser.getFromJson(Array<Nation>::class.java, nationsFile))
@ -488,10 +495,16 @@ object RulesetCache : HashMap<String,Ruleset>() {
}
newRuleset.updateBuildingCosts() // only after we've added all the mods can we calculate the building costs
// This one should be temporary
if (newRuleset.unitTypes.isEmpty()) {
newRuleset.unitTypes.putAll(getBaseRuleset().unitTypes)
}
// This one should be permanent
if (newRuleset.ruinRewards.isEmpty()) {
newRuleset.ruinRewards.putAll(getBaseRuleset().ruinRewards)
}
return newRuleset
}

View File

@ -1,11 +1,18 @@
package com.unciv.models.ruleset
import com.badlogic.gdx.math.Vector2
import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.CivFlags
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.*
import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.TileInfo
import com.unciv.models.stats.Stat
import com.unciv.models.stats.Stats
import com.unciv.models.translations.fillPlaceholders
import com.unciv.models.translations.getPlaceholderParameters
import com.unciv.models.translations.getPlaceholderText
import com.unciv.models.translations.hasPlaceholderParameters
import com.unciv.ui.worldscreen.unit.UnitActions
import kotlin.random.Random
class Unique(val text:String){
val placeholderText = text.getPlaceholderText()
@ -34,83 +41,285 @@ class UniqueMap:HashMap<String, ArrayList<Unique>>() {
fun getAllUniques() = this.asSequence().flatMap { it.value.asSequence() }
}
// Buildings, techs and policies can have 'triggered' effects
// Buildings, techs, policies, ancient ruins and promotions can have 'triggered' effects
object UniqueTriggerActivation {
fun triggerCivwideUnique(unique: Unique, civInfo: CivilizationInfo, cityInfo: CityInfo? = null) {
val chosenCity = if (cityInfo != null) cityInfo else civInfo.cities.firstOrNull { it.isCapital() }
/** @return boolean whether an action was successfully preformed */
fun triggerCivwideUnique(
unique: Unique,
civInfo: CivilizationInfo,
cityInfo: CityInfo? = null,
tile: TileInfo? = null,
notification: String? = null
): Boolean {
val chosenCity =
if (cityInfo != null) cityInfo
else civInfo.cities.firstOrNull { it.isCapital() }
val tileBasedRandom =
if (tile != null) Random(tile.position.toString().hashCode())
else Random(-550) // Very random indeed
when (unique.placeholderText) {
"Free [] appears" -> {
val unitName = unique.params[0]
val unit = civInfo.gameInfo.ruleSet.units[unitName]
if (chosenCity != null && unit != null && (!unit.uniques.contains("Founds a new city") || !civInfo.isOneCityChallenger()))
civInfo.addUnit(unitName, chosenCity)
if (chosenCity == null || unit == null || (unit.uniques.contains("Founds a new city") && civInfo.isOneCityChallenger()))
return false
val placedUnit = civInfo.addUnit(unitName, chosenCity)
if (notification != null && placedUnit != null) {
civInfo.addNotification(
notification,
placedUnit.getTile().position,
placedUnit.name
)
}
return true
}
"[] free [] units appear" -> {
val unitName = unique.params[1]
val unit = civInfo.gameInfo.ruleSet.units[unitName]
if (chosenCity != null && unit != null && (!unit.uniques.contains("Founds a new city") || !civInfo.isOneCityChallenger()))
for (i in 1..unique.params[0].toInt())
civInfo.addUnit(unitName, chosenCity)
if (chosenCity == null || unit == null || (unit.uniques.contains("Founds a new city") && civInfo.isOneCityChallenger()))
return false
val tilesUnitsWerePlacedOn: MutableList<Vector2> = mutableListOf()
for (i in 1..unique.params[0].toInt()) {
val placedUnit = civInfo.addUnit(unitName, chosenCity)
if (placedUnit != null)
tilesUnitsWerePlacedOn.add(placedUnit.getTile().position)
}
if (notification != null && tilesUnitsWerePlacedOn.isNotEmpty()) {
civInfo.addNotification(
notification,
LocationAction(tilesUnitsWerePlacedOn),
civInfo.getEquivalentUnit(unit).name
)
}
return true
}
// Differs from "Free [] appears" in that it spawns near the ruins instead of in a city
"Free [] found in the ruins" -> {
val unit = civInfo.getEquivalentUnit(unique.params[0])
val placingTile =
tile ?: civInfo.cities.random().getCenterTile()
val placedUnit = civInfo.placeUnitNearTile(placingTile.position, unit.name)
if (notification != null && placedUnit != null) {
val notificationText =
if (notification.hasPlaceholderParameters())
notification.fillPlaceholders(unique.params[0])
else notification
civInfo.addNotification(
notificationText,
placedUnit.getTile().position,
placedUnit.name
)
}
return placedUnit != null
}
// spectators get all techs at start of game, and if (in a mod) a tech gives a free policy, the game gets stuck on the policy picker screen
"Free Social Policy" -> {
if (civInfo.isSpectator()) return false
civInfo.policies.freePolicies++
if (notification != null) {
civInfo.addNotification(notification, NotificationIcon.Culture)
}
return true
}
"[] Free Social Policies" -> {
if (civInfo.isSpectator()) return false
civInfo.policies.freePolicies += unique.params[0].toInt()
if (notification != null) {
civInfo.addNotification(notification, NotificationIcon.Culture)
}
return true
}
"Empire enters golden age" -> {
civInfo.goldenAges.enterGoldenAge()
if (notification != null) {
civInfo.addNotification(notification, NotificationIcon.Happiness)
}
return true
}
// spectators get all techs at start of game, and if (in a mod) a tech gives a free policy, the game stucks on the policy picker screen
"Free Social Policy" -> if (!civInfo.isSpectator()) civInfo.policies.freePolicies++
"[] Free Social Policies" -> if (!civInfo.isSpectator()) civInfo.policies.freePolicies += unique.params[0].toInt()
"Empire enters golden age" -> civInfo.goldenAges.enterGoldenAge()
"Free Great Person" -> {
if (civInfo.isSpectator()) return
if (civInfo.isPlayerCivilization()) civInfo.greatPeople.freeGreatPeople++
else {
if (civInfo.isSpectator()) return false
if (civInfo.isPlayerCivilization()) {
civInfo.greatPeople.freeGreatPeople++
if (notification != null)
civInfo.addNotification(notification) // Anyone an idea for a good icon?
return true
} else {
val greatPeople = civInfo.getGreatPeople()
if (greatPeople.isEmpty()) return
if (greatPeople.isEmpty()) return false
var greatPerson = civInfo.getGreatPeople().random()
val preferredVictoryType = civInfo.victoryType()
if (preferredVictoryType == VictoryType.Cultural) {
val culturalGP = greatPeople.firstOrNull { it.uniques.contains("Great Person - [Culture]") }
val culturalGP =
greatPeople.firstOrNull { it.uniques.contains("Great Person - [Culture]") }
if (culturalGP != null) greatPerson = culturalGP
}
if (preferredVictoryType == VictoryType.Scientific) {
val scientificGP = greatPeople.firstOrNull { it.uniques.contains("Great Person - [Science]") }
val scientificGP =
greatPeople.firstOrNull { it.uniques.contains("Great Person - [Science]") }
if (scientificGP != null) greatPerson = scientificGP
}
civInfo.addUnit(greatPerson.name, chosenCity)
return civInfo.addUnit(greatPerson.name, chosenCity) != null
}
}
// Deprecated since 3.15.4
"+1 population in each city" ->
for (city in civInfo.cities) {
city.population.addPopulation(1)
}
//
"[] population []" ->
"+1 population in each city" -> {
for (city in civInfo.cities) {
city.population.addPopulation(1)
}
if (notification != null) {
civInfo.addNotification(
notification,
LocationAction(civInfo.cities.map { it.location }),
NotificationIcon.Population
)
}
return true
}
//
"[] population []" -> {
val citiesWithPopulationChanged: MutableList<Vector2> = mutableListOf()
for (city in civInfo.cities) {
if (city.matchesFilter(unique.params[1])) {
city.population.addPopulation(unique.params[0].toInt())
citiesWithPopulationChanged.add(city.location)
}
}
"Free Technology" -> if (!civInfo.isSpectator()) civInfo.tech.freeTechs += 1
"[] Free Technologies" -> if (!civInfo.isSpectator()) civInfo.tech.freeTechs += unique.params[0].toInt()
if (notification != null && citiesWithPopulationChanged.isNotEmpty())
civInfo.addNotification(
notification,
LocationAction(citiesWithPopulationChanged),
NotificationIcon.Population
)
return citiesWithPopulationChanged.isNotEmpty()
}
"[] population in a random city" -> {
if (civInfo.cities.isEmpty()) return false
val randomCity = civInfo.cities.random(tileBasedRandom)
randomCity.population.addPopulation(unique.params[0].toInt())
if (notification != null) {
val notificationText =
if (notification.hasPlaceholderParameters())
notification.fillPlaceholders(randomCity.name)
else notification
civInfo.addNotification(
notificationText,
randomCity.location,
NotificationIcon.Population
)
}
return true
}
"Free Technology" -> {
if (civInfo.isSpectator()) return false
civInfo.tech.freeTechs += 1
if (notification != null) {
civInfo.addNotification(notification, NotificationIcon.Science)
}
return true
}
"[] Free Technologies" -> {
if (civInfo.isSpectator()) return false
civInfo.tech.freeTechs += unique.params[0].toInt()
if (notification != null) {
civInfo.addNotification(notification, NotificationIcon.Science)
}
return true
}
"[] free random researchable Tech(s) from the []" -> {
val researchableTechsFromThatEra = civInfo.gameInfo.ruleSet.technologies.values
.filter {
(it.column!!.era == unique.params[1] || unique.params[1] == "any era")
&& civInfo.tech.canBeResearched(it.name)
}
if (researchableTechsFromThatEra.isEmpty()) return false
"Quantity of strategic resources produced by the empire increased by 100%" -> civInfo.updateDetailedCivResources()
"+[]% attack strength to all [] Units for [] turns" -> civInfo.temporaryUniques.add(Pair(unique, unique.params[2].toInt()))
val techsToResearch = researchableTechsFromThatEra.shuffled(tileBasedRandom)
.take(unique.params[0].toInt())
for (tech in techsToResearch)
civInfo.tech.addTechnology(tech.name)
"Reveals the entire map" -> civInfo.exploredTiles.addAll(civInfo.gameInfo.tileMap.values.asSequence().map { it.position })
if (notification != null) {
val notificationText =
if (notification.hasPlaceholderParameters())
notification.fillPlaceholders(*(techsToResearch.map { it.name }
.toTypedArray()))
else notification
civInfo.addNotification(notificationText, NotificationIcon.Science)
}
return true
}
"Quantity of strategic resources produced by the empire increased by 100%" -> {
civInfo.updateDetailedCivResources()
if (notification != null) {
civInfo.addNotification(
notification,
NotificationIcon.War
) // I'm open for better icons
}
return true
}
"+[]% attack strength to all [] Units for [] turns" -> {
civInfo.temporaryUniques.add(Pair(unique, unique.params[2].toInt()))
if (notification != null) {
civInfo.addNotification(notification, NotificationIcon.War)
}
return true
}
"Reveals the entire map" -> {
if (notification != null) {
civInfo.addNotification(notification, "UnitIcons/Scout")
}
return civInfo.exploredTiles.addAll(
civInfo.gameInfo.tileMap.values.asSequence().map { it.position })
}
"[] units gain the [] promotion" -> {
val filter = unique.params[0]
val promotion = unique.params[1]
for (unit in civInfo.getCivUnits())
val promotedUnitLocations: MutableList<Vector2> = mutableListOf()
for (unit in civInfo.getCivUnits()) {
if (unit.matchesFilter(filter)
|| civInfo.gameInfo.ruleSet.unitPromotions.values.any {
it.name == promotion && unit.type!!.name in it.unitTypes
&& civInfo.gameInfo.ruleSet.unitPromotions.values.any {
it.name == promotion && unit.type.name in it.unitTypes
}
) {
unit.promotions.addPromotion(promotion, isFree = true)
promotedUnitLocations.add(unit.getTile().position)
}
}
if (notification != null) {
civInfo.addNotification(
notification,
LocationAction(promotedUnitLocations),
"unitPromotionIcons/${unique.params[1]}"
)
}
return promotedUnitLocations.isNotEmpty()
}
"Allied City-States will occasionally gift Great People" -> {
civInfo.addFlag(
CivFlags.CityStateGreatPersonGift.name,
civInfo.turnsForGreatPersonFromCityState() / 2
)
if (notification != null) {
civInfo.addNotification(notification, NotificationIcon.CityState)
}
return true
}
"Allied City-States will occasionally gift Great People" ->
civInfo.addFlag(CivFlags.CityStateGreatPersonGift.name, civInfo.turnsForGreatPersonFromCityState() / 2)
// The mechanics for granting great people are wonky, but basically the following happens:
// Based on the game speed, a timer with some amount of turns is set, 40 on regular speed
// Every turn, 1 is subtracted from this timer, as long as you have at least 1 city state ally
@ -126,10 +335,191 @@ object UniqueTriggerActivation {
// Note that the way this is implemented now, this unique does NOT stack
// I could parametrize the [Allied], but eh.
"Triggers voting for the Diplomatic Victory" ->
"Gain [] []" -> {
if (Stat.values().none { it.name == unique.params[1] }) return false
val stat = Stat.valueOf(unique.params[1])
if (stat !in listOf(Stat.Gold, Stat.Faith, Stat.Science, Stat.Culture)
|| unique.params[0].toIntOrNull() == null
) return false
civInfo.addStat(stat, unique.params[0].toInt())
if (notification != null)
civInfo.addNotification(notification, stat.notificationIcon)
return true
}
"Gain []-[] []" -> {
if (Stat.values().none { it.name == unique.params[2] }) return false
val stat = Stat.valueOf(unique.params[2])
if (stat !in listOf(Stat.Gold, Stat.Faith, Stat.Science, Stat.Culture)
|| unique.params[0].toIntOrNull() == null
|| unique.params[1].toIntOrNull() == null
) return false
val foundStatAmount =
(tileBasedRandom.nextInt(unique.params[0].toInt(), unique.params[1].toInt()) *
civInfo.gameInfo.gameParameters.gameSpeed.modifier
).toInt()
civInfo.addStat(
Stat.valueOf(unique.params[2]),
foundStatAmount
)
if (notification != null) {
val notificationText =
if (notification.hasPlaceholderParameters()) {
notification.fillPlaceholders(foundStatAmount.toString())
} else notification
civInfo.addNotification(notificationText, stat.notificationIcon)
}
return true
}
"Gain enough Faith for a Pantheon" -> {
if (civInfo.religionManager.religionState != ReligionState.None) return false
val gainedFaith = civInfo.religionManager.faithForPantheon(2)
if (gainedFaith == 0) return false
civInfo.addStat(Stat.Faith, gainedFaith)
if (notification != null) {
val notificationText =
if (notification.hasPlaceholderParameters())
notification.fillPlaceholders(gainedFaith.toString())
else notification
civInfo.addNotification(notificationText, NotificationIcon.Faith)
}
return true
}
"Gain enough Faith for []% of a Great Prophet" -> {
val gainedFaith =
(civInfo.religionManager.faithForNextGreatProphet() * (unique.params[0].toFloat() / 100f)).toInt()
if (gainedFaith == 0) return false
civInfo.addStat(Stat.Faith, gainedFaith)
if (notification != null) {
val notificationText =
if (notification.hasPlaceholderParameters())
notification.fillPlaceholders(gainedFaith.toString())
else notification
civInfo.addNotification(notificationText, NotificationIcon.Faith)
}
return true
}
"Reveal up to [] [] within a [] tile radius" -> {
if (tile == null) return false
val nearbyRevealableTiles = tile
.getTilesInDistance(unique.params[2].toInt())
.filter {
!civInfo.exploredTiles.contains(it.position) && it.matchesFilter(
unique.params[1]
)
}
.map { it.position }
if (nearbyRevealableTiles.none()) return false
civInfo.exploredTiles.addAll(nearbyRevealableTiles
.shuffled(tileBasedRandom)
.apply {
if (unique.params[0] != "All") this.take(unique.params[0].toInt())
}
)
if (notification != null) {
civInfo.addNotification(
notification,
LocationAction(nearbyRevealableTiles.toList())
) // We really need a barbarian icon
}
return true
}
"From a randomly chosen tile [] tiles away from the ruins, reveal tiles up to [] tiles away with []% chance" -> {
if (tile == null) return false
val revealCenter = tile.getTilesAtDistance(unique.params[0].toInt())
.filter { it.position !in civInfo.exploredTiles }
.toList()
.randomOrNull(tileBasedRandom)
if (revealCenter == null) return false
val tilesToReveal = revealCenter
.getTilesInDistance(unique.params[1].toInt())
.map { it.position }
.filter { tileBasedRandom.nextFloat() < unique.params[2].toFloat() / 100f }
civInfo.exploredTiles.addAll(tilesToReveal)
civInfo.updateViewableTiles()
if (notification != null)
civInfo.addNotification(
notification,
tile.position,
"ImprovementIcons/Ancient ruins"
)
}
"Triggers voting for the Diplomatic Victory" -> {
for (civ in civInfo.gameInfo.civilizations)
if (!civ.isBarbarian() && !civ.isSpectator())
civ.addFlag(CivFlags.TurnsTillNextDiplomaticVote.name, civInfo.getTurnsBetweenDiplomaticVotings())
civ.addFlag(
CivFlags.TurnsTillNextDiplomaticVote.name,
civInfo.getTurnsBetweenDiplomaticVotings()
)
if (notification != null)
civInfo.addNotification(notification, NotificationIcon.Diplomacy)
return true
}
}
return false
}
/** @return boolean whether an action was successfully preformed */
fun triggerUnitwideUnique(
unique: Unique,
unit: MapUnit,
notification: String? = null
): Boolean {
when (unique.placeholderText) {
"Heal this unit by [] HP" -> {
unit.healBy(unique.params[0].toInt())
if (notification != null)
unit.civInfo.addNotification(notification, unit.getTile().position) // Do we have a heal icon?
return true
}
"This Unit gains [] XP" -> {
if (!unit.baseUnit.isMilitary()) return false
unit.promotions.XP += unique.params[0].toInt()
if (notification != null)
unit.civInfo.addNotification(notification, unit.getTile().position)
return true
}
"This Unit upgrades for free" -> {
val upgradeAction = UnitActions.getUpgradeAction(unit, true)
?: return false
upgradeAction.action!!()
if (notification != null)
unit.civInfo.addNotification(notification, unit.getTile().position)
return true
}
"This Unit upgrades for free including special upgrades" -> {
val upgradeAction = UnitActions.getAncientRuinsUpgradeAction(unit)
?: return false
upgradeAction.action!!()
if (notification != null)
unit.civInfo.addNotification(notification, unit.getTile().position)
return true
}
"This Unit gains the [] promotion" -> {
val promotion = unit.civInfo.gameInfo.ruleSet.unitPromotions.keys.firstOrNull { it == unique.params[0] }
if (promotion == null) return false
unit.promotions.addPromotion(promotion, true)
if (notification != null)
unit.civInfo.addNotification(notification, unit.name)
return true
}
}
return false
}
}

View File

@ -75,6 +75,7 @@ class TileImprovement : NamedStats(), ICivilopediaText {
fun hasUnique(unique: String) = uniques.contains(unique)
fun isGreatImprovement() = hasUnique("Great Improvement")
fun isRoad() = RoadStatus.values().any { it != RoadStatus.None && it.name == this.name }
fun isAncientRuinsEquivalent() = hasUnique("Provides a random bonus when entered")
/**
* Check: Is this improvement allowed on a [given][name] terrain feature?

View File

@ -45,6 +45,12 @@ class BaseUnit : INamed, INonPerpetualConstruction, CivilopediaText() {
var promotions = HashSet<String>()
var obsoleteTech: String? = null
var upgradesTo: String? = null
val specialUpgradesTo: String? by lazy {
uniqueObjects
.filter { it.placeholderText == "May upgrade to [] through ruins-like effects"}
.map { it.params[0] }
.firstOrNull()
}
var replaces: String? = null
var uniqueTo: String? = null
var attackSound: String? = null
@ -262,10 +268,14 @@ class BaseUnit : INamed, INonPerpetualConstruction, CivilopediaText() {
return ""
}
fun getRejectionReason(civInfo: CivilizationInfo): String {
/** @param ignoreTechPolicyRequirements: its `true` value is used when upgrading via ancient ruins,
* as there we don't care whether we have the required tech, policy or building for the unit,
* but do still care whether we have the resources required for the unit
*/
fun getRejectionReason(civInfo: CivilizationInfo, ignoreTechPolicyRequirements: Boolean = false): String {
if (uniques.contains("Unbuildable")) return "Unbuildable"
if (requiredTech != null && !civInfo.tech.isResearched(requiredTech!!)) return "$requiredTech not researched"
if (obsoleteTech != null && civInfo.tech.isResearched(obsoleteTech!!)) return "Obsolete by $obsoleteTech"
if (!ignoreTechPolicyRequirements && requiredTech != null && !civInfo.tech.isResearched(requiredTech!!)) return "$requiredTech not researched"
if (!ignoreTechPolicyRequirements && obsoleteTech != null && civInfo.tech.isResearched(obsoleteTech!!)) return "Obsolete by $obsoleteTech"
if (uniqueTo != null && uniqueTo != civInfo.civName) return "Unique to $uniqueTo"
if (civInfo.gameInfo.ruleSet.units.values.any { it.uniqueTo == civInfo.civName && it.replaces == name })
return "Our unique unit replaces this"
@ -279,9 +289,9 @@ class BaseUnit : INamed, INonPerpetualConstruction, CivilopediaText() {
for (unique in uniqueObjects.filter { it.placeholderText == "Requires []" }) {
val filter = unique.params[0]
if (filter in civInfo.gameInfo.ruleSet.buildings) {
if (!ignoreTechPolicyRequirements && filter in civInfo.gameInfo.ruleSet.buildings) {
if (civInfo.cities.none { it.cityConstructions.containsBuildingOrEquivalent(filter) }) return unique.text // Wonder is not built
} else if (!civInfo.policies.adoptedPolicies.contains(filter)) return "Policy is not adopted"
} else if (!ignoreTechPolicyRequirements && !civInfo.policies.adoptedPolicies.contains(filter)) return "Policy is not adopted"
}
for ((resource, amount) in getResourceRequirements())
@ -301,6 +311,12 @@ class BaseUnit : INamed, INonPerpetualConstruction, CivilopediaText() {
return getRejectionReason(cityConstructions) == ""
}
/** Preemptively as in: buildable without actually having the tech and/or policy required for it.
* Still checks for resource use and other things
*/
fun isBuildableIgnoringTechs(civInfo: CivilizationInfo) =
getRejectionReason(civInfo, true) == ""
override fun postBuildEvent(cityConstructions: CityConstructions, wasBought: Boolean): Boolean {
val civInfo = cityConstructions.cityInfo.civInfo
val unit = civInfo.placeUnitNearTile(cityConstructions.cityInfo.location, name)

View File

@ -1,13 +1,14 @@
package com.unciv.models.stats
import com.unciv.logic.civilization.NotificationIcon
import com.unciv.models.UncivSound
enum class Stat (val sound: UncivSound) {
Production(UncivSound.Click),
Food(UncivSound.Click),
Gold(UncivSound.Coin),
Science(UncivSound.Chimes),
Culture(UncivSound.Paper),
Happiness(UncivSound.Click),
Faith(UncivSound.Choir),
enum class Stat(val notificationIcon: String, val purchaseSound: UncivSound) {
Production(NotificationIcon.Production, UncivSound.Click),
Food(NotificationIcon.Food, UncivSound.Click),
Gold(NotificationIcon.Gold, UncivSound.Coin),
Science(NotificationIcon.Science, UncivSound.Chimes),
Culture(NotificationIcon.Culture, UncivSound.Paper),
Happiness(NotificationIcon.Happiness, UncivSound.Click),
Faith(NotificationIcon.Faith, UncivSound.Choir);
}

View File

@ -276,6 +276,8 @@ fun String.equalsPlaceholderText(str:String): Boolean {
return this.getPlaceholderText() == str
}
fun String.hasPlaceholderParameters() = squareBraceRegex.containsMatchIn(this)
fun String.getPlaceholderParameters() = squareBraceRegex.findAll(this).map { it.groups[1]!!.value }.toList()
/** Substitutes placeholders with [strings], respecting order of appearance. */

View File

@ -419,7 +419,7 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase
button.setText("Buy".tr() + " " + constructionBuyCost)
button.add(ImageGetter.getStatIcon(stat.name)).size(20f).padBottom(2f)
button.onClick(stat.sound) {
button.onClick(stat.purchaseSound) {
button.disable()
cityScreen.closeAllPopups()

View File

@ -182,6 +182,7 @@ class NewGameScreen(
try {
newGame = GameStarter.startNewGame(gameSetupInfo)
} catch (exception: Exception) {
exception.printStackTrace()
Gdx.app.postRunnable {
Popup(this).apply {
addGoodSizedLabel("It looks like we can't make a map with the parameters you requested!".tr()).row()

View File

@ -492,7 +492,7 @@ class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap
for (tile in allWorldTileGroups) {
if (tile.icons.populationIcon != null) tile.icons.populationIcon!!.color.a = fadeout
if (tile.icons.improvementIcon != null && tile.tileInfo.improvement != Constants.barbarianEncampment
&& tile.tileInfo.improvement != Constants.ancientRuins)
&& tile.tileInfo.getTileImprovement()!!.isAncientRuinsEquivalent())
tile.icons.improvementIcon!!.color.a = fadeout
if (tile.resourceImage != null) tile.resourceImage!!.color.a = fadeout
}

View File

@ -298,35 +298,59 @@ object UnitActions {
if (upgradeAction != null) actionList += upgradeAction
}
fun getUpgradeAction(unit: MapUnit): UnitAction? {
fun getUpgradeAction(unit: MapUnit, isFree: Boolean = false): UnitAction? {
val tile = unit.currentTile
if (unit.baseUnit().upgradesTo == null || tile.getOwner() != unit.civInfo
|| !unit.canUpgrade()) return null
val goldCostOfUpgrade = unit.getCostOfUpgrade()
if (unit.baseUnit().upgradesTo == null || !unit.canUpgrade()) return null
if (tile.getOwner() != unit.civInfo && !isFree) return null
val goldCostOfUpgrade =
if (isFree) 0
else unit.getCostOfUpgrade()
val upgradedUnit = unit.getUnitToUpgradeTo()
return UnitAction(UnitActionType.Upgrade,
title = "Upgrade to [${upgradedUnit.name}] ([$goldCostOfUpgrade] gold)",
action = {
unit.civInfo.addGold(-goldCostOfUpgrade)
val unitTile = unit.getTile()
unit.destroy()
val newUnit = unit.civInfo.placeUnitNearTile(unitTile.position, upgradedUnit.name)!!
newUnit.health = unit.health
newUnit.promotions = unit.promotions
newUnit.instanceName = unit.instanceName
for (promotion in newUnit.baseUnit.promotions)
if (promotion !in newUnit.promotions.promotions)
newUnit.promotions.addPromotion(promotion, true)
newUnit.updateUniques()
newUnit.updateVisibleTiles()
newUnit.currentMovement = 0f
}.takeIf {
title = "Upgrade to [${upgradedUnit.name}] ([$goldCostOfUpgrade] gold)",
action = {
unit.civInfo.addGold(-goldCostOfUpgrade)
val unitTile = unit.getTile()
unit.destroy()
val newUnit = unit.civInfo.placeUnitNearTile(unitTile.position, upgradedUnit.name)!!
unit.copyStatisticsTo(newUnit)
newUnit.currentMovement = 0f
}.takeIf {
isFree ||
(
unit.civInfo.gold >= goldCostOfUpgrade && !unit.isEmbarked()
&& unit.currentMovement == unit.getMaxMovement().toFloat()
})
&& unit.currentMovement == unit.getMaxMovement().toFloat()
)
}
)
}
fun getAncientRuinsUpgradeAction(unit: MapUnit): UnitAction? {
val upgradedUnitName =
when {
unit.baseUnit.specialUpgradesTo != null -> unit.baseUnit.specialUpgradesTo
unit.baseUnit.upgradesTo != null -> unit.baseUnit.upgradesTo
else -> return null
}
val upgradedUnit =
unit.civInfo.getEquivalentUnit(
unit.civInfo.gameInfo.ruleSet.units[upgradedUnitName]!!
)
if (!unit.canUpgrade(upgradedUnit,true)) return null
return UnitAction(UnitActionType.Upgrade,
title = "Upgrade to [${upgradedUnit.name}] (free)",
action = {
val unitTile = unit.getTile()
unit.destroy()
val newUnit = unit.civInfo.placeUnitNearTile(unitTile.position, upgradedUnit.name)!!
unit.copyStatisticsTo(newUnit)
newUnit.currentMovement = 0f
}
)
}
private fun addBuildingImprovementsAction(unit: MapUnit, actionList: ArrayList<UnitAction>, tile: TileInfo, worldScreen: WorldScreen, unitTable: UnitTable) {