mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-14 09:48:12 +07:00
Events! (#11276)
* Events! * Whoops * Resolved #11277 - "Upon turn end" also works for unit uniques
This commit is contained in:
@ -20,7 +20,8 @@ enum class AlertType : IsPartOfGameInfoSerialization {
|
|||||||
BulliedProtectedMinor,
|
BulliedProtectedMinor,
|
||||||
AttackedProtectedMinor,
|
AttackedProtectedMinor,
|
||||||
RecapturedCivilian,
|
RecapturedCivilian,
|
||||||
GameHasBeenWon
|
GameHasBeenWon,
|
||||||
|
Event
|
||||||
}
|
}
|
||||||
|
|
||||||
class PopupAlert : IsPartOfGameInfoSerialization {
|
class PopupAlert : IsPartOfGameInfoSerialization {
|
||||||
|
@ -12,6 +12,10 @@ class UnitTurnManager(val unit: MapUnit) {
|
|||||||
|
|
||||||
fun endTurn() {
|
fun endTurn() {
|
||||||
unit.movement.clearPathfindingCache()
|
unit.movement.clearPathfindingCache()
|
||||||
|
|
||||||
|
for (unique in unit.getTriggeredUniques(UniqueType.TriggerUponTurnEnd))
|
||||||
|
UniqueTriggerActivation.triggerUnique(unique, unit)
|
||||||
|
|
||||||
if (unit.currentMovement > 0
|
if (unit.currentMovement > 0
|
||||||
&& unit.getTile().improvementInProgress != null
|
&& unit.getTile().improvementInProgress != null
|
||||||
&& unit.canBuildImprovement(unit.getTile().getTileImprovementInProgress()!!)
|
&& unit.canBuildImprovement(unit.getTile().getTileImprovementInProgress()!!)
|
||||||
|
35
core/src/com/unciv/models/ruleset/Event.kt
Normal file
35
core/src/com/unciv/models/ruleset/Event.kt
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package com.unciv.models.ruleset
|
||||||
|
|
||||||
|
import com.unciv.logic.civilization.Civilization
|
||||||
|
import com.unciv.models.ruleset.unique.Conditionals
|
||||||
|
import com.unciv.models.ruleset.unique.StateForConditionals
|
||||||
|
import com.unciv.models.ruleset.unique.Unique
|
||||||
|
import com.unciv.models.ruleset.unique.UniqueTriggerActivation
|
||||||
|
import com.unciv.models.stats.INamed
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Event : INamed {
|
||||||
|
|
||||||
|
override var name = ""
|
||||||
|
var text = ""
|
||||||
|
// todo: add unrepeatable events
|
||||||
|
|
||||||
|
var choices = ArrayList<EventChoice>()
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventChoice {
|
||||||
|
var text = ""
|
||||||
|
var triggeredUniques = ArrayList<String>()
|
||||||
|
val triggerredUniqueObjects by lazy { triggeredUniques.map { Unique(it) } }
|
||||||
|
|
||||||
|
var conditions = ArrayList<String>()
|
||||||
|
val conditionObjects by lazy { conditions.map { Unique(it) } }
|
||||||
|
fun matchesConditions(stateForConditionals: StateForConditionals) =
|
||||||
|
conditionObjects.all { Conditionals.conditionalApplies(null, it, stateForConditionals) }
|
||||||
|
|
||||||
|
fun triggerChoice(civ: Civilization) {
|
||||||
|
for (unique in triggerredUniqueObjects)
|
||||||
|
UniqueTriggerActivation.triggerUnique(unique, civ)
|
||||||
|
}
|
||||||
|
}
|
@ -71,6 +71,7 @@ class Ruleset {
|
|||||||
var victories = LinkedHashMap<String, Victory>()
|
var victories = LinkedHashMap<String, Victory>()
|
||||||
var cityStateTypes = LinkedHashMap<String, CityStateType>()
|
var cityStateTypes = LinkedHashMap<String, CityStateType>()
|
||||||
val personalities = LinkedHashMap<String, Personality>()
|
val personalities = LinkedHashMap<String, Personality>()
|
||||||
|
val events = LinkedHashMap<String, Event>()
|
||||||
|
|
||||||
val greatGeneralUnits by lazy {
|
val greatGeneralUnits by lazy {
|
||||||
units.values.filter { it.hasUnique(UniqueType.GreatPersonFromCombat, StateForConditionals.IgnoreConditionals) }
|
units.values.filter { it.hasUnique(UniqueType.GreatPersonFromCombat, StateForConditionals.IgnoreConditionals) }
|
||||||
@ -162,6 +163,7 @@ class Ruleset {
|
|||||||
}
|
}
|
||||||
units.putAll(ruleset.units)
|
units.putAll(ruleset.units)
|
||||||
personalities.putAll(ruleset.personalities)
|
personalities.putAll(ruleset.personalities)
|
||||||
|
events.putAll(ruleset.events)
|
||||||
modOptions.uniques.addAll(ruleset.modOptions.uniques)
|
modOptions.uniques.addAll(ruleset.modOptions.uniques)
|
||||||
modOptions.constants.merge(ruleset.modOptions.constants)
|
modOptions.constants.merge(ruleset.modOptions.constants)
|
||||||
|
|
||||||
@ -196,6 +198,7 @@ class Ruleset {
|
|||||||
victories.clear()
|
victories.clear()
|
||||||
cityStateTypes.clear()
|
cityStateTypes.clear()
|
||||||
personalities.clear()
|
personalities.clear()
|
||||||
|
events.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun allRulesetObjects(): Sequence<IRulesetObject> =
|
fun allRulesetObjects(): Sequence<IRulesetObject> =
|
||||||
@ -384,6 +387,11 @@ class Ruleset {
|
|||||||
personalities += createHashmap(json().fromJsonFile(Array<Personality>::class.java, personalitiesFile))
|
personalities += createHashmap(json().fromJsonFile(Array<Personality>::class.java, personalitiesFile))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val eventsFile = folderHandle.child("Events.json")
|
||||||
|
if (eventsFile.exists()) {
|
||||||
|
events += createHashmap(json().fromJsonFile(Array<Event>::class.java, eventsFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Add objects that might not be present in base ruleset mods, but are required
|
// Add objects that might not be present in base ruleset mods, but are required
|
||||||
|
@ -12,7 +12,7 @@ import kotlin.random.Random
|
|||||||
object Conditionals {
|
object Conditionals {
|
||||||
|
|
||||||
fun conditionalApplies(
|
fun conditionalApplies(
|
||||||
unique: Unique,
|
unique: Unique?,
|
||||||
condition: Unique,
|
condition: Unique,
|
||||||
state: StateForConditionals
|
state: StateForConditionals
|
||||||
): Boolean {
|
): Boolean {
|
||||||
@ -270,14 +270,16 @@ object Conditionals {
|
|||||||
UniqueType.ConditionalInRegionExceptOfType -> state.region?.type != condition.params[0]
|
UniqueType.ConditionalInRegionExceptOfType -> state.region?.type != condition.params[0]
|
||||||
|
|
||||||
UniqueType.ConditionalFirstCivToResearch ->
|
UniqueType.ConditionalFirstCivToResearch ->
|
||||||
unique.sourceObjectType == UniqueTarget.Tech
|
unique != null
|
||||||
|
&& unique.sourceObjectType == UniqueTarget.Tech
|
||||||
&& checkOnGameInfo { civilizations.none {
|
&& checkOnGameInfo { civilizations.none {
|
||||||
it != relevantCiv && it.isMajorCiv()
|
it != relevantCiv && it.isMajorCiv()
|
||||||
&& it.tech.isResearched(unique.sourceObjectName!!) // guarded by the sourceObjectType check
|
&& it.tech.isResearched(unique.sourceObjectName!!) // guarded by the sourceObjectType check
|
||||||
} }
|
} }
|
||||||
|
|
||||||
UniqueType.ConditionalFirstCivToAdopt ->
|
UniqueType.ConditionalFirstCivToAdopt ->
|
||||||
unique.sourceObjectType == UniqueTarget.Policy
|
unique != null
|
||||||
|
&& unique.sourceObjectType == UniqueTarget.Policy
|
||||||
&& checkOnGameInfo { civilizations.none {
|
&& checkOnGameInfo { civilizations.none {
|
||||||
it != relevantCiv && it.isMajorCiv()
|
it != relevantCiv && it.isMajorCiv()
|
||||||
&& it.policies.isAdopted(unique.sourceObjectName!!) // guarded by the sourceObjectType check
|
&& it.policies.isAdopted(unique.sourceObjectName!!) // guarded by the sourceObjectType check
|
||||||
|
@ -509,13 +509,23 @@ enum class UniqueParameterType(
|
|||||||
// Used in FreeExtraBeliefs, FreeExtraAnyBeliefs
|
// Used in FreeExtraBeliefs, FreeExtraAnyBeliefs
|
||||||
private val knownValues = setOf("founding", "enhancing")
|
private val knownValues = setOf("founding", "enhancing")
|
||||||
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
|
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
|
||||||
UniqueType.UniqueParameterErrorSeverity? = when (parameterText) {
|
UniqueType.UniqueParameterErrorSeverity? = when (parameterText) {
|
||||||
in knownValues -> null
|
in knownValues -> null
|
||||||
else -> UniqueType.UniqueParameterErrorSeverity.RulesetInvariant
|
else -> UniqueType.UniqueParameterErrorSeverity.RulesetInvariant
|
||||||
}
|
}
|
||||||
override fun getTranslationWriterStringsForOutput() = knownValues
|
override fun getTranslationWriterStringsForOutput() = knownValues
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** [UniqueType.ConditionalTech] and others, no central implementation */
|
||||||
|
Event("event", "Inspiration", "The name of any event") {
|
||||||
|
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
|
||||||
|
UniqueType.UniqueParameterErrorSeverity? = when (parameterText) {
|
||||||
|
in ruleset.events -> null
|
||||||
|
else -> UniqueType.UniqueParameterErrorSeverity.RulesetSpecific
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
/** [UniqueType.ConditionalTech] and others, no central implementation */
|
/** [UniqueType.ConditionalTech] and others, no central implementation */
|
||||||
Technology("tech", "Agriculture", "The name of any tech") {
|
Technology("tech", "Agriculture", "The name of any tech") {
|
||||||
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
|
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
|
||||||
|
@ -5,6 +5,7 @@ import com.unciv.Constants
|
|||||||
import com.unciv.UncivGame
|
import com.unciv.UncivGame
|
||||||
import com.unciv.logic.automation.civilization.NextTurnAutomation
|
import com.unciv.logic.automation.civilization.NextTurnAutomation
|
||||||
import com.unciv.logic.city.City
|
import com.unciv.logic.city.City
|
||||||
|
import com.unciv.logic.civilization.AlertType
|
||||||
import com.unciv.logic.civilization.CivFlags
|
import com.unciv.logic.civilization.CivFlags
|
||||||
import com.unciv.logic.civilization.Civilization
|
import com.unciv.logic.civilization.Civilization
|
||||||
import com.unciv.logic.civilization.LocationAction
|
import com.unciv.logic.civilization.LocationAction
|
||||||
@ -13,6 +14,7 @@ import com.unciv.logic.civilization.NotificationAction
|
|||||||
import com.unciv.logic.civilization.NotificationCategory
|
import com.unciv.logic.civilization.NotificationCategory
|
||||||
import com.unciv.logic.civilization.NotificationIcon
|
import com.unciv.logic.civilization.NotificationIcon
|
||||||
import com.unciv.logic.civilization.PolicyAction
|
import com.unciv.logic.civilization.PolicyAction
|
||||||
|
import com.unciv.logic.civilization.PopupAlert
|
||||||
import com.unciv.logic.civilization.TechAction
|
import com.unciv.logic.civilization.TechAction
|
||||||
import com.unciv.logic.civilization.managers.ReligionState
|
import com.unciv.logic.civilization.managers.ReligionState
|
||||||
import com.unciv.logic.map.mapgenerator.NaturalWonderGenerator
|
import com.unciv.logic.map.mapgenerator.NaturalWonderGenerator
|
||||||
@ -87,7 +89,9 @@ object UniqueTriggerActivation {
|
|||||||
return { civInfo.temporaryUniques.add(TemporaryUnique(unique, timingConditional.params[0].toInt())) }
|
return { civInfo.temporaryUniques.add(TemporaryUnique(unique, timingConditional.params[0].toInt())) }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!unique.conditionalsApply(StateForConditionals(civInfo, city, unit, tile))) return null
|
val stateForConditionals = StateForConditionals(civInfo, city, unit, tile)
|
||||||
|
|
||||||
|
if (!unique.conditionalsApply(stateForConditionals)) return null
|
||||||
|
|
||||||
val chosenCity = relevantCity ?:
|
val chosenCity = relevantCity ?:
|
||||||
civInfo.cities.firstOrNull { it.isCapital() }
|
civInfo.cities.firstOrNull { it.isCapital() }
|
||||||
@ -98,6 +102,17 @@ object UniqueTriggerActivation {
|
|||||||
val ruleSet = civInfo.gameInfo.ruleset
|
val ruleSet = civInfo.gameInfo.ruleset
|
||||||
|
|
||||||
when (unique.type) {
|
when (unique.type) {
|
||||||
|
UniqueType.TriggerEvent -> {
|
||||||
|
val event = ruleSet.events[unique.params[0]] ?: return null
|
||||||
|
val choices = event.choices.filter { it.matchesConditions(stateForConditionals) }
|
||||||
|
if (choices.isEmpty()) return null
|
||||||
|
return {
|
||||||
|
if (civInfo.isAI()) choices.random().triggerChoice(civInfo)
|
||||||
|
else civInfo.popupAlerts.add(PopupAlert(AlertType.Event, event.name))
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
UniqueType.OneTimeFreeUnit -> {
|
UniqueType.OneTimeFreeUnit -> {
|
||||||
val unitName = unique.params[0]
|
val unitName = unique.params[0]
|
||||||
val baseUnit = ruleSet.units[unitName] ?: return null
|
val baseUnit = ruleSet.units[unitName] ?: return null
|
||||||
@ -950,6 +965,7 @@ object UniqueTriggerActivation {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> return null
|
else -> return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -772,6 +772,7 @@ enum class UniqueType(
|
|||||||
UnitsGainPromotion("[mapUnitFilter] units gain the [promotion] promotion", UniqueTarget.Triggerable), // Not used in Vanilla
|
UnitsGainPromotion("[mapUnitFilter] units gain the [promotion] promotion", UniqueTarget.Triggerable), // Not used in Vanilla
|
||||||
FreeStatBuildings("Provides the cheapest [stat] building in your first [positiveAmount] cities for free", UniqueTarget.Triggerable), // used in Policy
|
FreeStatBuildings("Provides the cheapest [stat] building in your first [positiveAmount] cities for free", UniqueTarget.Triggerable), // used in Policy
|
||||||
FreeSpecificBuildings("Provides a [buildingName] in your first [positiveAmount] cities for free", UniqueTarget.Triggerable), // used in Policy
|
FreeSpecificBuildings("Provides a [buildingName] in your first [positiveAmount] cities for free", UniqueTarget.Triggerable), // used in Policy
|
||||||
|
TriggerEvent("Triggers a [event] event", UniqueTarget.Triggerable),
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import com.unciv.logic.civilization.PopupAlert
|
|||||||
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
|
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
|
||||||
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
|
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
|
||||||
import com.unciv.logic.civilization.diplomacy.RelationshipLevel
|
import com.unciv.logic.civilization.diplomacy.RelationshipLevel
|
||||||
|
import com.unciv.models.ruleset.unique.StateForConditionals
|
||||||
import com.unciv.models.ruleset.unique.UniqueType
|
import com.unciv.models.ruleset.unique.UniqueType
|
||||||
import com.unciv.models.translations.fillPlaceholders
|
import com.unciv.models.translations.fillPlaceholders
|
||||||
import com.unciv.models.translations.tr
|
import com.unciv.models.translations.tr
|
||||||
@ -103,6 +104,7 @@ class AlertPopup(
|
|||||||
AlertType.BulliedProtectedMinor, AlertType.AttackedProtectedMinor -> addBulliedOrAttackedProtectedMinor()
|
AlertType.BulliedProtectedMinor, AlertType.AttackedProtectedMinor -> addBulliedOrAttackedProtectedMinor()
|
||||||
AlertType.RecapturedCivilian -> skipThisAlert = addRecapturedCivilian()
|
AlertType.RecapturedCivilian -> skipThisAlert = addRecapturedCivilian()
|
||||||
AlertType.GameHasBeenWon -> addGameHasBeenWon()
|
AlertType.GameHasBeenWon -> addGameHasBeenWon()
|
||||||
|
AlertType.Event -> skipThisAlert = !addEvent()
|
||||||
}
|
}
|
||||||
if (!skipThisAlert) open()
|
if (!skipThisAlert) open()
|
||||||
}
|
}
|
||||||
@ -497,6 +499,24 @@ class AlertPopup(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns if event was triggered correctly */
|
||||||
|
private fun addEvent(): Boolean {
|
||||||
|
val event = gameInfo.ruleset.events[popupAlert.value] ?: return false
|
||||||
|
|
||||||
|
val civ = gameInfo.currentPlayerCiv
|
||||||
|
val choices = event.choices.filter { it.matchesConditions(StateForConditionals(civ)) }
|
||||||
|
if (choices.isEmpty()) return false
|
||||||
|
|
||||||
|
addGoodSizedLabel(event.text)
|
||||||
|
for (choice in choices){
|
||||||
|
addSeparator()
|
||||||
|
add(choice.text.toTextButton().onActivation { close(); choice.triggerChoice(civ) }).row()
|
||||||
|
for (triggeredUnique in choice.triggeredUniques)
|
||||||
|
addGoodSizedLabel(triggeredUnique)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
|
@ -159,6 +159,11 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
|
|||||||
|
|
||||||
Applicable to: Triggerable
|
Applicable to: Triggerable
|
||||||
|
|
||||||
|
??? example "Triggers a [event] event"
|
||||||
|
Example: "Triggers a [Inspiration] event"
|
||||||
|
|
||||||
|
Applicable to: Triggerable
|
||||||
|
|
||||||
??? example "Suppress warning [validationWarning]"
|
??? example "Suppress warning [validationWarning]"
|
||||||
Allows suppressing specific validation warnings. Errors, deprecation warnings, or warnings about untyped and non-filtering uniques should be heeded, not suppressed, and are therefore not accepted. Note that this can be used in ModOptions, in the uniques a warning is about, or as modifier on the unique triggering a warning - but you still need to be specific. Even in the modifier case you will need to specify a sufficiently selective portion of the warning text as parameter.
|
Allows suppressing specific validation warnings. Errors, deprecation warnings, or warnings about untyped and non-filtering uniques should be heeded, not suppressed, and are therefore not accepted. Note that this can be used in ModOptions, in the uniques a warning is about, or as modifier on the unique triggering a warning - but you still need to be specific. Even in the modifier case you will need to specify a sufficiently selective portion of the warning text as parameter.
|
||||||
Example: "Suppress warning [Tinman is supposed to automatically upgrade at tech Clockwork, and therefore Servos for its upgrade Mecha may not yet be researched! -or- *is supposed to automatically upgrade*]"
|
Example: "Suppress warning [Tinman is supposed to automatically upgrade at tech Clockwork, and therefore Servos for its upgrade Mecha may not yet be researched! -or- *is supposed to automatically upgrade*]"
|
||||||
@ -2374,6 +2379,7 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
|
|||||||
*[combatantFilter]: This indicates a combatant, which can either be a unit or a city (when bombarding). Must either be `City` or a `mapUnitFilter`.
|
*[combatantFilter]: This indicates a combatant, which can either be a unit or a city (when bombarding). Must either be `City` or a `mapUnitFilter`.
|
||||||
*[costOrStrength]: `Cost` or `Strength`.
|
*[costOrStrength]: `Cost` or `Strength`.
|
||||||
*[era]: The name of any era.
|
*[era]: The name of any era.
|
||||||
|
*[event]: The name of any event.
|
||||||
*[foundingOrEnhancing]: `founding` or `enhancing`.
|
*[foundingOrEnhancing]: `founding` or `enhancing`.
|
||||||
*[fraction]: Indicates a fractional number, which can be negative.
|
*[fraction]: Indicates a fractional number, which can be negative.
|
||||||
*[improvementName]: The name of any improvement.
|
*[improvementName]: The name of any improvement.
|
||||||
|
Reference in New Issue
Block a user