* Events!

* Whoops

* Resolved #11277 - "Upon turn end" also works for unit uniques
This commit is contained in:
Yair Morgenstern
2024-03-09 21:26:45 +02:00
committed by GitHub
parent 86f0ed6e85
commit 20b4e6674b
10 changed files with 119 additions and 16 deletions

View File

@ -20,7 +20,8 @@ enum class AlertType : IsPartOfGameInfoSerialization {
BulliedProtectedMinor,
AttackedProtectedMinor,
RecapturedCivilian,
GameHasBeenWon
GameHasBeenWon,
Event
}
class PopupAlert : IsPartOfGameInfoSerialization {

View File

@ -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()!!)

View 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)
}
}

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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
}
}

View File

@ -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

View File

@ -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() {

View File

@ -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.