[TLA] Implement Firebender Ascension

Also implemented an OptionalOneShotEffect for ease of stapling together a mandatory effect and an optional effect on the same ability.
This commit is contained in:
Grath 2025-11-07 18:40:45 -05:00
parent 4494bdddbb
commit a502ee94d5
3 changed files with 220 additions and 0 deletions

View file

@ -0,0 +1,133 @@
package mage.cards.f;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.condition.Condition;
import mage.abilities.condition.common.SourceHasCounterCondition;
import mage.abilities.decorator.ConditionalOneShotEffect;
import mage.abilities.decorator.OptionalOneShotEffect;
import mage.abilities.effects.common.CopyStackObjectEffect;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.events.DefenderAttackedEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.SoldierFirebendingToken;
import mage.game.stack.StackObject;
import mage.target.targetpointer.FixedTarget;
/**
*
* @author Grath
*/
public final class FirebenderAscension extends CardImpl {
public FirebenderAscension(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{R}");
// When this enchantment enters, create a 2/2 red Soldier creature token with firebending 1.
this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new SoldierFirebendingToken())));
// Whenever a creature you control attacking causes a triggered ability of that creature to trigger, put a quest
// counter on this enchantment. Then if it has four or more quest counters on it, you may copy that ability. You
// may choose new targets for the copy.
this.addAbility(new FirebenderAscensionTriggeredAbility());
}
private FirebenderAscension(final FirebenderAscension card) {
super(card);
}
@Override
public FirebenderAscension copy() {
return new FirebenderAscension(this);
}
}
class FirebenderAscensionTriggeredAbility extends TriggeredAbilityImpl {
private static final Condition condition = new SourceHasCounterCondition(CounterType.QUEST, 4);
FirebenderAscensionTriggeredAbility() {
super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.QUEST.createInstance(), true), false);
addEffect(new ConditionalOneShotEffect(new OptionalOneShotEffect(new CopyStackObjectEffect(),
"You may copy that ability. You may choose new targets for the copy."), condition,
"Then if it has four or more quest counters on it, you may copy that ability. You may choose new targets for the copy."));
setTriggerPhrase("Whenever a creature you control attacking causes a triggered ability of that creature to trigger, ");
}
private FirebenderAscensionTriggeredAbility(final FirebenderAscensionTriggeredAbility ability) {
super(ability);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.TRIGGERED_ABILITY;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
StackObject stackObject = game.getStack().getStackObject(event.getTargetId());
Permanent permanent = game.getPermanent(event.getSourceId());
if (stackObject == null || permanent == null || !permanent.isCreature(game)) {
return false; // only creatures
}
if (this.getControllerId() != permanent.getControllerId()) {
return false; // only your creatures
}
Ability stackAbility = stackObject.getStackAbility();
if (!stackAbility.isTriggeredAbility()) {
return false;
}
GameEvent triggerEvent = ((TriggeredAbility) stackAbility).getTriggerEvent();
GameEvent.EventType eventType = triggerEvent.getType();
if (triggerEvent == null || (eventType != GameEvent.EventType.ATTACKER_DECLARED &&
eventType != GameEvent.EventType.DECLARED_ATTACKERS &&
eventType != GameEvent.EventType.DEFENDER_ATTACKED)) {
return false; // only attacking triggers
}
switch (triggerEvent.getType()) {
case ATTACKER_DECLARED:
if (triggerEvent.getSourceId() != permanent.getId()) {
return false;
} // only triggered abilities of that creature
break;
case DECLARED_ATTACKERS:
if (game
.getCombat()
.getAttackers()
.stream()
.noneMatch(permanent.getId()::equals)) {
return false;
} // only if the creature was one of the attackers that were declared
// This might give some false positives?
break;
case DEFENDER_ATTACKED:
if (((DefenderAttackedEvent) triggerEvent)
.getAttackers(game)
.stream()
.noneMatch(permanent::equals)) {
return false;
} // only if the creature was one of the attackers that were declared
// This might give some false positives?
}
getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game));
return true;
}
@Override
public FirebenderAscensionTriggeredAbility copy() {
return new FirebenderAscensionTriggeredAbility(this);
}
}

View file

@ -110,6 +110,8 @@ public final class AvatarTheLastAirbender extends ExpansionSet {
cards.add(new SetCardInfo("Fire Nation Raider", 135, Rarity.COMMON, mage.cards.f.FireNationRaider.class));
cards.add(new SetCardInfo("Fire Nation Warship", 256, Rarity.UNCOMMON, mage.cards.f.FireNationWarship.class));
cards.add(new SetCardInfo("Fire Sages", 136, Rarity.UNCOMMON, mage.cards.f.FireSages.class));
cards.add(new SetCardInfo("Firebender Ascension", 137, Rarity.RARE, mage.cards.f.FirebenderAscension.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Firebender Ascension", 312, Rarity.RARE, mage.cards.f.FirebenderAscension.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Firebending Student", 139, Rarity.RARE, mage.cards.f.FirebendingStudent.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Firebending Student", 342, Rarity.RARE, mage.cards.f.FirebendingStudent.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Firebending Student", 393, Rarity.RARE, mage.cards.f.FirebendingStudent.class, NON_FULL_USE_VARIOUS));

View file

@ -0,0 +1,85 @@
package mage.abilities.decorator;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.Effects;
import mage.abilities.effects.OneShotEffect;
import mage.game.Game;
import mage.players.Player;
import mage.target.targetpointer.TargetPointer;
import mage.util.CardUtil;
/**
* Adds condition to {@link OneShotEffect}. Acts as decorator.
*
* @author Grath
*/
public class OptionalOneShotEffect extends OneShotEffect {
private final Effects effects = new Effects();
public OptionalOneShotEffect(OneShotEffect effect, String text) {
super(effect.getOutcome());
if (effect != null) {
this.effects.add(effect);
}
this.staticText = text;
}
protected OptionalOneShotEffect(final OptionalOneShotEffect effect) {
super(effect);
this.effects.addAll(effect.effects.copy());
}
@Override
public boolean apply(Game game, Ability source) {
// nothing to do - no problem
if (effects.isEmpty()) {
return true;
}
Player player = game.getPlayer(source.getControllerId());
if (player != null && player.chooseUse(outcome, staticText, source, game)) {
effects.setTargetPointer(this.getTargetPointer().copy());
effects.forEach(effect -> effect.apply(game, source));
return true;
}
return false;
}
public OptionalOneShotEffect addEffect(OneShotEffect effect) {
this.effects.add(effect);
return this;
}
@Override
public void setValue(String key, Object value) {
super.setValue(key, value);
this.effects.setValue(key, value);
}
@Override
public OptionalOneShotEffect copy() {
return new OptionalOneShotEffect(this);
}
@Override
public String getText(Mode mode) {
if (staticText != null && !staticText.isEmpty()) {
return staticText;
}
return "you may " + CardUtil.getTextWithFirstCharLowerCase(effects.getText(mode));
}
@Override
public OptionalOneShotEffect setTargetPointer(TargetPointer targetPointer) {
effects.setTargetPointer(targetPointer);
super.setTargetPointer(targetPointer);
return this;
}
@Override
public OptionalOneShotEffect withTargetDescription(String target) {
effects.forEach(effect -> effect.withTargetDescription(target));
return this;
}
}