diff --git a/Mage.Sets/src/mage/cards/f/FractalHarness.java b/Mage.Sets/src/mage/cards/f/FractalHarness.java new file mode 100644 index 0000000000..775144b821 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FractalHarness.java @@ -0,0 +1,116 @@ +package mage.cards.f; + +import mage.abilities.Ability; +import mage.abilities.common.AttacksAttachedTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.common.ManacostVariableValue; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.EquipAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.QuandrixToken; +import mage.game.permanent.token.Token; +import mage.watchers.common.ManaSpentToCastWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FractalHarness extends CardImpl { + + public FractalHarness(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{X}{2}{G}"); + + this.subtype.add(SubType.EQUIPMENT); + + // When Fractal Harness enters the battlefield, create a 0/0 green and blue Fractal creature token. Put X +1/+1 counters on it and attach Fractal Harness to it. + this.addAbility(new EntersBattlefieldTriggeredAbility(new FractalHarnessTokenEffect()), new ManaSpentToCastWatcher()); + + // Whenever equipped creature attacks, double the number of +1/+1 counters on it. + this.addAbility(new AttacksAttachedTriggeredAbility( + new FractalHarnessDoubleEffect(), AttachmentType.EQUIPMENT, false + )); + + // Equip {2} + this.addAbility(new EquipAbility(2)); + } + + private FractalHarness(final FractalHarness card) { + super(card); + } + + @Override + public FractalHarness copy() { + return new FractalHarness(this); + } +} + +class FractalHarnessTokenEffect extends OneShotEffect { + + FractalHarnessTokenEffect() { + super(Outcome.Benefit); + staticText = "create a 0/0 green and blue Fractal creature token. " + + "Put X +1/+1 counters on it and attach {this} to it"; + } + + private FractalHarnessTokenEffect(final FractalHarnessTokenEffect effect) { + super(effect); + } + + @Override + public FractalHarnessTokenEffect copy() { + return new FractalHarnessTokenEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Token token = new QuandrixToken(); + token.putOntoBattlefield(1, game, source, source.getControllerId()); + int xValue = ManacostVariableValue.instance.calculate(game, source, this); + boolean flag = true; + for (UUID tokenId : token.getLastAddedTokenIds()) { + Permanent permanent = game.getPermanent(tokenId); + if (permanent == null) { + continue; + } + if (flag && permanent.addAttachment(tokenId, source, game)) { + flag = false; + } + permanent.addCounters(CounterType.P1P1.createInstance(xValue), source.getControllerId(), source, game); + } + return true; + } +} + +class FractalHarnessDoubleEffect extends OneShotEffect { + + FractalHarnessDoubleEffect() { + super(Outcome.Benefit); + staticText = "double the number of +1/+1 counters on it"; + } + + private FractalHarnessDoubleEffect(final FractalHarnessDoubleEffect effect) { + super(effect); + } + + @Override + public FractalHarnessDoubleEffect copy() { + return new FractalHarnessDoubleEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = (Permanent) getValue("attachedPermanent"); + return permanent != null && permanent.addCounters(CounterType.P1P1.createInstance( + permanent.getCounters(game).getCount(CounterType.P1P1) + ), source.getControllerId(), source, game); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GadwickTheWizened.java b/Mage.Sets/src/mage/cards/g/GadwickTheWizened.java index 342637a378..518703bb0c 100644 --- a/Mage.Sets/src/mage/cards/g/GadwickTheWizened.java +++ b/Mage.Sets/src/mage/cards/g/GadwickTheWizened.java @@ -5,22 +5,21 @@ import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SpellCastControllerTriggeredAbility; -import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.effects.Effect; +import mage.abilities.dynamicvalue.common.ManacostVariableValue; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.TapTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.TargetController; import mage.filter.FilterPermanent; import mage.filter.FilterSpell; import mage.filter.common.FilterNonlandPermanent; import mage.filter.predicate.mageobject.ColorPredicate; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.stack.Spell; import mage.target.TargetPermanent; -import mage.watchers.Watcher; +import mage.watchers.common.ManaSpentToCastWatcher; import java.util.UUID; @@ -50,8 +49,8 @@ public final class GadwickTheWizened extends CardImpl { // When Gadwick, the Wizened enters the battlefield, draw X cards. this.addAbility(new EntersBattlefieldTriggeredAbility( - new DrawCardSourceControllerEffect(GadwickTheWizenedValue.instance) - ), new GadwickTheWizenedWatcher()); + new DrawCardSourceControllerEffect(ManacostVariableValue.instance) + ), new ManaSpentToCastWatcher()); // Whenever you cast a blue spell, tap target nonland permanent an opponent controls. Ability ability = new SpellCastControllerTriggeredAbility(new TapTargetEffect(), filter, false); @@ -68,60 +67,3 @@ public final class GadwickTheWizened extends CardImpl { return new GadwickTheWizened(this); } } - -enum GadwickTheWizenedValue implements DynamicValue { - instance; - - @Override - public int calculate(Game game, Ability sourceAbility, Effect effect) { - // watcher in card's scope - GadwickTheWizenedWatcher watcher = game.getState().getWatcher(GadwickTheWizenedWatcher.class, sourceAbility.getSourceId()); - if (watcher == null) { - return 0; - } - if (game.getState().getValue(sourceAbility.getSourceId().toString() - + "cardsToDraw") == null) { - return 0; - } - return (Integer) game.getState().getValue(sourceAbility.getSourceId().toString() - + "cardsToDraw"); - } - - @Override - public DynamicValue copy() { - return instance; - } - - @Override - public String toString() { - return "X"; - } - - @Override - public String getMessage() { - return ""; - } -} - -class GadwickTheWizenedWatcher extends Watcher { - - GadwickTheWizenedWatcher() { - super(WatcherScope.CARD); - } - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() != GameEvent.EventType.SPELL_CAST) { - return; - } - Spell spell = game.getSpellOrLKIStack(event.getTargetId()); - if (spell == null) { - return; - } - if (spell.getSourceId() != super.getSourceId()) { - return; // the spell is not Gadwick, the Wizened - } - game.getState().setValue(spell.getSourceId().toString() - + "cardsToDraw", spell.getSpellAbility().getManaCostsToPay().getX()); - } -} diff --git a/Mage.Sets/src/mage/cards/n/NikoAris.java b/Mage.Sets/src/mage/cards/n/NikoAris.java index 2d5442fef7..8e529fec32 100644 --- a/Mage.Sets/src/mage/cards/n/NikoAris.java +++ b/Mage.Sets/src/mage/cards/n/NikoAris.java @@ -8,6 +8,7 @@ import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.MultipliedValue; import mage.abilities.dynamicvalue.common.CardsDrawnThisTurnDynamicValue; +import mage.abilities.dynamicvalue.common.ManacostVariableValue; import mage.abilities.effects.Effect; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.abilities.effects.common.CreateTokenEffect; @@ -16,18 +17,20 @@ import mage.abilities.effects.common.ReturnToHandTargetEffect; import mage.abilities.effects.common.combat.CantBeBlockedTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; import mage.filter.FilterPermanent; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.permanent.TappedPredicate; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.token.ShardToken; -import mage.game.stack.Spell; import mage.target.TargetPermanent; import mage.target.common.TargetControlledCreaturePermanent; -import mage.watchers.Watcher; import mage.watchers.common.CardsDrawnThisTurnWatcher; +import mage.watchers.common.ManaSpentToCastWatcher; import java.util.Objects; import java.util.UUID; @@ -54,8 +57,8 @@ public final class NikoAris extends CardImpl { // When Niko Aris enters the battlefield, create X Shard tokens. this.addAbility(new EntersBattlefieldTriggeredAbility( - new CreateTokenEffect(new ShardToken(), NikoArisValue.instance) - ), new NikoArisWatcher()); + new CreateTokenEffect(new ShardToken(), ManacostVariableValue.instance) + ), new ManaSpentToCastWatcher()); // +1: Up to one target creature you control can't be blocked this turn. Whenever that creature deals damage this turn, return it to its owner's hand. Ability ability = new LoyaltyAbility(new CantBeBlockedTargetEffect(Duration.EndOfTurn), 1); @@ -84,59 +87,6 @@ public final class NikoAris extends CardImpl { } } -enum NikoArisValue implements DynamicValue { - instance; - - @Override - public int calculate(Game game, Ability sourceAbility, Effect effect) { - NikoArisWatcher watcher = game.getState().getWatcher(NikoArisWatcher.class, sourceAbility.getSourceId()); - if (watcher == null) { - return 0; - } - if (game.getState().getValue(sourceAbility.getSourceId().toString() + "xValue") == null) { - return 0; - } - return (Integer) game.getState().getValue(sourceAbility.getSourceId().toString() + "xValue"); - } - - @Override - public DynamicValue copy() { - return instance; - } - - @Override - public String toString() { - return "X"; - } - - @Override - public String getMessage() { - return ""; - } -} - -class NikoArisWatcher extends Watcher { - - NikoArisWatcher() { - super(WatcherScope.CARD); - } - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() != GameEvent.EventType.SPELL_CAST) { - return; - } - Spell spell = game.getSpellOrLKIStack(event.getTargetId()); - if (spell == null || spell.getSourceId() != super.getSourceId()) { - return; - } - game.getState().setValue( - spell.getSourceId().toString() + "xValue", - spell.getSpellAbility().getManaCostsToPay().getX() - ); - } -} - class NikoArisDamageTriggeredAbility extends DelayedTriggeredAbility { NikoArisDamageTriggeredAbility() { @@ -176,4 +126,4 @@ class NikoArisDamageTriggeredAbility extends DelayedTriggeredAbility { public String getRule() { return "Whenever that creature deals damage this turn, return it to its owner's hand."; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/sets/Commander2021Edition.java b/Mage.Sets/src/mage/sets/Commander2021Edition.java index 8f962cf165..bf1867d8ed 100644 --- a/Mage.Sets/src/mage/sets/Commander2021Edition.java +++ b/Mage.Sets/src/mage/sets/Commander2021Edition.java @@ -128,6 +128,7 @@ public final class Commander2021Edition extends ExpansionSet { cards.add(new SetCardInfo("Fiery Fall", 170, Rarity.COMMON, mage.cards.f.FieryFall.class)); cards.add(new SetCardInfo("Forgotten Ancient", 189, Rarity.RARE, mage.cards.f.ForgottenAncient.class)); cards.add(new SetCardInfo("Forgotten Cave", 289, Rarity.COMMON, mage.cards.f.ForgottenCave.class)); + cards.add(new SetCardInfo("Fractal Harness", 61, Rarity.RARE, mage.cards.f.FractalHarness.class)); cards.add(new SetCardInfo("Garruk, Primal Hunter", 190, Rarity.MYTHIC, mage.cards.g.GarrukPrimalHunter.class)); cards.add(new SetCardInfo("Gaze of Granite", 217, Rarity.RARE, mage.cards.g.GazeOfGranite.class)); cards.add(new SetCardInfo("Geometric Nexus", 77, Rarity.RARE, mage.cards.g.GeometricNexus.class)); diff --git a/Mage/src/main/java/mage/abilities/common/AttacksAttachedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/AttacksAttachedTriggeredAbility.java index 4d8c1289fb..c6d7b212b1 100644 --- a/Mage/src/main/java/mage/abilities/common/AttacksAttachedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/AttacksAttachedTriggeredAbility.java @@ -1,7 +1,6 @@ package mage.abilities.common; -import java.util.Locale; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.constants.AttachmentType; @@ -10,6 +9,8 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; +import java.util.Locale; + /** * "When enchanted/equipped creature attacks " triggered ability * @@ -52,9 +53,8 @@ public class AttacksAttachedTriggeredAbility extends TriggeredAbilityImpl { Permanent equipment = game.getPermanent(this.sourceId); if (equipment != null && equipment.getAttachedTo() != null && event.getSourceId().equals(equipment.getAttachedTo())) { - for (Effect effect : this.getEffects()) { - effect.setValue("sourceId", event.getSourceId()); - } + getEffects().setValue("sourceId", event.getSourceId()); + getEffects().setValue("attachedPermanent", game.getPermanent(event.getSourceId())); return true; } return false; diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/ManacostVariableValue.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/ManacostVariableValue.java index bade65f544..fa7d812835 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/ManacostVariableValue.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/ManacostVariableValue.java @@ -3,14 +3,20 @@ package mage.abilities.dynamicvalue.common; import mage.abilities.Ability; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.effects.Effect; +import mage.constants.AbilityType; import mage.game.Game; +import mage.watchers.common.ManaSpentToCastWatcher; public enum ManacostVariableValue implements DynamicValue { instance; @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { - return sourceAbility.getManaCostsToPay().getX(); + if (sourceAbility.getAbilityType() == AbilityType.SPELL) { + return sourceAbility.getManaCostsToPay().getX(); + } + ManaSpentToCastWatcher watcher = game.getState().getWatcher(ManaSpentToCastWatcher.class, sourceAbility.getSourceId()); + return watcher != null ? watcher.getAndResetLastXValue() : sourceAbility.getManaCostsToPay().getX(); } @Override diff --git a/Mage/src/main/java/mage/watchers/common/ManaSpentToCastWatcher.java b/Mage/src/main/java/mage/watchers/common/ManaSpentToCastWatcher.java index b9da35f123..79b9ffdd03 100644 --- a/Mage/src/main/java/mage/watchers/common/ManaSpentToCastWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/ManaSpentToCastWatcher.java @@ -17,6 +17,7 @@ import mage.watchers.Watcher; public class ManaSpentToCastWatcher extends Watcher { private Mana payment = null; + private Integer xValue = 0; public ManaSpentToCastWatcher() { super(WatcherScope.CARD); @@ -29,12 +30,14 @@ public class ManaSpentToCastWatcher extends Watcher { Spell spell = (Spell) game.getObject(event.getTargetId()); if (spell != null && this.getSourceId().equals(spell.getSourceId())) { payment = spell.getSpellAbility().getManaCostsToPay().getUsedManaToPay(); + xValue = spell.getSpellAbility().getManaCostsToPay().getX(); } } if (event.getType() == GameEvent.EventType.ZONE_CHANGE && this.getSourceId().equals(event.getSourceId())) { if (((ZoneChangeEvent) event).getFromZone() == Zone.BATTLEFIELD) { payment = null; + xValue = 0; } } } @@ -45,13 +48,16 @@ public class ManaSpentToCastWatcher extends Watcher { returnPayment = payment.copy(); } return returnPayment; + } + public int getAndResetLastXValue() { + return xValue; } @Override public void reset() { super.reset(); payment = null; + xValue = 0; } - }