mirror of
https://github.com/yairm210/Unciv.git
synced 2025-01-10 23:37:31 +07: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"
|
||||
},
|
||||
|
||||
{ "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"}]
|
||||
}
|
||||
]
|
||||
|
@ -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."}
|
||||
|
@ -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 =
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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()
|
||||
|
@ -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.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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
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 = ""
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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?
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
@ -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. */
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user