From f0b31dd58538e6cf2d30623a661d73df9c5a173a Mon Sep 17 00:00:00 2001 From: Jeff Wadsworth Date: Tue, 5 Oct 2021 15:48:39 -0500 Subject: [PATCH 1/6] - Fixed #8304 --- Mage.Sets/src/mage/cards/w/Willbreaker.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Mage.Sets/src/mage/cards/w/Willbreaker.java b/Mage.Sets/src/mage/cards/w/Willbreaker.java index f3c017deccb..f75f3face03 100644 --- a/Mage.Sets/src/mage/cards/w/Willbreaker.java +++ b/Mage.Sets/src/mage/cards/w/Willbreaker.java @@ -34,7 +34,9 @@ public final class Willbreaker extends CardImpl { this.toughness = new MageInt(3); // Whenever a creature an opponent controls becomes the target of a spell or ability you control, gain control of that creature for as long as you control Willbreaker. - ConditionalContinuousEffect effect = new ConditionalContinuousEffect(new GainControlTargetEffect(Duration.Custom), new SourceOnBattlefieldControlUnchangedCondition(), null); + ConditionalContinuousEffect effect = new ConditionalContinuousEffect( + new GainControlTargetEffect(Duration.EndOfGame), + new SourceOnBattlefieldControlUnchangedCondition(), null); effect.setText("gain control of that creature for as long as you control {this}"); this.addAbility(new WillbreakerTriggeredAbility(effect), new LostControlWatcher()); } @@ -68,10 +70,13 @@ class WillbreakerTriggeredAbility extends TriggeredAbilityImpl { public boolean checkTrigger(GameEvent event, Game game) { if (isControlledBy(event.getPlayerId())) { Permanent permanent = game.getPermanent(event.getTargetId()); - if (permanent != null && permanent.isCreature(game)) { + if (permanent != null + && permanent.isCreature(game)) { Player controller = game.getPlayer(getControllerId()); - if (controller != null && controller.hasOpponent(permanent.getControllerId(), game)) { - getEffects().get(0).setTargetPointer(new FixedTarget(event.getTargetId())); + if (controller != null + && controller.hasOpponent(permanent.getControllerId(), game)) { + // always call this method for FixedTargets in case it is blinked + getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game)); return true; } } @@ -81,7 +86,7 @@ class WillbreakerTriggeredAbility extends TriggeredAbilityImpl { @Override public String getTriggerPhrase() { - return "Whenever a creature an opponent controls becomes the target of a spell or ability you control, " ; + return "Whenever a creature an opponent controls becomes the target of a spell or ability you control, "; } @Override From 22493c22af6d5b03f63323e196c04265d9f6d38b Mon Sep 17 00:00:00 2001 From: Jeff Wadsworth Date: Tue, 5 Oct 2021 16:54:49 -0500 Subject: [PATCH 2/6] - Fixed #8299 --- ...uControlYourOpponentsWhileSearchingReplacementEffect.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Mage/src/main/java/mage/abilities/effects/common/replacement/YouControlYourOpponentsWhileSearchingReplacementEffect.java b/Mage/src/main/java/mage/abilities/effects/common/replacement/YouControlYourOpponentsWhileSearchingReplacementEffect.java index 4ee9f4d7235..27dbbe58d2b 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/replacement/YouControlYourOpponentsWhileSearchingReplacementEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/replacement/YouControlYourOpponentsWhileSearchingReplacementEffect.java @@ -38,7 +38,10 @@ public class YouControlYourOpponentsWhileSearchingReplacementEffect extends Repl @Override public boolean applies(GameEvent event, Ability source, Game game) { Player controller = game.getPlayer(source.getControllerId()); - return controller != null && game.isOpponent(controller, event.getPlayerId()); + return controller != null + && game.isOpponent(controller, event.getPlayerId()) + // verify that the controller of the ability is searching their library + && event.getTargetId().equals(event.getPlayerId()); } @Override From 71d588710646cb4b72b0763a750291d8fdac06e1 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Wed, 6 Oct 2021 12:01:34 +0400 Subject: [PATCH 3/6] Fixed unnecessary logs on app startup (#8373); --- Mage.Client/src/main/resources/ormliteLocalLog.properties | 2 ++ .../src/main/resources/ormliteLocalLog.properties | 2 ++ Mage.Server/src/main/resources/ormliteLocalLog.properties | 2 ++ 3 files changed, 6 insertions(+) create mode 100644 Mage.Client/src/main/resources/ormliteLocalLog.properties create mode 100644 Mage.Server.Console/src/main/resources/ormliteLocalLog.properties create mode 100644 Mage.Server/src/main/resources/ormliteLocalLog.properties diff --git a/Mage.Client/src/main/resources/ormliteLocalLog.properties b/Mage.Client/src/main/resources/ormliteLocalLog.properties new file mode 100644 index 00000000000..a5b91026191 --- /dev/null +++ b/Mage.Client/src/main/resources/ormliteLocalLog.properties @@ -0,0 +1,2 @@ +#workaround to remove un-wanted ormlite logs, see https://github.com/magefree/mage/issues/8373 +LogBackendType.*=ERROR \ No newline at end of file diff --git a/Mage.Server.Console/src/main/resources/ormliteLocalLog.properties b/Mage.Server.Console/src/main/resources/ormliteLocalLog.properties new file mode 100644 index 00000000000..a5b91026191 --- /dev/null +++ b/Mage.Server.Console/src/main/resources/ormliteLocalLog.properties @@ -0,0 +1,2 @@ +#workaround to remove un-wanted ormlite logs, see https://github.com/magefree/mage/issues/8373 +LogBackendType.*=ERROR \ No newline at end of file diff --git a/Mage.Server/src/main/resources/ormliteLocalLog.properties b/Mage.Server/src/main/resources/ormliteLocalLog.properties new file mode 100644 index 00000000000..a5b91026191 --- /dev/null +++ b/Mage.Server/src/main/resources/ormliteLocalLog.properties @@ -0,0 +1,2 @@ +#workaround to remove un-wanted ormlite logs, see https://github.com/magefree/mage/issues/8373 +LogBackendType.*=ERROR \ No newline at end of file From bce88bd39922287e71677c516b9146e3918cf5e8 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Wed, 6 Oct 2021 09:08:47 -0400 Subject: [PATCH 4/6] [MIC] Implemented Shadow Kin --- Mage.Sets/src/mage/cards/s/ShadowKin.java | 120 ++++++++++++++++++ .../src/mage/sets/MidnightHuntCommander.java | 1 + 2 files changed, 121 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/ShadowKin.java diff --git a/Mage.Sets/src/mage/cards/s/ShadowKin.java b/Mage.Sets/src/mage/cards/s/ShadowKin.java new file mode 100644 index 00000000000..003aee8d10f --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ShadowKin.java @@ -0,0 +1,120 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CopyEffect; +import mage.abilities.keyword.FlashAbility; +import mage.cards.*; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentCard; +import mage.players.Player; +import mage.target.common.TargetCardInGraveyard; +import mage.util.functions.CopyApplier; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ShadowKin extends CardImpl { + + public ShadowKin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); + + this.subtype.add(SubType.SHAPESHIFTER); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // At the beginning of your upkeep, each player mills three cards. You may exile a creature card from among the cards milled this way. If you do, Shadow Kin becomes a copy of that card, except it has this ability. + this.addAbility(new BeginningOfUpkeepTriggeredAbility( + new ShadowKinEffect(), TargetController.YOU, false + )); + } + + private ShadowKin(final ShadowKin card) { + super(card); + } + + @Override + public ShadowKin copy() { + return new ShadowKin(this); + } +} + +class ShadowKinEffect extends OneShotEffect { + + ShadowKinEffect() { + super(Outcome.Benefit); + staticText = "each player mills three cards. You may exile a creature card from among " + + "the cards milled this way. If you do, {this} becomes a copy of that card, except it has this ability"; + } + + private ShadowKinEffect(final ShadowKinEffect effect) { + super(effect); + } + + @Override + public ShadowKinEffect copy() { + return new ShadowKinEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + Cards cards = new CardsImpl(); + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Player player = game.getPlayer(playerId); + if (player != null) { + cards.addAll(player.millCards(3, source, game)); + } + } + if (cards.isEmpty()) { + return false; + } + TargetCardInGraveyard target = new TargetCardInGraveyard( + 0, 1, StaticFilters.FILTER_CARD_CREATURE + ); + target.setNotTarget(true); + controller.choose(outcome, cards, target, game); + Card card = game.getCard(target.getFirstTarget()); + Permanent sourcePermanent = source.getSourcePermanentIfItStillExists(game); + if (card == null || sourcePermanent == null) { + return true; + } + controller.moveCards(card, Zone.EXILED, source, game); + Permanent blueprint = new PermanentCard(card, source.getControllerId(), game); + blueprint.assignNewId(); + CopyApplier applier = new ShadowKinApplier(); + applier.apply(game, blueprint, source, sourcePermanent.getId()); + CopyEffect copyEffect = new CopyEffect(Duration.Custom, blueprint, sourcePermanent.getId()); + copyEffect.newId(); + copyEffect.setApplier(applier); + Ability newAbility = source.copy(); + copyEffect.init(newAbility, game); + game.addEffect(copyEffect, newAbility); + return true; + } +} + +class ShadowKinApplier extends CopyApplier { + + @Override + public boolean apply(Game game, MageObject blueprint, Ability source, UUID targetObjectId) { + blueprint.getAbilities().add(new BeginningOfUpkeepTriggeredAbility( + new ShadowKinEffect(), TargetController.YOU, false + )); + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/MidnightHuntCommander.java b/Mage.Sets/src/mage/sets/MidnightHuntCommander.java index 94c82fcad2f..6eaeca743dd 100644 --- a/Mage.Sets/src/mage/sets/MidnightHuntCommander.java +++ b/Mage.Sets/src/mage/sets/MidnightHuntCommander.java @@ -125,6 +125,7 @@ public final class MidnightHuntCommander extends ExpansionSet { cards.add(new SetCardInfo("Ruinous Intrusion", 28, Rarity.RARE, mage.cards.r.RuinousIntrusion.class)); cards.add(new SetCardInfo("Ruthless Deathfang", 154, Rarity.UNCOMMON, mage.cards.r.RuthlessDeathfang.class)); cards.add(new SetCardInfo("Selesnya Sanctuary", 180, Rarity.UNCOMMON, mage.cards.s.SelesnyaSanctuary.class)); + cards.add(new SetCardInfo("Shadow Kin", 16, Rarity.RARE, mage.cards.s.ShadowKin.class)); cards.add(new SetCardInfo("Shamanic Revelation", 143, Rarity.RARE, mage.cards.s.ShamanicRevelation.class)); cards.add(new SetCardInfo("Sigarda's Vanguard", 8, Rarity.RARE, mage.cards.s.SigardasVanguard.class)); cards.add(new SetCardInfo("Sigarda, Heron's Grace", 155, Rarity.MYTHIC, mage.cards.s.SigardaHeronsGrace.class)); From 19eb9f71229681e31bc5f605d892ef4b4b619e5c Mon Sep 17 00:00:00 2001 From: Jeff Wadsworth Date: Wed, 6 Oct 2021 16:28:27 -0500 Subject: [PATCH 5/6] - Fixed #8376 and #8285. There is still a pre-existing issue with the attacking trigger on the Fractal Harness, though. It runs through the apply twice so the counters are four times as many instead of two. --- Mage.Sets/src/mage/cards/f/FractalHarness.java | 18 ++++++++++++------ .../src/mage/cards/t/TeysaEnvoyOfGhosts.java | 4 ++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Mage.Sets/src/mage/cards/f/FractalHarness.java b/Mage.Sets/src/mage/cards/f/FractalHarness.java index 5d09fb97404..8978cdfdce8 100644 --- a/Mage.Sets/src/mage/cards/f/FractalHarness.java +++ b/Mage.Sets/src/mage/cards/f/FractalHarness.java @@ -56,8 +56,8 @@ 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"; + 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) { @@ -80,7 +80,8 @@ class FractalHarnessTokenEffect extends OneShotEffect { if (permanent == null) { continue; } - if (flag && permanent.addAttachment(tokenId, source, game)) { + if (flag + && permanent.addAttachment(source.getSourceId(), source, game)) { flag = false; } permanent.addCounters(CounterType.P1P1.createInstance(xValue), source.getControllerId(), source, game); @@ -108,8 +109,13 @@ class FractalHarnessDoubleEffect extends OneShotEffect { @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); + if (permanent == null) { + return false; + } + // BUG : changed this to a integer due to the trigger firing twice + final int addedCounters = permanent.getCounters(game).getCount(CounterType.P1P1); + // BUG : this oneShotEffect is being run twice for some reason, so the number of counters is four times as many + return permanent.addCounters(CounterType.P1P1.createInstance(addedCounters), + source.getControllerId(), source, game); } } diff --git a/Mage.Sets/src/mage/cards/t/TeysaEnvoyOfGhosts.java b/Mage.Sets/src/mage/cards/t/TeysaEnvoyOfGhosts.java index 965440553c3..28886c5a1dd 100644 --- a/Mage.Sets/src/mage/cards/t/TeysaEnvoyOfGhosts.java +++ b/Mage.Sets/src/mage/cards/t/TeysaEnvoyOfGhosts.java @@ -20,8 +20,8 @@ import mage.game.permanent.token.WhiteBlackSpiritToken; import mage.target.targetpointer.FixedTarget; import java.util.UUID; +import mage.filter.common.FilterCreatureCard; -import static mage.filter.StaticFilters.FILTER_PERMANENT_CREATURES; /** * @author LevelX2 @@ -41,7 +41,7 @@ public final class TeysaEnvoyOfGhosts extends CardImpl { this.addAbility(VigilanceAbility.getInstance()); // protection from creatures - this.addAbility(new ProtectionAbility(FILTER_PERMANENT_CREATURES)); + this.addAbility(new ProtectionAbility(new FilterCreatureCard("creatures"))); // Whenever a creature deals combat damage to you, destroy that creature. Create a 1/1 white and black Spirit creature token with flying. this.addAbility(new TeysaEnvoyOfGhostsTriggeredAbility()); From 4175b97464f151eb851eec13846b898f98f793d8 Mon Sep 17 00:00:00 2001 From: "Alex W. Jackson" Date: Wed, 6 Oct 2021 21:57:10 -0400 Subject: [PATCH 6/6] Fix Thunder of Hooves rules text --- Mage.Sets/src/mage/cards/t/ThunderOfHooves.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/t/ThunderOfHooves.java b/Mage.Sets/src/mage/cards/t/ThunderOfHooves.java index 28184f6ca9a..dc7b1c9515f 100644 --- a/Mage.Sets/src/mage/cards/t/ThunderOfHooves.java +++ b/Mage.Sets/src/mage/cards/t/ThunderOfHooves.java @@ -34,7 +34,7 @@ public final class ThunderOfHooves extends CardImpl { // Thunder of Hooves deals X damage to each creature without flying and each player, where X is the number of Beasts on the battlefield. Effect effect = new DamageEverythingEffect(new PermanentsOnBattlefieldCount(new FilterPermanent(filterBeasts)), new FilterCreaturePermanent(filterNotFlying)); - effect.setText("{this} deals X damage to each creature and each player, where X is the number of creatures on the battlefield"); + effect.setText("{this} deals X damage to each creature without flying and each player, where X is the number of Beasts on the battlefield"); this.getSpellAbility().addEffect(effect); }