mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-20 04:38:18 +07:00
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:
@ -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"
|
||||
},
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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 =
|
||||
|
@ -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),
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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 "<for units with [promotion]>"
|
||||
Also applies to units with temporary status
|
||||
Example: "<for units with [Shock I]>"
|
||||
|
||||
Applicable to: Conditional
|
||||
|
||||
??? example "<for units without [promotion]>"
|
||||
Also applies to units with temporary status
|
||||
Example: "<for units without [Shock I]>"
|
||||
|
||||
Applicable to: Conditional
|
||||
|
Reference in New Issue
Block a user