Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[OTC] Implement Felix Five-Boots #12074

Merged
merged 8 commits into from
Jun 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions Mage.Sets/src/mage/cards/f/FelixFiveBoots.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package mage.cards.f;

import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.keyword.MenaceAbility;
import mage.abilities.keyword.WardAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.BatchEvent;
import mage.game.events.DamagedEvent;
import mage.game.events.GameEvent;
import mage.game.events.NumberOfTriggersEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;

import java.util.UUID;

/**
* @author PurpleCrowbar, Susucr
*/
public final class FelixFiveBoots extends CardImpl {

public FelixFiveBoots(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{G}{U}");

this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.OOZE, SubType.ROGUE);
this.power = new MageInt(5);
this.toughness = new MageInt(4);

// Menace
this.addAbility(new MenaceAbility(false));

// Ward {2}
this.addAbility(new WardAbility(new GenericManaCost(2), false));

// If a creature you control dealing combat damage to a player causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time.
this.addAbility(new SimpleStaticAbility(new FelixFiveBootsEffect()));
}

private FelixFiveBoots(final FelixFiveBoots card) {
super(card);
}

@Override
public FelixFiveBoots copy() {
return new FelixFiveBoots(this);
}
}

class FelixFiveBootsEffect extends ReplacementEffectImpl {

FelixFiveBootsEffect() {
super(Duration.WhileOnBattlefield, Outcome.Benefit);
staticText = "If a creature you control dealing combat damage to a player causes a triggered ability " +
"of a permanent you control to trigger, that ability triggers an additional time";
}

private FelixFiveBootsEffect(final FelixFiveBootsEffect effect) {
super(effect);
}

@Override
public FelixFiveBootsEffect copy() {
return new FelixFiveBootsEffect(this);
}

@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.NUMBER_OF_TRIGGERS;
}

@Override
public boolean applies(GameEvent event, Ability source, Game game) {
NumberOfTriggersEvent numberOfTriggersEvent = (NumberOfTriggersEvent) event;
Permanent sourcePermanent = game.getPermanent(numberOfTriggersEvent.getSourceId());
Susucre marked this conversation as resolved.
Show resolved Hide resolved
if (sourcePermanent == null || !sourcePermanent.isControlledBy(source.getControllerId())) {
return false;
}

GameEvent sourceEvent = numberOfTriggersEvent.getSourceEvent();
if (sourceEvent == null) {
return false;
}

if (sourceEvent instanceof DamagedEvent) {
return checkDamagedEvent((DamagedEvent) sourceEvent, source.getControllerId(), game);
} else if (sourceEvent instanceof BatchEvent) {
for (Object singleEventAsObject : ((BatchEvent) sourceEvent).getEvents()) {
if (singleEventAsObject instanceof DamagedEvent
&& checkDamagedEvent((DamagedEvent) singleEventAsObject, source.getControllerId(), game)
) {
// For batch events, if one of the event inside the condition match the condition,
// the effect applies to the whole batch events.
return true;
}
}
}

return false;
}

// Checks that a given DamagedEvent matches with
// "If a creature you control dealing combat damage to a player"
private static boolean checkDamagedEvent(DamagedEvent event, UUID controllerId, Game game) {
if (event == null) {
return false;
}
UUID sourceId = event.getSourceId();
Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(sourceId);
UUID targetId = event.getTargetId();
Player playerDealtDamage = game.getPlayer(targetId);
return sourcePermanent != null
&& sourcePermanent.isCreature(game)
&& sourcePermanent.isControlledBy(controllerId)
&& event.isCombatDamage()
&& playerDealtDamage != null;
}

@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
event.setAmount(event.getAmount() + 1);
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ private OutlawsOfThunderJunctionCommander() {
cards.add(new SetCardInfo("Faithless Looting", 165, Rarity.COMMON, mage.cards.f.FaithlessLooting.class));
cards.add(new SetCardInfo("Fallen Shinobi", 226, Rarity.RARE, mage.cards.f.FallenShinobi.class));
cards.add(new SetCardInfo("Feed the Swarm", 134, Rarity.COMMON, mage.cards.f.FeedTheSwarm.class));
cards.add(new SetCardInfo("Felix Five-Boots", 6, Rarity.MYTHIC, mage.cards.f.FelixFiveBoots.class));
cards.add(new SetCardInfo("Fellwar Stone", 257, Rarity.UNCOMMON, mage.cards.f.FellwarStone.class));
cards.add(new SetCardInfo("Ferrous Lake", 294, Rarity.RARE, mage.cards.f.FerrousLake.class));
cards.add(new SetCardInfo("Fetid Heath", 295, Rarity.RARE, mage.cards.f.FetidHeath.class));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package org.mage.test.cards.triggers.damage;

import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.counters.CounterType;
import org.junit.Ignore;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;

/**
* @author PurpleCrowbar, Susucr
* https://scryfall.com/card/otc/42/felix-five-boots
*/
public class FelixFiveBootsTest extends CardTestPlayerBase {

// Trample. Whenever Belligerent Guest deals combat damage to a player, create a Blood token.
private static final String vampire = "Belligerent Guest";

@Test
public void testBasicFelixFunctionality() {
xenohedron marked this conversation as resolved.
Show resolved Hide resolved
setStrictChooseMode(true);

addCard(Zone.BATTLEFIELD, playerA, "Felix Five-Boots");
addCard(Zone.BATTLEFIELD, playerA, vampire);

attack(1, playerA, vampire, playerB);
setChoice(playerA, "Whenever {this} deals combat damage to a player, create a Blood token."); // need to order the triggers
checkStackSize("two triggers", 1, PhaseStep.COMBAT_DAMAGE, playerA, 2);

setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();
}

@Test
public void testDoubleTriggerDeadAttacker() {
setStrictChooseMode(true);

addCard(Zone.BATTLEFIELD, playerA, "Felix Five-Boots");
addCard(Zone.BATTLEFIELD, playerA, vampire);
addCard(Zone.BATTLEFIELD, playerB, "Moss Viper"); // 1/1 Deathtouch

attack(1, playerA, vampire, playerB);
block(1, playerB, "Moss Viper", vampire);

setChoice(playerA, "X=1"); // assign damage to Moss Viper
setChoice(playerA, "Whenever {this} deals combat damage to a player, create a Blood token."); // need to order the triggers
checkStackSize("two triggers", 1, PhaseStep.COMBAT_DAMAGE, playerA, 2);

setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();
}

@Test
public void testNoBonusTriggerForEnemy() {
setStrictChooseMode(true);

addCard(Zone.BATTLEFIELD, playerA, vampire);
addCard(Zone.BATTLEFIELD, playerB, "Felix Five-Boots");

attack(1, playerA, vampire, playerB);
checkStackSize("one trigger", 1, PhaseStep.COMBAT_DAMAGE, playerA, 1);

setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();
}

@Test
public void testNoTriggerOnNonCombatDamage() {
setStrictChooseMode(true);

addCard(Zone.BATTLEFIELD, playerA, "Felix Five-Boots");
addCard(Zone.BATTLEFIELD, playerA, "Nettle Drone"); // {T}: {this} deals 1 damage to each opponent
addCard(Zone.BATTLEFIELD, playerA, "Island");
addCard(Zone.HAND, playerA, "Curiosity"); // Whenever enchanted creature deals damage to an opponent, you may draw a card

castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curiosity", "Nettle Drone", true);
activateAbility(1, PhaseStep.BEGIN_COMBAT, playerA, "{T}: {this} deals 1 damage to each opponent");
checkStackSize("one trigger", 1, PhaseStep.BEGIN_COMBAT, playerA, 1);
setChoice(playerA, true); // yes to Curiosity "you may draw"

setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();
}

@Test
public void testBatchEvent() {
setStrictChooseMode(true);

addCard(Zone.BATTLEFIELD, playerA, "Felix Five-Boots");
// Whenever Olivia's Attendants deals damage, create that many Blood tokens. 6/6
addCard(Zone.BATTLEFIELD, playerA, "Olivia's Attendants");

attack(1, playerA, "Olivia's Attendants", playerB);
setChoice(playerA, "Whenever {this} deals damage, create that many Blood tokens."); // need to order the triggers
checkStackSize("two triggers", 1, PhaseStep.COMBAT_DAMAGE, playerA, 2);

setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();

assertPermanentCount(playerA, "Blood Token", 6 + 6);
}

@Test
@Ignore // see #12095
public void testSelectRightPartOfBatch() {
setStrictChooseMode(true);

addCard(Zone.BATTLEFIELD, playerA, "Felix Five-Boots");
// Whenever equipped creature deals combat damage, put two charge counters on Umezawa’s Jitte.
addCard(Zone.BATTLEFIELD, playerA, "Umezawa's Jitte");
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
addCard(Zone.BATTLEFIELD, playerA, "Elite Vanguard");
addCard(Zone.BATTLEFIELD, playerA, "Raging Goblin");
addCard(Zone.BATTLEFIELD, playerB, "Wall of Blossoms");

activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip", "Elite Vanguard");

attack(1, playerA, "Elite Vanguard", playerB);
attack(1, playerA, "Raging Goblin", playerB);
block(1, playerB, "Wall of Blossoms", "Elite Vanguard");

checkStackSize("only one Jitte triggers", 1, PhaseStep.COMBAT_DAMAGE, playerA, 1);

setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();

assertCounterCount(playerA, "Umezawa's Jitte", CounterType.CHARGE, 2);
}
}