mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-13 17:28:57 +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,
|
||||
AttackedProtectedMinor,
|
||||
RecapturedCivilian,
|
||||
GameHasBeenWon
|
||||
GameHasBeenWon,
|
||||
Event
|
||||
}
|
||||
|
||||
class PopupAlert : IsPartOfGameInfoSerialization {
|
||||
|
@ -12,6 +12,10 @@ class UnitTurnManager(val unit: MapUnit) {
|
||||
|
||||
fun endTurn() {
|
||||
unit.movement.clearPathfindingCache()
|
||||
|
||||
for (unique in unit.getTriggeredUniques(UniqueType.TriggerUponTurnEnd))
|
||||
UniqueTriggerActivation.triggerUnique(unique, unit)
|
||||
|
||||
if (unit.currentMovement > 0
|
||||
&& unit.getTile().improvementInProgress != null
|
||||
&& 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 cityStateTypes = LinkedHashMap<String, CityStateType>()
|
||||
val personalities = LinkedHashMap<String, Personality>()
|
||||
val events = LinkedHashMap<String, Event>()
|
||||
|
||||
val greatGeneralUnits by lazy {
|
||||
units.values.filter { it.hasUnique(UniqueType.GreatPersonFromCombat, StateForConditionals.IgnoreConditionals) }
|
||||
@ -162,6 +163,7 @@ class Ruleset {
|
||||
}
|
||||
units.putAll(ruleset.units)
|
||||
personalities.putAll(ruleset.personalities)
|
||||
events.putAll(ruleset.events)
|
||||
modOptions.uniques.addAll(ruleset.modOptions.uniques)
|
||||
modOptions.constants.merge(ruleset.modOptions.constants)
|
||||
|
||||
@ -196,6 +198,7 @@ class Ruleset {
|
||||
victories.clear()
|
||||
cityStateTypes.clear()
|
||||
personalities.clear()
|
||||
events.clear()
|
||||
}
|
||||
|
||||
fun allRulesetObjects(): Sequence<IRulesetObject> =
|
||||
@ -384,6 +387,11 @@ class Ruleset {
|
||||
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
|
||||
|
@ -12,7 +12,7 @@ import kotlin.random.Random
|
||||
object Conditionals {
|
||||
|
||||
fun conditionalApplies(
|
||||
unique: Unique,
|
||||
unique: Unique?,
|
||||
condition: Unique,
|
||||
state: StateForConditionals
|
||||
): Boolean {
|
||||
@ -270,14 +270,16 @@ object Conditionals {
|
||||
UniqueType.ConditionalInRegionExceptOfType -> state.region?.type != condition.params[0]
|
||||
|
||||
UniqueType.ConditionalFirstCivToResearch ->
|
||||
unique.sourceObjectType == UniqueTarget.Tech
|
||||
unique != null
|
||||
&& unique.sourceObjectType == UniqueTarget.Tech
|
||||
&& checkOnGameInfo { civilizations.none {
|
||||
it != relevantCiv && it.isMajorCiv()
|
||||
&& it.tech.isResearched(unique.sourceObjectName!!) // guarded by the sourceObjectType check
|
||||
} }
|
||||
|
||||
UniqueType.ConditionalFirstCivToAdopt ->
|
||||
unique.sourceObjectType == UniqueTarget.Policy
|
||||
unique != null
|
||||
&& unique.sourceObjectType == UniqueTarget.Policy
|
||||
&& checkOnGameInfo { civilizations.none {
|
||||
it != relevantCiv && it.isMajorCiv()
|
||||
&& it.policies.isAdopted(unique.sourceObjectName!!) // guarded by the sourceObjectType check
|
||||
|
@ -509,13 +509,23 @@ enum class UniqueParameterType(
|
||||
// Used in FreeExtraBeliefs, FreeExtraAnyBeliefs
|
||||
private val knownValues = setOf("founding", "enhancing")
|
||||
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
|
||||
UniqueType.UniqueParameterErrorSeverity? = when (parameterText) {
|
||||
UniqueType.UniqueParameterErrorSeverity? = when (parameterText) {
|
||||
in knownValues -> null
|
||||
else -> UniqueType.UniqueParameterErrorSeverity.RulesetInvariant
|
||||
}
|
||||
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 */
|
||||
Technology("tech", "Agriculture", "The name of any tech") {
|
||||
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
|
||||
|
@ -5,6 +5,7 @@ import com.unciv.Constants
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.automation.civilization.NextTurnAutomation
|
||||
import com.unciv.logic.city.City
|
||||
import com.unciv.logic.civilization.AlertType
|
||||
import com.unciv.logic.civilization.CivFlags
|
||||
import com.unciv.logic.civilization.Civilization
|
||||
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.NotificationIcon
|
||||
import com.unciv.logic.civilization.PolicyAction
|
||||
import com.unciv.logic.civilization.PopupAlert
|
||||
import com.unciv.logic.civilization.TechAction
|
||||
import com.unciv.logic.civilization.managers.ReligionState
|
||||
import com.unciv.logic.map.mapgenerator.NaturalWonderGenerator
|
||||
@ -87,7 +89,9 @@ object UniqueTriggerActivation {
|
||||
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 ?:
|
||||
civInfo.cities.firstOrNull { it.isCapital() }
|
||||
@ -98,6 +102,17 @@ object UniqueTriggerActivation {
|
||||
val ruleSet = civInfo.gameInfo.ruleset
|
||||
|
||||
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 -> {
|
||||
val unitName = unique.params[0]
|
||||
val baseUnit = ruleSet.units[unitName] ?: return null
|
||||
@ -950,6 +965,7 @@ object UniqueTriggerActivation {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
else -> return null
|
||||
}
|
||||
}
|
||||
|
@ -772,6 +772,7 @@ enum class UniqueType(
|
||||
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
|
||||
FreeSpecificBuildings("Provides a [buildingName] in your first [positiveAmount] cities for free", UniqueTarget.Triggerable), // used in Policy
|
||||
TriggerEvent("Triggers a [event] event", UniqueTarget.Triggerable),
|
||||
|
||||
//endregion
|
||||
|
||||
|
@ -18,6 +18,7 @@ import com.unciv.logic.civilization.PopupAlert
|
||||
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
|
||||
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
|
||||
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.translations.fillPlaceholders
|
||||
import com.unciv.models.translations.tr
|
||||
@ -103,6 +104,7 @@ class AlertPopup(
|
||||
AlertType.BulliedProtectedMinor, AlertType.AttackedProtectedMinor -> addBulliedOrAttackedProtectedMinor()
|
||||
AlertType.RecapturedCivilian -> skipThisAlert = addRecapturedCivilian()
|
||||
AlertType.GameHasBeenWon -> addGameHasBeenWon()
|
||||
AlertType.Event -> skipThisAlert = !addEvent()
|
||||
}
|
||||
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
|
||||
|
||||
override fun close() {
|
||||
|
@ -159,6 +159,11 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
|
||||
|
||||
Applicable to: Triggerable
|
||||
|
||||
??? example "Triggers a [event] event"
|
||||
Example: "Triggers a [Inspiration] event"
|
||||
|
||||
Applicable to: Triggerable
|
||||
|
||||
??? 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.
|
||||
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`.
|
||||
*[costOrStrength]: `Cost` or `Strength`.
|
||||
*[era]: The name of any era.
|
||||
*[event]: The name of any event.
|
||||
*[foundingOrEnhancing]: `founding` or `enhancing`.
|
||||
*[fraction]: Indicates a fractional number, which can be negative.
|
||||
*[improvementName]: The name of any improvement.
|
||||
|
Reference in New Issue
Block a user