Temporary unit statuses! Can be applied with "This Unit gains the [promotion] status for [positiveAmount] turn(s)" trigger unique

Example: "This Unit gains the [Morale] status for [1] turn(s) <for [1] movement> <for units without [Morale]>"
This commit is contained in:
yairm210
2024-08-21 09:32:07 +03:00
parent d8afdd0f42
commit 43b0c9bbad
9 changed files with 81 additions and 12 deletions

View File

@ -808,6 +808,7 @@
"requiredTech": "Rifling",
"obsoleteTech": "Replaceable Parts",
"upgradesTo": "Infantry",
"uniques": ["This Unit gains the [Morale] status for [2] turn(s) <for [1] movement> <for units without [Morale]>"],
"attackSound": "shot"
},
{

View File

@ -96,6 +96,21 @@ class MapUnit : IsPartOfGameInfoSerialization {
/** Array list of all the tiles that this unit has attacked since the start of its most recent turn. Used in movement arrow overlay. */
var attacksSinceTurnStart = ArrayList<Vector2>()
class UnitStatus {
var name:String = ""
/** Decreses at *start on next turn* so defensive statuses persist on enemy turns */
var turnsLeft = 1
@Transient
lateinit var uniques: List<Unique>
fun setTransients(unit: MapUnit) {
uniques = unit.civ.gameInfo.ruleset.unitPromotions[name]?.uniqueObjects ?: emptyList()
}
}
var statuses = ArrayList<UnitStatus>()
//endregion
//region Transient fields
@ -198,6 +213,7 @@ class MapUnit : IsPartOfGameInfoSerialization {
toReturn.religion = religion
toReturn.religiousStrengthLost = religiousStrengthLost
toReturn.movementMemories = movementMemories.copy()
toReturn.statuses = ArrayList(statuses)
toReturn.mostRecentMoveType = mostRecentMoveType
toReturn.attacksSinceTurnStart = ArrayList(attacksSinceTurnStart.map { Vector2(it) })
return toReturn
@ -628,6 +644,7 @@ class MapUnit : IsPartOfGameInfoSerialization {
promotions.setTransients(this)
baseUnit = ruleset.units[name]
?: throw java.lang.Exception("Unit $name is not found!")
for (status in statuses) status.setTransients(this)
updateUniques()
if (action == UnitActionType.Automate.value){
@ -640,7 +657,9 @@ class MapUnit : IsPartOfGameInfoSerialization {
val uniqueSources =
baseUnit.uniqueObjects.asSequence() +
type.uniqueObjects +
promotions.getPromotions().flatMap { it.uniqueObjects }
promotions.getPromotions().flatMap { it.uniqueObjects } +
statuses.flatMap { it.uniques }
tempUniquesMap = UniqueMap(uniqueSources)
cache.updateUniques()
}
@ -1008,6 +1027,20 @@ class MapUnit : IsPartOfGameInfoSerialization {
}
}
fun setStatus(name:String, turns:Int){
val existingStatus = statuses.firstOrNull { it.name == name }
if (existingStatus != null){
if (turns > existingStatus.turnsLeft) existingStatus.turnsLeft = turns
return
}
val status = UnitStatus()
status.name = name
status.turnsLeft = turns
status.setTransients(this)
statuses.add(status)
}
fun isNuclearWeapon() = hasUnique(UniqueType.NuclearWeapon)
//endregion

View File

@ -166,5 +166,11 @@ class UnitTurnManager(val unit: MapUnit) {
unit.addMovementMemory()
unit.attacksSinceTurnStart.clear()
for (status in unit.statuses.toList()){
status.turnsLeft--
if (status.turnsLeft <= 0) unit.statuses.remove(status)
}
unit.updateUniques()
}
}

View File

@ -202,8 +202,12 @@ object Conditionals {
UniqueType.ConditionalVsUnits, UniqueType.ConditionalVsCombatant -> state.theirCombatant?.matchesFilter(conditional.params[0]) == true
UniqueType.ConditionalOurUnit, UniqueType.ConditionalOurUnitOnUnit ->
state.relevantUnit?.matchesFilter(conditional.params[0]) == true
UniqueType.ConditionalUnitWithPromotion -> state.relevantUnit?.promotions?.promotions?.contains(conditional.params[0]) == true
UniqueType.ConditionalUnitWithoutPromotion -> state.relevantUnit?.promotions?.promotions?.contains(conditional.params[0]) == false
UniqueType.ConditionalUnitWithPromotion -> state.relevantUnit != null &&
(state.relevantUnit!!.promotions.promotions.contains(conditional.params[0])
|| state.relevantUnit!!.statuses.any { it.name == conditional.params[0] } )
UniqueType.ConditionalUnitWithoutPromotion -> state.relevantUnit != null &&
!(state.relevantUnit!!.promotions.promotions.contains(conditional.params[0])
|| state.relevantUnit!!.statuses.any { it.name == conditional.params[0] } )
UniqueType.ConditionalAttacking -> state.combatAction == CombatAction.Attack
UniqueType.ConditionalDefending -> state.combatAction == CombatAction.Defend
UniqueType.ConditionalAboveHP -> state.relevantUnit != null && state.relevantUnit!!.health > conditional.params[0].toInt()

View File

@ -962,6 +962,14 @@ object UniqueTriggerActivation {
true
}
}
UniqueType.OneTimeUnitGainStatus -> {
if (unit == null) return null
if (unique.params[0] !in unit.civ.gameInfo.ruleset.unitPromotions) return null
return {
unit.setStatus(unique.params[0], unique.params[1].toInt())
true
}
}
UniqueType.OneTimeUnitUpgrade, UniqueType.OneTimeUnitSpecialUpgrade -> {
if (unit == null) return null
val upgradeAction =

View File

@ -737,8 +737,8 @@ enum class UniqueType(
/////// unit conditionals
ConditionalOurUnit("for [mapUnitFilter] units", UniqueTarget.Conditional),
ConditionalOurUnitOnUnit("when [mapUnitFilter]", UniqueTarget.Conditional), // Same but for the unit itself
ConditionalUnitWithPromotion("for units with [promotion]", UniqueTarget.Conditional),
ConditionalUnitWithoutPromotion("for units without [promotion]", UniqueTarget.Conditional),
ConditionalUnitWithPromotion("for units with [promotion]", UniqueTarget.Conditional, docDescription = "Also applies to units with temporary status"),
ConditionalUnitWithoutPromotion("for units without [promotion]", UniqueTarget.Conditional, docDescription = "Also applies to units with temporary status"),
ConditionalVsCity("vs cities", UniqueTarget.Conditional),
ConditionalVsUnits("vs [mapUnitFilter] units", UniqueTarget.Conditional),
ConditionalVsCombatant("vs [combatantFilter]", UniqueTarget.Conditional),
@ -837,6 +837,9 @@ enum class UniqueType(
OneTimeUnitRemovePromotion("This Unit loses the [promotion] promotion", UniqueTarget.UnitTriggerable),
OneTimeUnitGainMovement("This Unit gains [amount] movement", UniqueTarget.UnitTriggerable),
OneTimeUnitLoseMovement("This Unit loses [amount] movement", UniqueTarget.UnitTriggerable),
OneTimeUnitGainStatus("This Unit gains the [promotion] status for [positiveAmount] turn(s)", UniqueTarget.UnitTriggerable,
docDescription = "Statuses are temporary promotions. They do not stack, and reapplying a specific status take the highest number - so reapplying a 3-turn on a 1-turn makes it 3, but doing the opposite will have no effect. " +
"Turns left on the status decrease at the *start of turn*, so bonuses applied for 1 turn are stll applied during other civ's turns."),
SkipPromotion("Doing so will consume this opportunity to choose a Promotion", UniqueTarget.Promotion),
FreePromotion("This Promotion is free", UniqueTarget.Promotion),

View File

@ -6,18 +6,15 @@ import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.Image
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align
import com.unciv.logic.battle.CityCombatant
import com.unciv.logic.city.City
import com.unciv.logic.map.mapunit.MapUnit
import com.unciv.logic.map.tile.Tile
import com.unciv.models.Spy
import com.unciv.models.translations.tr
import com.unciv.ui.components.extensions.addSeparator
import com.unciv.ui.components.extensions.center
import com.unciv.ui.components.extensions.darken
import com.unciv.ui.components.extensions.getCloseButton
import com.unciv.ui.components.extensions.isShiftKeyPressed
import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.components.extensions.*
import com.unciv.ui.components.fonts.Fonts
import com.unciv.ui.components.input.keyShortcuts
import com.unciv.ui.components.input.onClick
import com.unciv.ui.components.widgets.UnitIconGroup
@ -65,7 +62,7 @@ class UnitTable(val worldScreen: WorldScreen) : Table() {
// This is so that not on every update(), we will update the unit table.
// Most of the time it's the same unit with the same stats so why waste precious time?
private var selectedUnitHasChanged = false
var selectedUnitHasChanged = false
val separator: Actor
var selectedSpy: Spy? = null
@ -288,6 +285,14 @@ class UnitTable(val worldScreen: WorldScreen) : Table() {
for (promotion in selectedUnit!!.promotions.getPromotions(true))
promotionsTable.add(ImageGetter.getPromotionPortrait(promotion.name)).padBottom(2f)
for (status in selectedUnit!!.statuses) {
val group = ImageGetter.getPromotionPortrait(status.name)
val turnsLeft = "${status.turnsLeft}${Fonts.turn}".toLabel(fontSize = 8).surroundWithCircle(15f, color = Color.BLACK)
group.addActor(turnsLeft)
turnsLeft.setPosition(group.width, 0f, Align.bottomRight)
promotionsTable.add(group).padBottom(2f)
}
// Since Clear also clears the listeners, we need to re-add them every time
promotionsTable.onClick {
if (selectedUnit == null || selectedUnit!!.promotions.promotions.isEmpty()) return@onClick

View File

@ -181,5 +181,6 @@ class UnitActionsTable(val worldScreen: WorldScreen) : Table() {
if (!UncivGame.Current.settings.autoUnitCycle) return
if (unit.isDestroyed || unitAction.type.isSkippingToNextUnit && (unit.isMoving() && !unit.hasMovement() || !unit.isMoving()))
worldScreen.switchToNextUnit()
else worldScreen.bottomUnitTable.selectedUnitHasChanged = true
}
}

View File

@ -237,6 +237,12 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
Applicable to: UnitTriggerable
??? example "This Unit gains the [promotion] status for [positiveAmount] turn(s)"
Statuses are temporary promotions. They do not stack, and reapplying a specific status take the highest number - so reapplying a 3-turn on a 1-turn makes it 3, but doing the opposite will have no effect. Turns left on the status decrease at the *start of turn*, so bonuses applied for 1 turn are stll applied during other civ's turns.
Example: "This Unit gains the [Shock I] status for [3] turn(s)"
Applicable to: UnitTriggerable
## Global uniques
!!! note ""
@ -2966,11 +2972,13 @@ If your mod renames Coast or Lakes, do not use this with one of these as paramet
Applicable to: Conditional
??? example "&lt;for units with [promotion]&gt;"
Also applies to units with temporary status
Example: "&lt;for units with [Shock I]&gt;"
Applicable to: Conditional
??? example "&lt;for units without [promotion]&gt;"
Also applies to units with temporary status
Example: "&lt;for units without [Shock I]&gt;"
Applicable to: Conditional