From a0f9923c21807f7c1db2a2fb944078a46f2d74c3 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sun, 17 Aug 2025 13:24:11 +0200 Subject: [PATCH] TLA: Airbend (#8419) --- .../main/java/forge/game/ability/ApiType.java | 1 + .../game/ability/effects/AirbendEffect.java | 94 +++++++++++++++++++ .../ability/effects/ChangeZoneEffect.java | 3 +- .../upcoming/airbending_lesson.txt | 6 ++ 4 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 forge-game/src/main/java/forge/game/ability/effects/AirbendEffect.java create mode 100644 forge-gui/res/cardsfolder/upcoming/airbending_lesson.txt diff --git a/forge-game/src/main/java/forge/game/ability/ApiType.java b/forge-game/src/main/java/forge/game/ability/ApiType.java index 1093655d647..ae214f3bd73 100644 --- a/forge-game/src/main/java/forge/game/ability/ApiType.java +++ b/forge-game/src/main/java/forge/game/ability/ApiType.java @@ -19,6 +19,7 @@ public enum ApiType { AddPhase (AddPhaseEffect.class), AddTurn (AddTurnEffect.class), AdvanceCrank (AdvanceCrankEffect.class), + Airbend (AirbendEffect.class), AlterAttribute (AlterAttributeEffect.class), Amass (AmassEffect.class), Animate (AnimateEffect.class), diff --git a/forge-game/src/main/java/forge/game/ability/effects/AirbendEffect.java b/forge-game/src/main/java/forge/game/ability/effects/AirbendEffect.java new file mode 100644 index 00000000000..c95ea23ed6e --- /dev/null +++ b/forge-game/src/main/java/forge/game/ability/effects/AirbendEffect.java @@ -0,0 +1,94 @@ +package forge.game.ability.effects; + +import java.util.Map; + +import com.google.common.collect.Iterables; + +import forge.game.Game; +import forge.game.ability.AbilityKey; +import forge.game.ability.SpellAbilityEffect; +import forge.game.card.Card; +import forge.game.card.CardZoneTable; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; +import forge.game.trigger.TriggerType; +import forge.game.zone.ZoneType; +import forge.util.Lang; + +public class AirbendEffect extends SpellAbilityEffect { + + @Override + protected String getStackDescription(SpellAbility sa) { + final StringBuilder sb = new StringBuilder("Airbend "); + + Iterable tgts; + if (sa.usesTargeting()) { + tgts = getCardsfromTargets(sa); + } else { // otherwise add self to list and go from there + tgts = sa.knownDetermineDefined(sa.getParam("Defined")); + } + + sb.append(sa.getParamOrDefault("DefinedDesc", Lang.joinHomogenous(tgts))); + sb.append("."); + if (Iterables.size(tgts) > 1) { + sb.append(" (Exile them. While each one is exiled, its owner may cast it for {2} rather than its mana cost.)"); + } else { + sb.append(" (Exile it. While it’s exiled, its owner may cast it for {2} rather than its mana cost.)"); + } + + return sb.toString(); + } + + + @Override + public void resolve(SpellAbility sa) { + final Card hostCard = sa.getHostCard(); + final Game game = hostCard.getGame(); + final Player pl = sa.getActivatingPlayer(); + + final CardZoneTable triggerList = CardZoneTable.getSimultaneousInstance(sa); + + for (Card c : getTargetCards(sa)) { + final Card gameCard = game.getCardState(c, null); + // gameCard is LKI in that case, the card is not in game anymore + // or the timestamp did change + // this should check Self too + if (gameCard == null || !c.equalsWithGameTimestamp(gameCard) || gameCard.isPhasedOut()) { + continue; + } + + if (!gameCard.canExiledBy(sa, true)) { + continue; + } + handleExiledWith(gameCard, sa); + + Map moveParams = AbilityKey.newMap(); + AbilityKey.addCardZoneTableParams(moveParams, triggerList); + + Card movedCard = game.getAction().exile(gameCard, sa, moveParams); + + if (movedCard == null || !movedCard.isInZone(ZoneType.Exile)) { + continue; + } + + // Effect to cast for 2 from exile + Card eff = createEffect(sa, movedCard.getOwner(), "Airbend" + movedCard, hostCard.getImageKey()); + eff.addRemembered(movedCard); + + StringBuilder sbPlay = new StringBuilder(); + sbPlay.append("Mode$ Continuous | MayPlay$ True | MayPlayAltManaCost$ 2 | EffectZone$ Command | Affected$ Card.IsRemembered+nonLand"); + sbPlay.append(" | AffectedZone$ Exile | Description$ You may cast the card."); + eff.addStaticAbility(sbPlay.toString()); + + addForgetOnMovedTrigger(eff, "Exile"); + addForgetOnCastTrigger(eff, "Card.IsRemembered"); + + game.getAction().moveToCommand(eff, sa); + } + triggerList.triggerChangesZoneAll(game, sa); + handleExiledWith(triggerList.allCards(), sa); + + game.getTriggerHandler().runTrigger(TriggerType.Airbend, AbilityKey.mapFromPlayer(pl), false); + } + +} diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java index a62e31e631d..530e944e6c1 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java @@ -19,7 +19,6 @@ import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementType; import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityStackInstance; -import forge.game.staticability.StaticAbility; import forge.game.trigger.TriggerType; import forge.game.zone.Zone; import forge.game.zone.ZoneType; @@ -765,7 +764,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { StringBuilder sbPlay = new StringBuilder(); sbPlay.append("Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonLand+!ThisTurnEntered"); sbPlay.append(" | AffectedZone$ Exile | Description$ You may cast the card."); - final StaticAbility st = eff.addStaticAbility(sbPlay.toString()); + eff.addStaticAbility(sbPlay.toString()); eff.addRemembered(movedCard); addForgetOnMovedTrigger(eff, "Exile"); addForgetOnCastTrigger(eff, "Card.IsRemembered"); diff --git a/forge-gui/res/cardsfolder/upcoming/airbending_lesson.txt b/forge-gui/res/cardsfolder/upcoming/airbending_lesson.txt new file mode 100644 index 00000000000..3e422f67f9a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/airbending_lesson.txt @@ -0,0 +1,6 @@ +Name:Airbending Lesson +ManaCost:2 W +Types:Instant Lesson +A:SP$ Airbend | ValidTgts$ Permanent.nonLand | TgtPrompt$ Select target nonland permanent | SubAbility$ DBDraw | SpellDescription$ Airbend target nonland permanent. (Exile it. While it’s exiled, its owner may cast it for {2} rather than its mana cost.) +SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 1 | SpellDescription$ Draw a card. +Oracle:Airbend target nonland permanent. (Exile it. While it’s exiled, its owner may cast it for {2} rather than its mana cost.)\nDraw a card.