mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-27 13:55:54 -04:00
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:
parent
4d0b2405e1
commit
92d3fa65e3
64
android/assets/jsons/Civ V - Vanilla/Ruins.json
Normal file
64
android/assets/jsons/Civ V - Vanilla/Ruins.json
Normal 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"]
|
||||||
|
}
|
||||||
|
]
|
@ -234,7 +234,9 @@
|
|||||||
"shortcutKey": "F"
|
"shortcutKey": "F"
|
||||||
},
|
},
|
||||||
|
|
||||||
{ "name": "Ancient ruins", "uniques": ["Unpillagable"],
|
{
|
||||||
|
"name": "Ancient ruins",
|
||||||
|
"uniques": ["Unpillagable", "Provides a random bonus when entered"],
|
||||||
"civilopediaText": [
|
"civilopediaText": [
|
||||||
{"text":"Ancient ruins provide a one-time random bonus when explored"},
|
{"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}
|
{"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 ruins",
|
||||||
{ "name": "City center", "uniques": ["Unpillagable", "Indestructible"],
|
"uniques": ["Unpillagable"],
|
||||||
|
"civilopediaText": [{"text":"A bleak reminder of the destruction wreaked by War"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "City center",
|
||||||
|
"uniques": ["Unpillagable", "Indestructible"],
|
||||||
"civilopediaText": [
|
"civilopediaText": [
|
||||||
{"text":"Marks the center of a city"},
|
{"text":"Marks the center of a city"},
|
||||||
{"text":"Appearance changes with the technological era of the owning civilization"}
|
{"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"}]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
"strength": 5,
|
"strength": 5,
|
||||||
"cost": 25,
|
"cost": 25,
|
||||||
"obsoleteTech": "Scientific Theory",
|
"obsoleteTech": "Scientific Theory",
|
||||||
"uniques": ["Ignores terrain cost"],
|
"uniques": ["Ignores terrain cost", "May upgrade to [Archer] through ruins-like effects"],
|
||||||
"attackSound": "nonmetalhit"
|
"attackSound": "nonmetalhit"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -37,6 +37,7 @@
|
|||||||
"cost": 40,
|
"cost": 40,
|
||||||
"obsoleteTech": "Metal Casting",
|
"obsoleteTech": "Metal Casting",
|
||||||
"upgradesTo": "Swordsman",
|
"upgradesTo": "Swordsman",
|
||||||
|
"uniques" : ["May upgrade to [Spearman] through ruins-like effects"],
|
||||||
"attackSound": "nonmetalhit",
|
"attackSound": "nonmetalhit",
|
||||||
"civilopediaText": [
|
"civilopediaText": [
|
||||||
{"text": "This is your basic, club-swinging fighter."}
|
{"text": "This is your basic, club-swinging fighter."}
|
||||||
|
@ -1141,4 +1141,8 @@ in all cities with a world wonder =
|
|||||||
in all cities connected to capital =
|
in all cities connected to capital =
|
||||||
in all cities with a garrison =
|
in all cities with a garrison =
|
||||||
|
|
||||||
|
# Uniques not found in JSON files
|
||||||
|
|
||||||
|
Only available after [] turns =
|
||||||
|
This Unit upgrades for free =
|
||||||
|
|
||||||
|
@ -45,7 +45,6 @@ object Constants {
|
|||||||
const val fountainOfYouth = "Fountain of Youth"
|
const val fountainOfYouth = "Fountain of Youth"
|
||||||
|
|
||||||
const val barbarianEncampment = "Barbarian encampment"
|
const val barbarianEncampment = "Barbarian encampment"
|
||||||
const val ancientRuins = "Ancient ruins"
|
|
||||||
|
|
||||||
const val peaceTreaty = "Peace Treaty"
|
const val peaceTreaty = "Peace Treaty"
|
||||||
const val researchAgreement = "Research Agreement"
|
const val researchAgreement = "Research Agreement"
|
||||||
|
@ -240,9 +240,14 @@ object GameStarter {
|
|||||||
if(civ.isCityState())
|
if(civ.isCityState())
|
||||||
addCityStateLuxury(gameInfo, startingLocation)
|
addCityStateLuxury(gameInfo, startingLocation)
|
||||||
|
|
||||||
for (tile in startingLocation.getTilesInDistance(3))
|
for (tile in startingLocation.getTilesInDistance(3)) {
|
||||||
if (tile.improvement == Constants.ancientRuins)
|
if (tile.improvement != null
|
||||||
|
&& !tile.improvement!!.startsWith("StartingLocation")
|
||||||
|
&& tile.getTileImprovement()!!.isAncientRuinsEquivalent()
|
||||||
|
) {
|
||||||
tile.improvement = null // Remove ancient ruins in immediate vicinity
|
tile.improvement = null // Remove ancient ruins in immediate vicinity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun placeNearStartingPosition(unitName: String) {
|
fun placeNearStartingPosition(unitName: String) {
|
||||||
civ.placeUnitNearTile(startingLocation.position, unitName)
|
civ.placeUnitNearTile(startingLocation.position, unitName)
|
||||||
|
@ -48,7 +48,10 @@ object UnitAutomation {
|
|||||||
val unitDistanceToTiles = unit.movement.getDistanceToTiles()
|
val unitDistanceToTiles = unit.movement.getDistanceToTiles()
|
||||||
val tileWithRuinOrEncampment = unitDistanceToTiles.keys
|
val tileWithRuinOrEncampment = unitDistanceToTiles.keys
|
||||||
.firstOrNull {
|
.firstOrNull {
|
||||||
(it.improvement == Constants.ancientRuins || it.improvement == Constants.barbarianEncampment)
|
(
|
||||||
|
(it.improvement != null && it.getTileImprovement()!!.isAncientRuinsEquivalent())
|
||||||
|
|| it.improvement == Constants.barbarianEncampment
|
||||||
|
)
|
||||||
&& unit.movement.canMoveTo(it)
|
&& unit.movement.canMoveTo(it)
|
||||||
}
|
}
|
||||||
if (tileWithRuinOrEncampment == null)
|
if (tileWithRuinOrEncampment == null)
|
||||||
|
@ -7,6 +7,7 @@ import com.unciv.logic.GameInfo
|
|||||||
import com.unciv.logic.UncivShowableException
|
import com.unciv.logic.UncivShowableException
|
||||||
import com.unciv.logic.automation.NextTurnAutomation
|
import com.unciv.logic.automation.NextTurnAutomation
|
||||||
import com.unciv.logic.city.CityInfo
|
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.DiplomacyFlags
|
||||||
import com.unciv.logic.civilization.diplomacy.DiplomacyManager
|
import com.unciv.logic.civilization.diplomacy.DiplomacyManager
|
||||||
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
|
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
|
||||||
@ -86,6 +87,7 @@ class CivilizationInfo {
|
|||||||
var goldenAges = GoldenAgeManager()
|
var goldenAges = GoldenAgeManager()
|
||||||
var greatPeople = GreatPersonManager()
|
var greatPeople = GreatPersonManager()
|
||||||
var victoryManager = VictoryManager()
|
var victoryManager = VictoryManager()
|
||||||
|
var ruinsManager = RuinsManager()
|
||||||
var diplomacy = HashMap<String, DiplomacyManager>()
|
var diplomacy = HashMap<String, DiplomacyManager>()
|
||||||
var notifications = ArrayList<Notification>()
|
var notifications = ArrayList<Notification>()
|
||||||
val popupAlerts = ArrayList<PopupAlert>()
|
val popupAlerts = ArrayList<PopupAlert>()
|
||||||
@ -133,6 +135,7 @@ class CivilizationInfo {
|
|||||||
toReturn.questManager = questManager.clone()
|
toReturn.questManager = questManager.clone()
|
||||||
toReturn.goldenAges = goldenAges.clone()
|
toReturn.goldenAges = goldenAges.clone()
|
||||||
toReturn.greatPeople = greatPeople.clone()
|
toReturn.greatPeople = greatPeople.clone()
|
||||||
|
toReturn.ruinsManager = ruinsManager.clone()
|
||||||
toReturn.victoryManager = victoryManager.clone()
|
toReturn.victoryManager = victoryManager.clone()
|
||||||
toReturn.allyCivName = allyCivName
|
toReturn.allyCivName = allyCivName
|
||||||
for (diplomacyManager in diplomacy.values.map { it.clone() })
|
for (diplomacyManager in diplomacy.values.map { it.clone() })
|
||||||
@ -510,6 +513,7 @@ class CivilizationInfo {
|
|||||||
tech.civInfo = this
|
tech.civInfo = this
|
||||||
tech.setTransients()
|
tech.setTransients()
|
||||||
|
|
||||||
|
ruinsManager.setTransients(this)
|
||||||
|
|
||||||
for (diplomacyManager in diplomacy.values) {
|
for (diplomacyManager in diplomacy.values) {
|
||||||
diplomacyManager.civInfo = this
|
diplomacyManager.civInfo = this
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.unciv.logic.civilization
|
package com.unciv.logic.civilization
|
||||||
|
|
||||||
import com.badlogic.gdx.math.Vector2
|
import com.badlogic.gdx.math.Vector2
|
||||||
|
import com.unciv.models.stats.Stat
|
||||||
import com.unciv.ui.cityscreen.CityScreen
|
import com.unciv.ui.cityscreen.CityScreen
|
||||||
import com.unciv.ui.pickerscreens.TechPickerScreen
|
import com.unciv.ui.pickerscreens.TechPickerScreen
|
||||||
import com.unciv.ui.trade.DiplomacyScreen
|
import com.unciv.ui.trade.DiplomacyScreen
|
||||||
@ -18,6 +19,12 @@ object NotificationIcon {
|
|||||||
const val Diplomacy = "OtherIcons/Diplomacy"
|
const val Diplomacy = "OtherIcons/Diplomacy"
|
||||||
const val City = "ImprovementIcons/City center"
|
const val City = "ImprovementIcons/City center"
|
||||||
const val Citadel = "ImprovementIcons/Citadel"
|
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"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,7 +24,8 @@ class ReligionManager {
|
|||||||
// But the other one should still be _somewhere_. So our only option is to have the GameInfo
|
// 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.
|
// 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
|
var religionState = ReligionState.None
|
||||||
private set
|
private set
|
||||||
@ -62,7 +63,8 @@ class ReligionManager {
|
|||||||
storedFaith += faithFromNewTurn
|
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 {
|
fun canFoundPantheon(): Boolean {
|
||||||
if (!civInfo.gameInfo.hasReligionEnabled()) return false
|
if (!civInfo.gameInfo.hasReligionEnabled()) return false
|
||||||
@ -92,7 +94,7 @@ class ReligionManager {
|
|||||||
|
|
||||||
// https://www.reddit.com/r/civ/comments/2m82wu/can_anyone_detail_the_finer_points_of_great/
|
// https://www.reddit.com/r/civ/comments/2m82wu/can_anyone_detail_the_finer_points_of_great/
|
||||||
// Game files (globaldefines.xml)
|
// Game files (globaldefines.xml)
|
||||||
private fun faithForNextGreatProphet() = (
|
fun faithForNextGreatProphet() = (
|
||||||
(200 + 100 * greatProphetsEarned * (greatProphetsEarned + 1) / 2) *
|
(200 + 100 * greatProphetsEarned * (greatProphetsEarned + 1) / 2) *
|
||||||
civInfo.gameInfo.gameParameters.gameSpeed.modifier
|
civInfo.gameInfo.gameParameters.gameSpeed.modifier
|
||||||
).toInt()
|
).toInt()
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,6 @@ import com.unciv.models.ruleset.tile.TileImprovement
|
|||||||
import com.unciv.models.ruleset.unit.BaseUnit
|
import com.unciv.models.ruleset.unit.BaseUnit
|
||||||
import com.unciv.models.ruleset.unit.UnitType
|
import com.unciv.models.ruleset.unit.UnitType
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The immutable properties and mutable game state of an individual unit present on the map
|
* The immutable properties and mutable game state of an individual unit present on the map
|
||||||
@ -115,12 +114,6 @@ class MapUnit {
|
|||||||
var abilityUsedCount: HashMap<String, Int> = hashMapOf()
|
var abilityUsedCount: HashMap<String, Int> = hashMapOf()
|
||||||
var religion: String? = null
|
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
|
//region pure functions
|
||||||
fun clone(): MapUnit {
|
fun clone(): MapUnit {
|
||||||
val toReturn = MapUnit()
|
val toReturn = MapUnit()
|
||||||
@ -212,6 +205,18 @@ class MapUnit {
|
|||||||
return getUniques().any { it.placeholderText == unique }
|
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.
|
* 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
|
* @return Maximum distance of tiles this unit may possibly see
|
||||||
@ -346,16 +351,20 @@ class MapUnit {
|
|||||||
return unit
|
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,
|
// We need to remove the unit from the civ for this check,
|
||||||
// because if the unit requires, say, horses, and so does its upgrade,
|
// because if the unit requires, say, horses, and so does its upgrade,
|
||||||
// and the civ currently has 0 horses,
|
// and the civ currently has 0 horses,
|
||||||
// if we don't remove the unit before the check it's return false!
|
// if we don't remove the unit before the check it's return false!
|
||||||
|
|
||||||
val unitToUpgradeTo = getUnitToUpgradeTo()
|
|
||||||
if (name == unitToUpgradeTo.name) return false
|
if (name == unitToUpgradeTo.name) return false
|
||||||
civInfo.removeUnit(this)
|
civInfo.removeUnit(this)
|
||||||
val canUpgrade = unitToUpgradeTo.isBuildable(civInfo)
|
val canUpgrade =
|
||||||
|
if (ignoreRequired) unitToUpgradeTo.isBuildableIgnoringTechs(civInfo)
|
||||||
|
else unitToUpgradeTo.isBuildable(civInfo)
|
||||||
civInfo.addUnit(this)
|
civInfo.addUnit(this)
|
||||||
return canUpgrade
|
return canUpgrade
|
||||||
}
|
}
|
||||||
@ -678,7 +687,11 @@ class MapUnit {
|
|||||||
// getAncientRuinBonus, if it places a new unit, does too
|
// getAncientRuinBonus, if it places a new unit, does too
|
||||||
currentTile = tile
|
currentTile = tile
|
||||||
|
|
||||||
if (tile.improvement == Constants.ancientRuins && civInfo.isMajorCiv())
|
if (civInfo.isMajorCiv()
|
||||||
|
&& tile.improvement != null
|
||||||
|
&& !tile.improvement!!.startsWith("StartingLocation ")
|
||||||
|
&& tile.getTileImprovement()!!.isAncientRuinsEquivalent()
|
||||||
|
)
|
||||||
getAncientRuinBonus(tile)
|
getAncientRuinBonus(tile)
|
||||||
if (tile.improvement == Constants.barbarianEncampment && !civInfo.isBarbarian())
|
if (tile.improvement == Constants.barbarianEncampment && !civInfo.isBarbarian())
|
||||||
clearEncampment(tile)
|
clearEncampment(tile)
|
||||||
@ -754,114 +767,7 @@ class MapUnit {
|
|||||||
|
|
||||||
private fun getAncientRuinBonus(tile: TileInfo) {
|
private fun getAncientRuinBonus(tile: TileInfo) {
|
||||||
tile.improvement = null
|
tile.improvement = null
|
||||||
val tileBasedRandom = Random(tile.position.toString().hashCode())
|
civInfo.ruinsManager.selectNextRuinsReward(this)
|
||||||
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))()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assignOwner(civInfo: CivilizationInfo, updateCivInfo: Boolean = true) {
|
fun assignOwner(civInfo: CivilizationInfo, updateCivInfo: Boolean = true) {
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package com.unciv.logic.map
|
package com.unciv.logic.map
|
||||||
|
|
||||||
|
import com.unciv.models.ruleset.UniqueTriggerActivation
|
||||||
import com.unciv.models.ruleset.unit.Promotion
|
import com.unciv.models.ruleset.unit.Promotion
|
||||||
|
|
||||||
class UnitPromotions{
|
class UnitPromotions{
|
||||||
@Transient lateinit var unit:MapUnit
|
@Transient lateinit var unit:MapUnit
|
||||||
|
@Suppress("PropertyName")
|
||||||
var XP = 0
|
var XP = 0
|
||||||
var promotions = HashSet<String>()
|
var promotions = HashSet<String>()
|
||||||
// The number of times this unit has been promoted
|
// The number of times this unit has been promoted
|
||||||
@ -40,8 +42,9 @@ class UnitPromotions{
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun doDirectPromotionEffects(promotion: Promotion) {
|
fun doDirectPromotionEffects(promotion: Promotion) {
|
||||||
for (unique in promotion.uniqueObjects.filter { it.placeholderText == "Heal this unit by [] HP"})
|
for (unique in promotion.uniqueObjects) {
|
||||||
unit.healBy(unique.params[0].toInt())
|
UniqueTriggerActivation.triggerUnitwideUnique(unique, unit)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAvailablePromotions(): List<Promotion> {
|
fun getAvailablePromotions(): List<Promotion> {
|
||||||
|
@ -105,13 +105,14 @@ class MapGenerator(val ruleset: Ruleset) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun spreadAncientRuins(map: TileMap) {
|
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
|
return
|
||||||
val suitableTiles = map.values.filter { it.isLand && !it.isImpassible() }
|
val suitableTiles = map.values.filter { it.isLand && !it.isImpassible() }
|
||||||
val locations = randomness.chooseSpreadOutLocations(suitableTiles.size / 50,
|
val locations = randomness.chooseSpreadOutLocations(suitableTiles.size / 50,
|
||||||
suitableTiles, 10)
|
suitableTiles, 10)
|
||||||
for (tile in locations)
|
for (tile in locations)
|
||||||
tile.improvement = Constants.ancientRuins
|
tile.improvement = ruinsEquivalents.keys.random()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun spreadResources(tileMap: TileMap) {
|
private fun spreadResources(tileMap: TileMap) {
|
||||||
|
12
core/src/com/unciv/models/ruleset/RuinReward.kt
Normal file
12
core/src/com/unciv/models/ruleset/RuinReward.kt
Normal 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
|
||||||
|
}
|
@ -52,13 +52,14 @@ class Ruleset {
|
|||||||
|
|
||||||
var name = ""
|
var name = ""
|
||||||
val beliefs = LinkedHashMap<String, Belief>()
|
val beliefs = LinkedHashMap<String, Belief>()
|
||||||
val religions = ArrayList<String>()
|
|
||||||
val buildings = LinkedHashMap<String, Building>()
|
val buildings = LinkedHashMap<String, Building>()
|
||||||
val difficulties = LinkedHashMap<String, Difficulty>()
|
val difficulties = LinkedHashMap<String, Difficulty>()
|
||||||
val eras = LinkedHashMap<String, Era>()
|
val eras = LinkedHashMap<String, Era>()
|
||||||
val nations = LinkedHashMap<String, Nation>()
|
val nations = LinkedHashMap<String, Nation>()
|
||||||
val policies = LinkedHashMap<String, Policy>()
|
val policies = LinkedHashMap<String, Policy>()
|
||||||
val policyBranches = LinkedHashMap<String, PolicyBranch>()
|
val policyBranches = LinkedHashMap<String, PolicyBranch>()
|
||||||
|
val religions = ArrayList<String>()
|
||||||
|
val ruinRewards = LinkedHashMap<String, RuinReward>()
|
||||||
val quests = LinkedHashMap<String, Quest>()
|
val quests = LinkedHashMap<String, Quest>()
|
||||||
val specialists = LinkedHashMap<String, Specialist>()
|
val specialists = LinkedHashMap<String, Specialist>()
|
||||||
val technologies = LinkedHashMap<String, Technology>()
|
val technologies = LinkedHashMap<String, Technology>()
|
||||||
@ -97,6 +98,7 @@ class Ruleset {
|
|||||||
beliefs.putAll(ruleset.beliefs)
|
beliefs.putAll(ruleset.beliefs)
|
||||||
quests.putAll(ruleset.quests)
|
quests.putAll(ruleset.quests)
|
||||||
religions.addAll(ruleset.religions)
|
religions.addAll(ruleset.religions)
|
||||||
|
ruinRewards.putAll(ruleset.ruinRewards)
|
||||||
specialists.putAll(ruleset.specialists)
|
specialists.putAll(ruleset.specialists)
|
||||||
technologies.putAll(ruleset.technologies)
|
technologies.putAll(ruleset.technologies)
|
||||||
for (techToRemove in ruleset.modOptions.techsToRemove) technologies.remove(techToRemove)
|
for (techToRemove in ruleset.modOptions.techsToRemove) technologies.remove(techToRemove)
|
||||||
@ -122,6 +124,7 @@ class Ruleset {
|
|||||||
nations.clear()
|
nations.clear()
|
||||||
policies.clear()
|
policies.clear()
|
||||||
religions.clear()
|
religions.clear()
|
||||||
|
ruinRewards.clear()
|
||||||
quests.clear()
|
quests.clear()
|
||||||
technologies.clear()
|
technologies.clear()
|
||||||
terrains.clear()
|
terrains.clear()
|
||||||
@ -210,6 +213,10 @@ class Ruleset {
|
|||||||
if (religionsFile.exists())
|
if (religionsFile.exists())
|
||||||
religions += jsonParser.getFromJson(Array<String>::class.java, religionsFile).toList()
|
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")
|
val nationsFile = folderHandle.child("Nations.json")
|
||||||
if (nationsFile.exists()) {
|
if (nationsFile.exists()) {
|
||||||
nations += createHashmap(jsonParser.getFromJson(Array<Nation>::class.java, nationsFile))
|
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
|
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()) {
|
if (newRuleset.unitTypes.isEmpty()) {
|
||||||
newRuleset.unitTypes.putAll(getBaseRuleset().unitTypes)
|
newRuleset.unitTypes.putAll(getBaseRuleset().unitTypes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This one should be permanent
|
||||||
|
if (newRuleset.ruinRewards.isEmpty()) {
|
||||||
|
newRuleset.ruinRewards.putAll(getBaseRuleset().ruinRewards)
|
||||||
|
}
|
||||||
|
|
||||||
return newRuleset
|
return newRuleset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
package com.unciv.models.ruleset
|
package com.unciv.models.ruleset
|
||||||
|
|
||||||
|
import com.badlogic.gdx.math.Vector2
|
||||||
import com.unciv.logic.city.CityInfo
|
import com.unciv.logic.city.CityInfo
|
||||||
import com.unciv.logic.civilization.CivFlags
|
import com.unciv.logic.civilization.*
|
||||||
import com.unciv.logic.civilization.CivilizationInfo
|
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.stats.Stats
|
||||||
|
import com.unciv.models.translations.fillPlaceholders
|
||||||
import com.unciv.models.translations.getPlaceholderParameters
|
import com.unciv.models.translations.getPlaceholderParameters
|
||||||
import com.unciv.models.translations.getPlaceholderText
|
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){
|
class Unique(val text:String){
|
||||||
val placeholderText = text.getPlaceholderText()
|
val placeholderText = text.getPlaceholderText()
|
||||||
@ -34,83 +41,285 @@ class UniqueMap:HashMap<String, ArrayList<Unique>>() {
|
|||||||
fun getAllUniques() = this.asSequence().flatMap { it.value.asSequence() }
|
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 {
|
object UniqueTriggerActivation {
|
||||||
fun triggerCivwideUnique(unique: Unique, civInfo: CivilizationInfo, cityInfo: CityInfo? = null) {
|
/** @return boolean whether an action was successfully preformed */
|
||||||
val chosenCity = if (cityInfo != null) cityInfo else civInfo.cities.firstOrNull { it.isCapital() }
|
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) {
|
when (unique.placeholderText) {
|
||||||
"Free [] appears" -> {
|
"Free [] appears" -> {
|
||||||
val unitName = unique.params[0]
|
val unitName = unique.params[0]
|
||||||
val unit = civInfo.gameInfo.ruleSet.units[unitName]
|
val unit = civInfo.gameInfo.ruleSet.units[unitName]
|
||||||
if (chosenCity != null && unit != null && (!unit.uniques.contains("Founds a new city") || !civInfo.isOneCityChallenger()))
|
if (chosenCity == null || unit == null || (unit.uniques.contains("Founds a new city") && civInfo.isOneCityChallenger()))
|
||||||
civInfo.addUnit(unitName, chosenCity)
|
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" -> {
|
"[] free [] units appear" -> {
|
||||||
val unitName = unique.params[1]
|
val unitName = unique.params[1]
|
||||||
val unit = civInfo.gameInfo.ruleSet.units[unitName]
|
val unit = civInfo.gameInfo.ruleSet.units[unitName]
|
||||||
if (chosenCity != null && unit != null && (!unit.uniques.contains("Founds a new city") || !civInfo.isOneCityChallenger()))
|
if (chosenCity == null || unit == null || (unit.uniques.contains("Founds a new city") && civInfo.isOneCityChallenger()))
|
||||||
for (i in 1..unique.params[0].toInt())
|
return false
|
||||||
civInfo.addUnit(unitName, chosenCity)
|
|
||||||
|
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" -> {
|
"Free Great Person" -> {
|
||||||
if (civInfo.isSpectator()) return
|
if (civInfo.isSpectator()) return false
|
||||||
if (civInfo.isPlayerCivilization()) civInfo.greatPeople.freeGreatPeople++
|
if (civInfo.isPlayerCivilization()) {
|
||||||
else {
|
civInfo.greatPeople.freeGreatPeople++
|
||||||
|
if (notification != null)
|
||||||
|
civInfo.addNotification(notification) // Anyone an idea for a good icon?
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
val greatPeople = civInfo.getGreatPeople()
|
val greatPeople = civInfo.getGreatPeople()
|
||||||
if (greatPeople.isEmpty()) return
|
if (greatPeople.isEmpty()) return false
|
||||||
var greatPerson = civInfo.getGreatPeople().random()
|
var greatPerson = civInfo.getGreatPeople().random()
|
||||||
|
|
||||||
val preferredVictoryType = civInfo.victoryType()
|
val preferredVictoryType = civInfo.victoryType()
|
||||||
if (preferredVictoryType == VictoryType.Cultural) {
|
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 (culturalGP != null) greatPerson = culturalGP
|
||||||
}
|
}
|
||||||
if (preferredVictoryType == VictoryType.Scientific) {
|
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
|
if (scientificGP != null) greatPerson = scientificGP
|
||||||
}
|
}
|
||||||
|
|
||||||
civInfo.addUnit(greatPerson.name, chosenCity)
|
return civInfo.addUnit(greatPerson.name, chosenCity) != null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Deprecated since 3.15.4
|
// Deprecated since 3.15.4
|
||||||
"+1 population in each city" ->
|
"+1 population in each city" -> {
|
||||||
for (city in civInfo.cities) {
|
for (city in civInfo.cities) {
|
||||||
city.population.addPopulation(1)
|
city.population.addPopulation(1)
|
||||||
}
|
}
|
||||||
|
if (notification != null) {
|
||||||
|
civInfo.addNotification(
|
||||||
|
notification,
|
||||||
|
LocationAction(civInfo.cities.map { it.location }),
|
||||||
|
NotificationIcon.Population
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
//
|
//
|
||||||
"[] population []" ->
|
"[] population []" -> {
|
||||||
|
val citiesWithPopulationChanged: MutableList<Vector2> = mutableListOf()
|
||||||
for (city in civInfo.cities) {
|
for (city in civInfo.cities) {
|
||||||
if (city.matchesFilter(unique.params[1])) {
|
if (city.matchesFilter(unique.params[1])) {
|
||||||
city.population.addPopulation(unique.params[0].toInt())
|
city.population.addPopulation(unique.params[0].toInt())
|
||||||
|
citiesWithPopulationChanged.add(city.location)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"Free Technology" -> if (!civInfo.isSpectator()) civInfo.tech.freeTechs += 1
|
if (notification != null && citiesWithPopulationChanged.isNotEmpty())
|
||||||
"[] Free Technologies" -> if (!civInfo.isSpectator()) civInfo.tech.freeTechs += unique.params[0].toInt()
|
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
|
||||||
|
}
|
||||||
|
|
||||||
"Quantity of strategic resources produced by the empire increased by 100%" -> civInfo.updateDetailedCivResources()
|
"Free Technology" -> {
|
||||||
"+[]% attack strength to all [] Units for [] turns" -> civInfo.temporaryUniques.add(Pair(unique, unique.params[2].toInt()))
|
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
|
||||||
|
|
||||||
"Reveals the entire map" -> civInfo.exploredTiles.addAll(civInfo.gameInfo.tileMap.values.asSequence().map { it.position })
|
val techsToResearch = researchableTechsFromThatEra.shuffled(tileBasedRandom)
|
||||||
|
.take(unique.params[0].toInt())
|
||||||
|
for (tech in techsToResearch)
|
||||||
|
civInfo.tech.addTechnology(tech.name)
|
||||||
|
|
||||||
|
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" -> {
|
"[] units gain the [] promotion" -> {
|
||||||
val filter = unique.params[0]
|
val filter = unique.params[0]
|
||||||
val promotion = unique.params[1]
|
val promotion = unique.params[1]
|
||||||
for (unit in civInfo.getCivUnits())
|
|
||||||
|
val promotedUnitLocations: MutableList<Vector2> = mutableListOf()
|
||||||
|
for (unit in civInfo.getCivUnits()) {
|
||||||
if (unit.matchesFilter(filter)
|
if (unit.matchesFilter(filter)
|
||||||
|| civInfo.gameInfo.ruleSet.unitPromotions.values.any {
|
&& civInfo.gameInfo.ruleSet.unitPromotions.values.any {
|
||||||
it.name == promotion && unit.type!!.name in it.unitTypes
|
it.name == promotion && unit.type.name in it.unitTypes
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
unit.promotions.addPromotion(promotion, isFree = true)
|
unit.promotions.addPromotion(promotion, isFree = true)
|
||||||
|
promotedUnitLocations.add(unit.getTile().position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"Allied City-States will occasionally gift Great People" ->
|
|
||||||
civInfo.addFlag(CivFlags.CityStateGreatPersonGift.name, civInfo.turnsForGreatPersonFromCityState() / 2)
|
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
|
||||||
|
}
|
||||||
// The mechanics for granting great people are wonky, but basically the following happens:
|
// 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
|
// 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
|
// 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
|
// Note that the way this is implemented now, this unique does NOT stack
|
||||||
// I could parametrize the [Allied], but eh.
|
// 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)
|
for (civ in civInfo.gameInfo.civilizations)
|
||||||
if (!civ.isBarbarian() && !civ.isSpectator())
|
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
|
||||||
|
}
|
||||||
}
|
}
|
@ -75,6 +75,7 @@ class TileImprovement : NamedStats(), ICivilopediaText {
|
|||||||
fun hasUnique(unique: String) = uniques.contains(unique)
|
fun hasUnique(unique: String) = uniques.contains(unique)
|
||||||
fun isGreatImprovement() = hasUnique("Great Improvement")
|
fun isGreatImprovement() = hasUnique("Great Improvement")
|
||||||
fun isRoad() = RoadStatus.values().any { it != RoadStatus.None && it.name == this.name }
|
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?
|
* Check: Is this improvement allowed on a [given][name] terrain feature?
|
||||||
|
@ -45,6 +45,12 @@ class BaseUnit : INamed, INonPerpetualConstruction, CivilopediaText() {
|
|||||||
var promotions = HashSet<String>()
|
var promotions = HashSet<String>()
|
||||||
var obsoleteTech: String? = null
|
var obsoleteTech: String? = null
|
||||||
var upgradesTo: 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 replaces: String? = null
|
||||||
var uniqueTo: String? = null
|
var uniqueTo: String? = null
|
||||||
var attackSound: String? = null
|
var attackSound: String? = null
|
||||||
@ -262,10 +268,14 @@ class BaseUnit : INamed, INonPerpetualConstruction, CivilopediaText() {
|
|||||||
return ""
|
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 (uniques.contains("Unbuildable")) return "Unbuildable"
|
||||||
if (requiredTech != null && !civInfo.tech.isResearched(requiredTech!!)) return "$requiredTech not researched"
|
if (!ignoreTechPolicyRequirements && requiredTech != null && !civInfo.tech.isResearched(requiredTech!!)) return "$requiredTech not researched"
|
||||||
if (obsoleteTech != null && civInfo.tech.isResearched(obsoleteTech!!)) return "Obsolete by $obsoleteTech"
|
if (!ignoreTechPolicyRequirements && obsoleteTech != null && civInfo.tech.isResearched(obsoleteTech!!)) return "Obsolete by $obsoleteTech"
|
||||||
if (uniqueTo != null && uniqueTo != civInfo.civName) return "Unique to $uniqueTo"
|
if (uniqueTo != null && uniqueTo != civInfo.civName) return "Unique to $uniqueTo"
|
||||||
if (civInfo.gameInfo.ruleSet.units.values.any { it.uniqueTo == civInfo.civName && it.replaces == name })
|
if (civInfo.gameInfo.ruleSet.units.values.any { it.uniqueTo == civInfo.civName && it.replaces == name })
|
||||||
return "Our unique unit replaces this"
|
return "Our unique unit replaces this"
|
||||||
@ -279,9 +289,9 @@ class BaseUnit : INamed, INonPerpetualConstruction, CivilopediaText() {
|
|||||||
|
|
||||||
for (unique in uniqueObjects.filter { it.placeholderText == "Requires []" }) {
|
for (unique in uniqueObjects.filter { it.placeholderText == "Requires []" }) {
|
||||||
val filter = unique.params[0]
|
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
|
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())
|
for ((resource, amount) in getResourceRequirements())
|
||||||
@ -301,6 +311,12 @@ class BaseUnit : INamed, INonPerpetualConstruction, CivilopediaText() {
|
|||||||
return getRejectionReason(cityConstructions) == ""
|
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 {
|
override fun postBuildEvent(cityConstructions: CityConstructions, wasBought: Boolean): Boolean {
|
||||||
val civInfo = cityConstructions.cityInfo.civInfo
|
val civInfo = cityConstructions.cityInfo.civInfo
|
||||||
val unit = civInfo.placeUnitNearTile(cityConstructions.cityInfo.location, name)
|
val unit = civInfo.placeUnitNearTile(cityConstructions.cityInfo.location, name)
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
package com.unciv.models.stats
|
package com.unciv.models.stats
|
||||||
|
|
||||||
|
import com.unciv.logic.civilization.NotificationIcon
|
||||||
import com.unciv.models.UncivSound
|
import com.unciv.models.UncivSound
|
||||||
|
|
||||||
enum class Stat (val sound: UncivSound) {
|
enum class Stat(val notificationIcon: String, val purchaseSound: UncivSound) {
|
||||||
Production(UncivSound.Click),
|
Production(NotificationIcon.Production, UncivSound.Click),
|
||||||
Food(UncivSound.Click),
|
Food(NotificationIcon.Food, UncivSound.Click),
|
||||||
Gold(UncivSound.Coin),
|
Gold(NotificationIcon.Gold, UncivSound.Coin),
|
||||||
Science(UncivSound.Chimes),
|
Science(NotificationIcon.Science, UncivSound.Chimes),
|
||||||
Culture(UncivSound.Paper),
|
Culture(NotificationIcon.Culture, UncivSound.Paper),
|
||||||
Happiness(UncivSound.Click),
|
Happiness(NotificationIcon.Happiness, UncivSound.Click),
|
||||||
Faith(UncivSound.Choir),
|
Faith(NotificationIcon.Faith, UncivSound.Choir);
|
||||||
}
|
}
|
@ -276,6 +276,8 @@ fun String.equalsPlaceholderText(str:String): Boolean {
|
|||||||
return this.getPlaceholderText() == str
|
return this.getPlaceholderText() == str
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun String.hasPlaceholderParameters() = squareBraceRegex.containsMatchIn(this)
|
||||||
|
|
||||||
fun String.getPlaceholderParameters() = squareBraceRegex.findAll(this).map { it.groups[1]!!.value }.toList()
|
fun String.getPlaceholderParameters() = squareBraceRegex.findAll(this).map { it.groups[1]!!.value }.toList()
|
||||||
|
|
||||||
/** Substitutes placeholders with [strings], respecting order of appearance. */
|
/** Substitutes placeholders with [strings], respecting order of appearance. */
|
||||||
|
@ -419,7 +419,7 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase
|
|||||||
button.setText("Buy".tr() + " " + constructionBuyCost)
|
button.setText("Buy".tr() + " " + constructionBuyCost)
|
||||||
button.add(ImageGetter.getStatIcon(stat.name)).size(20f).padBottom(2f)
|
button.add(ImageGetter.getStatIcon(stat.name)).size(20f).padBottom(2f)
|
||||||
|
|
||||||
button.onClick(stat.sound) {
|
button.onClick(stat.purchaseSound) {
|
||||||
button.disable()
|
button.disable()
|
||||||
cityScreen.closeAllPopups()
|
cityScreen.closeAllPopups()
|
||||||
|
|
||||||
|
@ -182,6 +182,7 @@ class NewGameScreen(
|
|||||||
try {
|
try {
|
||||||
newGame = GameStarter.startNewGame(gameSetupInfo)
|
newGame = GameStarter.startNewGame(gameSetupInfo)
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
|
exception.printStackTrace()
|
||||||
Gdx.app.postRunnable {
|
Gdx.app.postRunnable {
|
||||||
Popup(this).apply {
|
Popup(this).apply {
|
||||||
addGoodSizedLabel("It looks like we can't make a map with the parameters you requested!".tr()).row()
|
addGoodSizedLabel("It looks like we can't make a map with the parameters you requested!".tr()).row()
|
||||||
|
@ -492,7 +492,7 @@ class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap
|
|||||||
for (tile in allWorldTileGroups) {
|
for (tile in allWorldTileGroups) {
|
||||||
if (tile.icons.populationIcon != null) tile.icons.populationIcon!!.color.a = fadeout
|
if (tile.icons.populationIcon != null) tile.icons.populationIcon!!.color.a = fadeout
|
||||||
if (tile.icons.improvementIcon != null && tile.tileInfo.improvement != Constants.barbarianEncampment
|
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
|
tile.icons.improvementIcon!!.color.a = fadeout
|
||||||
if (tile.resourceImage != null) tile.resourceImage!!.color.a = fadeout
|
if (tile.resourceImage != null) tile.resourceImage!!.color.a = fadeout
|
||||||
}
|
}
|
||||||
|
@ -298,11 +298,13 @@ object UnitActions {
|
|||||||
if (upgradeAction != null) actionList += upgradeAction
|
if (upgradeAction != null) actionList += upgradeAction
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUpgradeAction(unit: MapUnit): UnitAction? {
|
fun getUpgradeAction(unit: MapUnit, isFree: Boolean = false): UnitAction? {
|
||||||
val tile = unit.currentTile
|
val tile = unit.currentTile
|
||||||
if (unit.baseUnit().upgradesTo == null || tile.getOwner() != unit.civInfo
|
if (unit.baseUnit().upgradesTo == null || !unit.canUpgrade()) return null
|
||||||
|| !unit.canUpgrade()) return null
|
if (tile.getOwner() != unit.civInfo && !isFree) return null
|
||||||
val goldCostOfUpgrade = unit.getCostOfUpgrade()
|
val goldCostOfUpgrade =
|
||||||
|
if (isFree) 0
|
||||||
|
else unit.getCostOfUpgrade()
|
||||||
val upgradedUnit = unit.getUnitToUpgradeTo()
|
val upgradedUnit = unit.getUnitToUpgradeTo()
|
||||||
|
|
||||||
return UnitAction(UnitActionType.Upgrade,
|
return UnitAction(UnitActionType.Upgrade,
|
||||||
@ -312,21 +314,43 @@ object UnitActions {
|
|||||||
val unitTile = unit.getTile()
|
val unitTile = unit.getTile()
|
||||||
unit.destroy()
|
unit.destroy()
|
||||||
val newUnit = unit.civInfo.placeUnitNearTile(unitTile.position, upgradedUnit.name)!!
|
val newUnit = unit.civInfo.placeUnitNearTile(unitTile.position, upgradedUnit.name)!!
|
||||||
newUnit.health = unit.health
|
unit.copyStatisticsTo(newUnit)
|
||||||
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
|
newUnit.currentMovement = 0f
|
||||||
}.takeIf {
|
}.takeIf {
|
||||||
|
isFree ||
|
||||||
|
(
|
||||||
unit.civInfo.gold >= goldCostOfUpgrade && !unit.isEmbarked()
|
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) {
|
private fun addBuildingImprovementsAction(unit: MapUnit, actionList: ArrayList<UnitAction>, tile: TileInfo, worldScreen: WorldScreen, unitTable: UnitTable) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user