[FIN] Implement Gogo, Master of Mimicry (#13686)

* [FIN] Implement Gogo, Master of Mimicry

* add test

* fix text

* move abstract method definition to interface where it belongs
This commit is contained in:
Evan Kranzler 2025-05-27 22:01:53 -04:00 committed by Failure
parent 70b1f23248
commit 56e9986b06
9 changed files with 149 additions and 0 deletions

View file

@ -0,0 +1,88 @@
package mage.cards.g;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.costs.mana.VariableManaCost;
import mage.abilities.dynamicvalue.common.GetXValue;
import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.FilterStackObject;
import mage.filter.common.FilterActivatedOrTriggeredAbility;
import mage.game.Game;
import mage.game.stack.StackObject;
import mage.target.common.TargetActivatedOrTriggeredAbility;
import mage.util.CardUtil;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class GogoMasterOfMimicry extends CardImpl {
private static final FilterStackObject filter
= new FilterActivatedOrTriggeredAbility("activated or triggered ability you control");
static {
filter.add(TargetController.YOU.getControllerPredicate());
}
public GogoMasterOfMimicry(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.WIZARD);
this.power = new MageInt(2);
this.toughness = new MageInt(4);
// {X}{X}, {T}: Copy target activated or triggered ability you control X times. You may choose new targets for the copy. This ability can't be copied, and X can't be 0.
Ability ability = new SimpleActivatedAbility(new GogoMasterOfMimicryCopyEffect(), new ManaCostsImpl<>("{X}{X}"));
ability.addCost(new TapSourceCost());
ability.addTarget(new TargetActivatedOrTriggeredAbility(filter));
CardUtil.castStream(ability.getCosts(), VariableManaCost.class).forEach(cost -> cost.setMinX(1));
this.addAbility(ability.withCanBeCopied(false));
}
private GogoMasterOfMimicry(final GogoMasterOfMimicry card) {
super(card);
}
@Override
public GogoMasterOfMimicry copy() {
return new GogoMasterOfMimicry(this);
}
}
class GogoMasterOfMimicryCopyEffect extends OneShotEffect {
GogoMasterOfMimicryCopyEffect() {
super(Outcome.Benefit);
staticText = "copy target activated or triggered ability you control X times. " +
"You may choose new targets for the copies. This ability can't be copied and X can't be 0";
}
private GogoMasterOfMimicryCopyEffect(final GogoMasterOfMimicryCopyEffect effect) {
super(effect);
}
@Override
public GogoMasterOfMimicryCopyEffect copy() {
return new GogoMasterOfMimicryCopyEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
int amount = GetXValue.instance.calculate(game, source, this);
StackObject stackObject = game.getStack().getStackObject(getTargetPointer().getFirst(game, source));
if (amount < 1 || stackObject == null) {
return false;
}
stackObject.createCopyOnStack(game, source, source.getControllerId(), true, amount);
return true;
}
}

View file

@ -221,6 +221,8 @@ public final class FinalFantasy extends ExpansionSet {
cards.add(new SetCardInfo("Giott, King of the Dwarves", 223, Rarity.UNCOMMON, mage.cards.g.GiottKingOfTheDwarves.class));
cards.add(new SetCardInfo("Gladiolus Amicitia", 224, Rarity.UNCOMMON, mage.cards.g.GladiolusAmicitia.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Gladiolus Amicitia", 489, Rarity.UNCOMMON, mage.cards.g.GladiolusAmicitia.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Gogo, Master of Mimicry", 377, Rarity.MYTHIC, mage.cards.g.GogoMasterOfMimicry.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Gogo, Master of Mimicry", 54, Rarity.MYTHIC, mage.cards.g.GogoMasterOfMimicry.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Gohn, Town of Ruin", 278, Rarity.COMMON, mage.cards.g.GohnTownOfRuin.class));
cards.add(new SetCardInfo("Gongaga, Reactor Town", 280, Rarity.COMMON, mage.cards.g.GongagaReactorTown.class));
cards.add(new SetCardInfo("Goobbue Gardener", 188, Rarity.COMMON, mage.cards.g.GoobbueGardener.class));

View file

@ -1,6 +1,9 @@
package org.mage.test.cards.copy;
import mage.abilities.MageSingleton;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.AdventureCard;
import mage.cards.Card;
@ -957,4 +960,23 @@ public class CopySpellTest extends CardTestPlayerBase {
setStopAt(1, PhaseStep.END_COMBAT);
execute();
}
private static final String engine = "Lithoform Engine";
@Test
public void testAbilityCantBeCopied() {
addCustomCardWithAbility("activator", playerA, new SimpleActivatedAbility(new GainLifeEffect(1), new TapSourceCost()).withCanBeCopied(false));
addCard(Zone.BATTLEFIELD, playerA, "Wastes", 2);
addCard(Zone.BATTLEFIELD, playerA, engine);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}", "stack ability ({T");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertTapped(engine, true);
assertLife(playerA, 20 + 1);
}
}

View file

@ -534,6 +534,10 @@ public interface Ability extends Controllable, Serializable {
boolean canFizzle();
Ability withCanBeCopied(boolean canBeCopied);
boolean canBeCopied();
/**
* Adds a target adjuster to this ability.
* If using a generic adjuster, only use after adding the blueprint target!

View file

@ -83,6 +83,7 @@ public abstract class AbilityImpl implements Ability {
private List<Watcher> watchers = new ArrayList<>(); // access to it by GetWatchers only (it can be overridden by some abilities)
private List<Ability> subAbilities = null;
private boolean canFizzle = true; // for Gilded Drake
private boolean canBeCopied = true;
private TargetAdjuster targetAdjuster = null;
private CostAdjuster costAdjuster = null;
private List<Hint> hints = new ArrayList<>();
@ -129,6 +130,7 @@ public abstract class AbilityImpl implements Ability {
this.flavorWord = ability.flavorWord;
this.sourceObjectZoneChangeCounter = ability.sourceObjectZoneChangeCounter;
this.canFizzle = ability.canFizzle;
this.canBeCopied = ability.canBeCopied;
this.targetAdjuster = ability.targetAdjuster;
this.costAdjuster = ability.costAdjuster;
this.hints = CardUtil.deepCopyObject(ability.hints);
@ -1733,6 +1735,17 @@ public abstract class AbilityImpl implements Ability {
this.canFizzle = canFizzle;
}
@Override
public boolean canBeCopied() {
return canBeCopied;
}
@Override
public Ability withCanBeCopied(boolean canBeCopied) {
this.canBeCopied = canBeCopied;
return this;
}
@Override
public AbilityImpl setTargetAdjuster(TargetAdjuster targetAdjuster) {
if (targetAdjuster instanceof GenericTargetAdjuster && this.getTargets().isEmpty()) {

View file

@ -1169,6 +1169,11 @@ public class Spell extends StackObjectImpl implements Card {
game.fireEvent(new CopiedStackObjectEvent(this, spellCopy, newControllerId));
}
@Override
public boolean canBeCopied() {
return this.getSpellAbility().canBeCopied();
}
@Override
public boolean isAllCreatureTypes(Game game) {
return card.isAllCreatureTypes(game);

View file

@ -718,6 +718,16 @@ public class StackAbility extends StackObjectImpl implements Ability {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public boolean canBeCopied() {
return ability.canBeCopied();
}
@Override
public Ability withCanBeCopied(boolean canBeCopied) {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public void createSingleCopy(UUID newControllerId, StackObjectCopyApplier applier, MageObjectReferencePredicate newTargetFilterPredicate, Game game, Ability source, boolean chooseNewTargets) {
Ability newAbility = this.ability.copy();

View file

@ -40,6 +40,8 @@ public interface StackObject extends MageObject, Controllable {
void createSingleCopy(UUID newControllerId, StackObjectCopyApplier applier, MageObjectReferencePredicate newTargetFilterPredicate, Game game, Ability source, boolean chooseNewTargets);
boolean canBeCopied();
boolean isTargetChanged();
void setTargetChanged(boolean targetChanged);

View file

@ -154,6 +154,9 @@ public abstract class StackObjectImpl implements StackObject {
@Override
public void createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets, int amount, StackObjectCopyApplier applier) {
if (!this.canBeCopied()) {
return;
}
GameEvent gameEvent = new CopyStackObjectEvent(source, this, newControllerId, amount);
if (game.replaceEvent(gameEvent)) {
return;