From e93d95d7a0bf2f5211e75d850b0b4f507a344022 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Tue, 12 Aug 2025 17:50:23 -0400 Subject: [PATCH] [TLA] Implement Fire Nation Attacks --- .../src/mage/cards/f/FireNationAttacks.java | 36 ++++++++ .../src/mage/sets/AvatarTheLastAirbender.java | 1 + .../abilities/keyword/FirebendingAbility.java | 91 +++++++++++++++++++ .../main/java/mage/game/events/GameEvent.java | 1 + .../token/SoldierFirebendingToken.java | 30 ++++++ Mage/src/main/java/mage/players/ManaPool.java | 27 ++++-- Utils/keywords.txt | 1 + 7 files changed, 180 insertions(+), 7 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/f/FireNationAttacks.java create mode 100644 Mage/src/main/java/mage/abilities/keyword/FirebendingAbility.java create mode 100644 Mage/src/main/java/mage/game/permanent/token/SoldierFirebendingToken.java diff --git a/Mage.Sets/src/mage/cards/f/FireNationAttacks.java b/Mage.Sets/src/mage/cards/f/FireNationAttacks.java new file mode 100644 index 00000000000..c4ffb6c0dd1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FireNationAttacks.java @@ -0,0 +1,36 @@ +package mage.cards.f; + +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.SoldierFirebendingToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FireNationAttacks extends CardImpl { + + public FireNationAttacks(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{R}"); + + // Create two 2/2 red Soldier creature tokens with firebending 1. + this.getSpellAbility().addEffect(new CreateTokenEffect(new SoldierFirebendingToken(), 2)); + + // Flashback {8}{R} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{8}{R}"))); + } + + private FireNationAttacks(final FireNationAttacks card) { + super(card); + } + + @Override + public FireNationAttacks copy() { + return new FireNationAttacks(this); + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java index f0bd48395a0..a02c3a6a6d0 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java @@ -23,6 +23,7 @@ public final class AvatarTheLastAirbender extends ExpansionSet { cards.add(new SetCardInfo("Avatar Enthusiasts", 11, Rarity.COMMON, mage.cards.a.AvatarEnthusiasts.class)); cards.add(new SetCardInfo("Earthbending Lesson", 176, Rarity.COMMON, mage.cards.e.EarthbendingLesson.class)); + cards.add(new SetCardInfo("Fire Nation Attacks", 133, Rarity.UNCOMMON, mage.cards.f.FireNationAttacks.class)); cards.add(new SetCardInfo("Katara, the Fearless", 230, Rarity.RARE, mage.cards.k.KataraTheFearless.class)); cards.add(new SetCardInfo("Sokka's Haiku", 71, Rarity.UNCOMMON, mage.cards.s.SokkasHaiku.class)); cards.add(new SetCardInfo("Southern Air Temple", 36, Rarity.UNCOMMON, mage.cards.s.SouthernAirTemple.class)); diff --git a/Mage/src/main/java/mage/abilities/keyword/FirebendingAbility.java b/Mage/src/main/java/mage/abilities/keyword/FirebendingAbility.java new file mode 100644 index 00000000000..6ac9f6c8b24 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/FirebendingAbility.java @@ -0,0 +1,91 @@ +package mage.abilities.keyword; + +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; + +import java.util.Collections; + +/** + * @author TheElk801 + */ +public class FirebendingAbility extends AttacksTriggeredAbility { + + private final DynamicValue amount; + + public FirebendingAbility(int amount) { + this(StaticValue.get(amount)); + } + + public FirebendingAbility(DynamicValue amount) { + super(new FirebendingAbilityEffect(amount)); + this.amount = amount; + } + + private FirebendingAbility(final FirebendingAbility ability) { + super(ability); + this.amount = ability.amount; + } + + @Override + public FirebendingAbility copy() { + return new FirebendingAbility(this); + } + + @Override + public String getRule() { + if (amount instanceof StaticValue) { + return "firebending " + amount + + " (Whenever this creature attacks, add " + + String.join("", Collections.nCopies(((StaticValue) amount).getValue(), "{R}")) + + ". This mana lasts until end of combat.)"; + } + return "firebending X, where X is " + amount.getMessage() + + ". (Whenever this creature attacks, add X {R}. This mana lasts until end of combat.)"; + } +} + +class FirebendingAbilityEffect extends OneShotEffect { + + private final DynamicValue amount; + + FirebendingAbilityEffect(DynamicValue amount) { + super(Outcome.Benefit); + this.amount = amount; + } + + private FirebendingAbilityEffect(final FirebendingAbilityEffect effect) { + super(effect); + this.amount = effect.amount; + } + + @Override + public FirebendingAbilityEffect copy() { + return new FirebendingAbilityEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + int amount = Math.max(this.amount.calculate(game, source, this), 0); + if (amount > 0) { + player.getManaPool().addMana(Mana.RedMana(amount), game, source, Duration.EndOfCombat); + } + game.fireEvent(GameEvent.getEvent( + GameEvent.EventType.FIREBENDED, source.getSourceId(), + source, source.getControllerId(), amount + )); + return true; + } +} diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index 5968f5eb52f..17df58f941f 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -696,6 +696,7 @@ public class GameEvent implements Serializable { */ PAY_SACRIFICE_COST, EARTHBENDED, + FIREBENDED, // custom events - must store some unique data to track CUSTOM_EVENT; diff --git a/Mage/src/main/java/mage/game/permanent/token/SoldierFirebendingToken.java b/Mage/src/main/java/mage/game/permanent/token/SoldierFirebendingToken.java new file mode 100644 index 00000000000..80dd650652a --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/SoldierFirebendingToken.java @@ -0,0 +1,30 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.keyword.FirebendingAbility; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author TheElk801 + */ +public final class SoldierFirebendingToken extends TokenImpl { + + public SoldierFirebendingToken() { + super("Soldier Token", "2/2 red Soldier creature token with firebending 1"); + cardType.add(CardType.CREATURE); + color.setRed(true); + subtype.add(SubType.SOLDIER); + power = new MageInt(2); + toughness = new MageInt(2); + this.addAbility(new FirebendingAbility(1)); + } + + private SoldierFirebendingToken(final SoldierFirebendingToken token) { + super(token); + } + + public SoldierFirebendingToken copy() { + return new SoldierFirebendingToken(this); + } +} diff --git a/Mage/src/main/java/mage/players/ManaPool.java b/Mage/src/main/java/mage/players/ManaPool.java index 82bca7edcef..1311c7408fa 100644 --- a/Mage/src/main/java/mage/players/ManaPool.java +++ b/Mage/src/main/java/mage/players/ManaPool.java @@ -9,6 +9,7 @@ import mage.abilities.costs.Cost; import mage.abilities.effects.mana.ManaEffect; import mage.constants.Duration; import mage.constants.ManaType; +import mage.constants.PhaseStep; import mage.constants.TurnPhase; import mage.filter.Filter; import mage.filter.FilterMana; @@ -302,9 +303,17 @@ public class ManaPool implements Serializable { } private int emptyItem(ManaPoolItem item, Emptiable toEmpty, Game game, ManaType manaType) { - if (item.getDuration() == Duration.EndOfTurn - && game.getTurnPhaseType() != TurnPhase.END) { - return 0; + switch (item.getDuration()) { + case EndOfTurn: + if (game.getTurnPhaseType() != TurnPhase.END) { + return 0; + } + break; + case EndOfCombat: + if (game.getTurnPhaseType() != TurnPhase.COMBAT + || game.getTurnStepType() != PhaseStep.END_COMBAT) { + return 0; + } } if (manaBecomesBlack) { int amount = toEmpty.get(manaType); @@ -366,6 +375,10 @@ public class ManaPool implements Serializable { } public void addMana(Mana manaToAdd, Game game, Ability source, boolean dontLoseUntilEOT) { + addMana(manaToAdd, game, source, dontLoseUntilEOT ? Duration.EndOfTurn : null); + } + + public void addMana(Mana manaToAdd, Game game, Ability source, Duration duration) { if (manaToAdd != null) { Mana mana = manaToAdd.copy(); if (!game.replaceEvent(new ManaEvent(EventType.ADD_MANA, source.getId(), source, playerId, mana))) { @@ -376,8 +389,8 @@ public class ManaPool implements Serializable { source.getSourceObject(game), conditionalMana.getManaProducerOriginalId() != null ? conditionalMana.getManaProducerOriginalId() : source.getOriginalId() ); - if (dontLoseUntilEOT) { - item.setDuration(Duration.EndOfTurn); + if (duration != null) { + item.setDuration(duration); } this.manaItems.add(item); } else { @@ -392,8 +405,8 @@ public class ManaPool implements Serializable { source.getOriginalId(), mana.getFlag() ); - if (dontLoseUntilEOT) { - item.setDuration(Duration.EndOfTurn); + if (duration != null) { + item.setDuration(duration); } this.manaItems.add(item); } diff --git a/Utils/keywords.txt b/Utils/keywords.txt index fa446453d07..a2e1ccb4ebe 100644 --- a/Utils/keywords.txt +++ b/Utils/keywords.txt @@ -52,6 +52,7 @@ Exploit|new| Extort|new| Fabricate|number| Fear|instance| +Firebending|number| First strike|instance| Flanking|new| Flash|instance|