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 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/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/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()); 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); } 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 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)); 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