From 5a1fb5f90eb65bbe4037eaf217ee2c657d76a046 Mon Sep 17 00:00:00 2001 From: jeffwadsworth Date: Sat, 30 Nov 2019 05:27:00 -0600 Subject: [PATCH 01/19] - Fixed #6059 --- Mage/src/main/java/mage/game/GameImpl.java | 23 +++++++++++----------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index f3f48ac143..6ec10a8a84 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -29,7 +29,6 @@ import mage.filter.Filter; import mage.filter.FilterCard; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; -import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.predicate.mageobject.NamePredicate; import mage.filter.predicate.mageobject.SupertypePredicate; import mage.filter.predicate.permanent.ControllerIdPredicate; @@ -69,6 +68,7 @@ import java.io.IOException; import java.io.Serializable; import java.util.*; import java.util.Map.Entry; +import mage.filter.common.FilterControlledPermanent; public abstract class GameImpl implements Game, Serializable { @@ -1011,7 +1011,6 @@ public abstract class GameImpl implements Game, Serializable { } } - public void initGameDefaultWatchers() { getState().addWatcher(new MorbidWatcher()); getState().addWatcher(new CastSpellLastTurnWatcher()); @@ -1465,7 +1464,7 @@ public abstract class GameImpl implements Game, Serializable { /** * @param emblem * @param sourceObject - * @param toPlayerId controller and owner of the emblem + * @param toPlayerId controller and owner of the emblem */ @Override public void addEmblem(Emblem emblem, MageObject sourceObject, UUID toPlayerId) { @@ -1483,8 +1482,8 @@ public abstract class GameImpl implements Game, Serializable { /** * @param plane * @param sourceObject - * @param toPlayerId controller and owner of the plane (may only be one per - * game..) + * @param toPlayerId controller and owner of the plane (may only be one per + * game..) * @return boolean - whether the plane was added successfully or not */ @Override @@ -1713,7 +1712,7 @@ public abstract class GameImpl implements Game, Serializable { break; } // triggered abilities that don't use the stack have to be executed first (e.g. Banisher Priest Return exiled creature - for (Iterator it = abilities.iterator(); it.hasNext(); ) { + for (Iterator it = abilities.iterator(); it.hasNext();) { TriggeredAbility triggeredAbility = it.next(); if (!triggeredAbility.isUsesStack()) { state.removeTriggeredAbility(triggeredAbility); @@ -1924,8 +1923,8 @@ public abstract class GameImpl implements Game, Serializable { } } else { Filter auraFilter = spellAbility.getTargets().get(0).getFilter(); - if (auraFilter instanceof FilterControlledCreaturePermanent) { - if (!((FilterControlledCreaturePermanent) auraFilter).match(attachedTo, perm.getId(), perm.getControllerId(), this) + if (auraFilter instanceof FilterControlledPermanent) { + if (!((FilterControlledPermanent) auraFilter).match(attachedTo, perm.getId(), perm.getControllerId(), this) || attachedTo.cantBeAttachedBy(perm, this)) { if (movePermanentToGraveyardWithInfo(perm)) { somethingHappened = true; @@ -2448,7 +2447,7 @@ public abstract class GameImpl implements Game, Serializable { } //20100423 - 800.4a Set toOutside = new HashSet<>(); - for (Iterator it = getBattlefield().getAllPermanents().iterator(); it.hasNext(); ) { + for (Iterator it = getBattlefield().getAllPermanents().iterator(); it.hasNext();) { Permanent perm = it.next(); if (perm.isOwnedBy(playerId)) { if (perm.getAttachedTo() != null) { @@ -2491,7 +2490,7 @@ public abstract class GameImpl implements Game, Serializable { player.moveCards(toOutside, Zone.OUTSIDE, null, this); // triggered abilities that don't use the stack have to be executed List abilities = state.getTriggered(player.getId()); - for (Iterator it = abilities.iterator(); it.hasNext(); ) { + for (Iterator it = abilities.iterator(); it.hasNext();) { TriggeredAbility triggeredAbility = it.next(); if (!triggeredAbility.isUsesStack()) { state.removeTriggeredAbility(triggeredAbility); @@ -2511,7 +2510,7 @@ public abstract class GameImpl implements Game, Serializable { // Remove cards from the player in all exile zones for (ExileZone exile : this.getExile().getExileZones()) { - for (Iterator it = exile.iterator(); it.hasNext(); ) { + for (Iterator it = exile.iterator(); it.hasNext();) { Card card = this.getCard(it.next()); if (card != null && card.isOwnedBy(playerId)) { it.remove(); @@ -2521,7 +2520,7 @@ public abstract class GameImpl implements Game, Serializable { //Remove all commander/emblems/plane the player controls boolean addPlaneAgain = false; - for (Iterator it = this.getState().getCommand().iterator(); it.hasNext(); ) { + for (Iterator it = this.getState().getCommand().iterator(); it.hasNext();) { CommandObject obj = it.next(); if (obj.isControlledBy(playerId)) { if (obj instanceof Emblem) { From dbf56a516055c4e45cd169968da1dfe80f13d185 Mon Sep 17 00:00:00 2001 From: jeffwadsworth Date: Sat, 30 Nov 2019 06:33:39 -0600 Subject: [PATCH 02/19] - Fixed #5956 --- Mage.Sets/src/mage/cards/v/VivienArkbowRanger.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Mage.Sets/src/mage/cards/v/VivienArkbowRanger.java b/Mage.Sets/src/mage/cards/v/VivienArkbowRanger.java index 29d488866d..e5c72823ab 100644 --- a/Mage.Sets/src/mage/cards/v/VivienArkbowRanger.java +++ b/Mage.Sets/src/mage/cards/v/VivienArkbowRanger.java @@ -18,9 +18,10 @@ import mage.counters.CounterType; import mage.filter.StaticFilters; import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetCreatureOrPlaneswalker; -import mage.target.common.TargetCreaturePermanentAmount; import java.util.UUID; +import mage.filter.common.FilterCreaturePermanent; +import mage.target.common.TargetCreaturePermanent; /** * @author TheElk801 @@ -42,13 +43,13 @@ public final class VivienArkbowRanger extends CardImpl { TrampleAbility.getInstance(), Duration.EndOfTurn, "They gain trample until end of turn" )); - ability.addTarget(new TargetCreaturePermanentAmount(2)); + ability.addTarget(new TargetCreaturePermanent(0, 2, new FilterCreaturePermanent(), false)); this.addAbility(ability); // −3: Target creature you control deals damage equal to its power to target creature or planeswalker. ability = new LoyaltyAbility( - new DamageWithPowerTargetEffect().setText("Target creature you control deals damage " + - "equal to its power to target creature or planeswalker."), -3 + new DamageWithPowerTargetEffect().setText("Target creature you control deals damage " + + "equal to its power to target creature or planeswalker."), -3 ); ability.addTarget(new TargetControlledCreaturePermanent()); ability.addTarget(new TargetCreatureOrPlaneswalker()); From fda5ac170a2dceb43cb2b9e8abae9cd532a94446 Mon Sep 17 00:00:00 2001 From: jeffwadsworth Date: Sat, 30 Nov 2019 06:58:52 -0600 Subject: [PATCH 03/19] - Fixed #5985 --- .../java/mage/watchers/common/ManaSpentToCastWatcher.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Mage/src/main/java/mage/watchers/common/ManaSpentToCastWatcher.java b/Mage/src/main/java/mage/watchers/common/ManaSpentToCastWatcher.java index 127574ca08..7654246820 100644 --- a/Mage/src/main/java/mage/watchers/common/ManaSpentToCastWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/ManaSpentToCastWatcher.java @@ -1,4 +1,3 @@ - package mage.watchers.common; import mage.Mana; @@ -30,13 +29,15 @@ public class ManaSpentToCastWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.SPELL_CAST && event.getZone() == Zone.HAND) { + // There was a check for the from zone being the hand, but that should not matter + if (event.getType() == GameEvent.EventType.SPELL_CAST) { Spell spell = (Spell) game.getObject(event.getTargetId()); if (spell != null && this.getSourceId().equals(spell.getSourceId())) { payment = spell.getSpellAbility().getManaCostsToPay().getPayment(); } } - if (event.getType() == GameEvent.EventType.ZONE_CHANGE && this.getSourceId().equals(event.getSourceId())) { + if (event.getType() == GameEvent.EventType.ZONE_CHANGE + && this.getSourceId().equals(event.getSourceId())) { if (((ZoneChangeEvent) event).getFromZone() == Zone.BATTLEFIELD) { payment = null; } From 76da8dd539b5344dc2818a824caab2f8beab6d47 Mon Sep 17 00:00:00 2001 From: Tosh94 Date: Sat, 30 Nov 2019 22:27:37 +0100 Subject: [PATCH 04/19] Fix NoSuchElementException when resolving Tectonic Hellion's triggered ability. (#6061) --- Mage.Sets/src/mage/cards/t/TectonicHellion.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Mage.Sets/src/mage/cards/t/TectonicHellion.java b/Mage.Sets/src/mage/cards/t/TectonicHellion.java index 0bb8208f51..d6757793bb 100644 --- a/Mage.Sets/src/mage/cards/t/TectonicHellion.java +++ b/Mage.Sets/src/mage/cards/t/TectonicHellion.java @@ -70,9 +70,8 @@ class TectonicHellionEffect extends OneShotEffect { Map landMap = new HashMap<>(); game.getState() .getPlayersInRange(source.getControllerId(), game) - .stream() - .map(uuid -> landMap.put(uuid, game.getBattlefield().getActivePermanents( - StaticFilters.FILTER_LAND, uuid, source.getSourceId(), game + .forEach(uuid -> landMap.put(uuid, game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_CONTROLLED_PERMANENT_LAND, uuid, source.getSourceId(), game ).size())); int max = landMap .values() From 2de7c136eabd84428771f95786d4a3ae1b3bf60b Mon Sep 17 00:00:00 2001 From: jeffwadsworth Date: Sun, 1 Dec 2019 21:53:01 -0600 Subject: [PATCH 05/19] - Fixed #6056. Please test when you can. Now you will see other abilities/spellAbilities from cards presented during the cast from exile. Overload, Emerge, Surge, etc. --- .../src/mage/player/human/HumanPlayer.java | 59 +++++++++++---- .../mage/cards/c/ChandraTorchOfDefiance.java | 13 +++- .../java/org/mage/test/player/TestPlayer.java | 5 ++ .../java/org/mage/test/stub/PlayerStub.java | 5 ++ .../java/mage/abilities/SpellAbility.java | 9 ++- .../mage/abilities/keyword/EmergeAbility.java | 3 +- .../abilities/keyword/FlashbackAbility.java | 9 ++- .../abilities/keyword/SpectacleAbility.java | 5 +- .../mage/abilities/keyword/SurgeAbility.java | 5 +- Mage/src/main/java/mage/players/Player.java | 75 ++++++++++--------- .../main/java/mage/players/PlayerImpl.java | 13 +++- 11 files changed, 135 insertions(+), 66 deletions(-) diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index d2c615b80b..25e018a02e 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -710,9 +710,9 @@ public class HumanPlayer extends PlayerImpl { if (!isExecutingMacro()) { String selectedNames = target.getTargetedName(game); game.fireSelectTargetEvent(playerId, new MessageToClient(target.getMessage() - + "
Amount remaining: " + target.getAmountRemaining() - + (selectedNames.isEmpty() ? "" : ", selected: " + selectedNames), - getRelatedObjectName(source, game)), + + "
Amount remaining: " + target.getAmountRemaining() + + (selectedNames.isEmpty() ? "" : ", selected: " + selectedNames), + getRelatedObjectName(source, game)), target.possibleTargets(source == null ? null : source.getSourceId(), playerId, game), target.isRequired(source), getOptions(target, null)); @@ -725,7 +725,7 @@ public class HumanPlayer extends PlayerImpl { boolean removeMode = target.getTargets().contains(targetId) && chooseUse(outcome, "What do you want to do with " + (targetObject != null ? targetObject.getLogName() : "target") + "?", "", - "Remove from selected", "Add extra amount", source, game); + "Remove from selected", "Add extra amount", source, game); if (removeMode) { target.remove(targetId); @@ -862,9 +862,9 @@ public class HumanPlayer extends PlayerImpl { if (!skippedAtLeastOnce || (playerId.equals(game.getActivePlayerId()) && !controllingPlayer - .getUserData() - .getUserSkipPrioritySteps() - .isStopOnAllEndPhases())) { + .getUserData() + .getUserSkipPrioritySteps() + .isStopOnAllEndPhases())) { skippedAtLeastOnce = true; if (passWithManaPoolCheck(game)) { return false; @@ -896,9 +896,9 @@ public class HumanPlayer extends PlayerImpl { if (haveNewObjectsOnStack && (playerId.equals(game.getActivePlayerId()) && controllingPlayer - .getUserData() - .getUserSkipPrioritySteps() - .isStopOnStackNewObjects())) { + .getUserData() + .getUserSkipPrioritySteps() + .isStopOnStackNewObjects())) { // new objects on stack -- disable "pass until stack resolved" passedUntilStackResolved = false; } else { @@ -975,7 +975,9 @@ public class HumanPlayer extends PlayerImpl { } } return result; - } else return response.getManaType() == null; + } else { + return response.getManaType() == null; + } return true; } return false; @@ -1233,8 +1235,8 @@ public class HumanPlayer extends PlayerImpl { if (passedAllTurns || passedUntilEndStepBeforeMyTurn || (!getControllingPlayersUserData(game) - .getUserSkipPrioritySteps() - .isStopOnDeclareAttackers() + .getUserSkipPrioritySteps() + .isStopOnDeclareAttackers() && (passedTurn || passedTurnSkipStack || passedUntilEndOfTurn @@ -1255,8 +1257,7 @@ public class HumanPlayer extends PlayerImpl { return; } } - */ - + */ Map options = new HashMap<>(); options.put(Constants.Option.POSSIBLE_ATTACKERS, (Serializable) possibleAttackers); if (!possibleAttackers.isEmpty()) { @@ -1418,7 +1419,7 @@ public class HumanPlayer extends PlayerImpl { /** * Selects a defender for an attacker and adds the attacker to combat * - * @param defenders - list of possible defender + * @param defenders - list of possible defender * @param attackerId - UUID of attacker * @param game * @return @@ -1782,6 +1783,31 @@ public class HumanPlayer extends PlayerImpl { } } + @Override + public SpellAbility chooseAbilityForCast(Card card, Game game, boolean nonMana) { + MageObject object = game.getObject(card.getId()); + if (object != null) { + LinkedHashMap useableAbilities = getSpellAbilities(object, game.getState().getZone(object.getId()), game); + if (useableAbilities != null + && useableAbilities.size() == 1) { + return (SpellAbility) useableAbilities.values().iterator().next(); + } else if (useableAbilities != null + && !useableAbilities.isEmpty()) { + prepareForResponse(game); + if (!isExecutingMacro()) { + game.fireGetChoiceEvent(playerId, name, object, new ArrayList<>(useableAbilities.values())); + } + waitForResponse(game); + if (response.getUUID() != null) { + if (useableAbilities.containsKey(response.getUUID())) { + return (SpellAbility) useableAbilities.get(response.getUUID()); + } + } + } + } + return card.getSpellAbility(); + } + @Override public Mode chooseMode(Modes modes, Ability source, Game game) { // choose mode to activate @@ -2115,4 +2141,5 @@ public class HumanPlayer extends PlayerImpl { public String getHistory() { return "no available"; } + } diff --git a/Mage.Sets/src/mage/cards/c/ChandraTorchOfDefiance.java b/Mage.Sets/src/mage/cards/c/ChandraTorchOfDefiance.java index 95fd6740e5..1ecb741f4b 100644 --- a/Mage.Sets/src/mage/cards/c/ChandraTorchOfDefiance.java +++ b/Mage.Sets/src/mage/cards/c/ChandraTorchOfDefiance.java @@ -21,6 +21,7 @@ import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.SuperType; import mage.constants.TargetController; +import mage.constants.Zone; import mage.game.Game; import mage.game.command.emblems.ChandraTorchOfDefianceEmblem; import mage.players.Library; @@ -91,15 +92,19 @@ class ChandraTorchOfDefianceEffect extends OneShotEffect { if (card != null) { boolean exiledCardWasCast = false; controller.moveCardsToExile(card, source, game, true, source.getSourceId(), sourceObject.getIdName()); - if (!card.getManaCost().isEmpty() && !card.isLand()) { - if (controller.chooseUse(Outcome.Benefit, "Cast " + card.getName() + "? (You still pay the costs)", source, game)) { - exiledCardWasCast = controller.cast(card.getSpellAbility(), game, false, new MageObjectReference(sourceObject, game)); + if (!card.getManaCost().isEmpty() + || !card.isLand()) { + if (controller.chooseUse(Outcome.Benefit, "Cast " + card.getName() + "? (You still pay the costs)", source, game) + && (game.getState().getZone(card.getId()) == Zone.EXILED)) { // card must be in the exile zone + game.getState().setValue("CastFromExileEnabled" + card.getId(), Boolean.TRUE); // enable the card to be cast from the exile zone + exiledCardWasCast = controller.cast(controller.chooseAbilityForCast(card, game, false), + game, false, new MageObjectReference(sourceObject, game)); + game.getState().setValue("CastFromExileEnabled" + card.getId(), Boolean.FALSE); // reset to false } } if (!exiledCardWasCast) { new DamagePlayersEffect(Outcome.Damage, new StaticValue(2), TargetController.OPPONENT).apply(game, source); } - } return true; } diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index 5c3bd9efab..5c0e75f141 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -3478,4 +3478,9 @@ public class TestPlayer implements Player { public FilterMana getPhyrexianColors() { return computerPlayer.getPhyrexianColors(); } + + @Override + public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) { + return card.getSpellAbility(); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java index 34e6f69832..97465f1770 100644 --- a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java +++ b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java @@ -1386,5 +1386,10 @@ public class PlayerStub implements Player { public FilterMana getPhyrexianColors() { return (new FilterMana()); } + + @Override + public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) { + return card.getSpellAbility(); + } } diff --git a/Mage/src/main/java/mage/abilities/SpellAbility.java b/Mage/src/main/java/mage/abilities/SpellAbility.java index 2256c0c82f..0a76898c13 100644 --- a/Mage/src/main/java/mage/abilities/SpellAbility.java +++ b/Mage/src/main/java/mage/abilities/SpellAbility.java @@ -63,6 +63,9 @@ public class SpellAbility extends ActivatedAbilityImpl { */ public boolean spellCanBeActivatedRegularlyNow(UUID playerId, Game game) { MageObject object = game.getObject(sourceId); + if ((Boolean) game.getState().getValue("CastFromExileEnabled" + object.getId()) != null) { + return (Boolean) game.getState().getValue("CastFromExileEnabled" + object.getId()); // card like Chandra, Torch of Defiance +1 loyal ability) + } return null != game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.CAST_AS_INSTANT, this, playerId, game) // check this first to allow Offering in main phase || timing == TimingRule.INSTANT || object.hasAbility(FlashAbility.getInstance().getId(), game) @@ -72,7 +75,8 @@ public class SpellAbility extends ActivatedAbilityImpl { @Override public ActivationStatus canActivate(UUID playerId, Game game) { if (this.spellCanBeActivatedRegularlyNow(playerId, game)) { - if (spellAbilityType == SpellAbilityType.SPLIT || spellAbilityType == SpellAbilityType.SPLIT_AFTERMATH) { + if (spellAbilityType == SpellAbilityType.SPLIT + || spellAbilityType == SpellAbilityType.SPLIT_AFTERMATH) { return ActivationStatus.getFalse(); } // fix for Gitaxian Probe and casting opponent's spells @@ -93,7 +97,8 @@ public class SpellAbility extends ActivatedAbilityImpl { // Alternate spell abilities (Flashback, Overload) can't be cast with no mana to pay option if (getSpellAbilityType() == SpellAbilityType.BASE_ALTERNATE) { Player player = game.getPlayer(playerId); - if (player != null && getSourceId().equals(player.getCastSourceIdWithAlternateMana())) { + if (player != null + && getSourceId().equals(player.getCastSourceIdWithAlternateMana())) { return ActivationStatus.getFalse(); } } diff --git a/Mage/src/main/java/mage/abilities/keyword/EmergeAbility.java b/Mage/src/main/java/mage/abilities/keyword/EmergeAbility.java index 24ade34422..c11ec9da55 100644 --- a/Mage/src/main/java/mage/abilities/keyword/EmergeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/EmergeAbility.java @@ -53,7 +53,8 @@ public class EmergeAbility extends SpellAbility { if (super.canActivate(playerId, game).canActivate()) { Player controller = game.getPlayer(this.getControllerId()); if (controller != null) { - for (Permanent creature : game.getBattlefield().getActivePermanents(new FilterControlledCreaturePermanent(), this.getControllerId(), this.getSourceId(), game)) { + for (Permanent creature : game.getBattlefield().getActivePermanents( + new FilterControlledCreaturePermanent(), this.getControllerId(), this.getSourceId(), game)) { ManaCost costToPay = CardUtil.reduceCost(emergeCost.copy(), creature.getConvertedManaCost()); if (costToPay.canPay(this, this.getSourceId(), this.getControllerId(), game)) { return ActivationStatus.getTrue(); diff --git a/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java b/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java index ba192daadf..145257ae4b 100644 --- a/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java @@ -57,10 +57,13 @@ public class FlashbackAbility extends SpellAbility { @Override public ActivationStatus canActivate(UUID playerId, Game game) { - ActivationStatus activationStatus = super.canActivate(playerId, game); - if (activationStatus.canActivate()) { + if (super.canActivate(playerId, game).canActivate()) { Card card = game.getCard(getSourceId()); if (card != null) { + // Card must be in the graveyard zone + if (game.getState().getZone(card.getId()) != Zone.GRAVEYARD) { + return ActivationStatus.getFalse(); + } // Cards with no Mana Costs cant't be flashbacked (e.g. Ancestral Vision) if (card.getManaCost().isEmpty()) { return ActivationStatus.getFalse(); @@ -76,7 +79,7 @@ public class FlashbackAbility extends SpellAbility { return card.getSpellAbility().canActivate(playerId, game); } } - return activationStatus; + return ActivationStatus.getFalse(); } @Override diff --git a/Mage/src/main/java/mage/abilities/keyword/SpectacleAbility.java b/Mage/src/main/java/mage/abilities/keyword/SpectacleAbility.java index 393da2251e..531f3b6304 100644 --- a/Mage/src/main/java/mage/abilities/keyword/SpectacleAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/SpectacleAbility.java @@ -45,8 +45,9 @@ public class SpectacleAbility extends SpellAbility { @Override public ActivationStatus canActivate(UUID playerId, Game game) { - if (OpponentsLostLifeCount.instance.calculate(game, playerId) > 0) { - return super.canActivate(playerId, game); + if (OpponentsLostLifeCount.instance.calculate(game, playerId) > 0 + && super.canActivate(playerId, game).canActivate()) { + return ActivationStatus.getTrue(); } return ActivationStatus.getFalse(); } diff --git a/Mage/src/main/java/mage/abilities/keyword/SurgeAbility.java b/Mage/src/main/java/mage/abilities/keyword/SurgeAbility.java index f1a335bf8e..4d3a17be18 100644 --- a/Mage/src/main/java/mage/abilities/keyword/SurgeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/SurgeAbility.java @@ -51,8 +51,9 @@ public class SurgeAbility extends SpellAbility { if (player != null) { for (UUID playerToCheckId : game.getState().getPlayersInRange(playerId, game)) { if (!player.hasOpponent(playerToCheckId, game)) { - if (watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(playerToCheckId) > 0) { - return super.canActivate(playerId, game); + if (watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(playerToCheckId) > 0 + && super.canActivate(playerId, game).canActivate()) { + return ActivationStatus.getTrue(); } } } diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index 9220f50835..1e8ddbb897 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -75,7 +75,7 @@ public interface Player extends MageItem, Copyable { void setLife(int life, Game game, UUID sourceId); /** - * @param amount amount of life loss + * @param amount amount of life loss * @param game * @param atCombat was the source combat damage * @return @@ -311,7 +311,7 @@ public interface Player extends MageItem, Copyable { void useDeck(Deck deck, Game game); /** - * Called before each applyEffects, to rest all what can be applyed by + * Called before each applyEffects, to rest all what can be applied by * continuous effects */ void reset(); @@ -326,6 +326,8 @@ public interface Player extends MageItem, Copyable { SpellAbility chooseSpellAbilityForCast(SpellAbility ability, Game game, boolean noMana); + SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana); + boolean putInHand(Card card, Game game); boolean removeFromHand(Card card, Game game); @@ -349,14 +351,15 @@ public interface Player extends MageItem, Copyable { * @param source * @param game * @param targetPlayerId player whose library will be searched - * @param triggerEvents whether searching will trigger any game events + * @param triggerEvents whether searching will trigger any game events * @return true if search was successful */ boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId, boolean triggerEvents); /** - * Reveals all players' libraries. Useful for abilities like Jace, Architect of Thought's -8 - * that have effects that require information from all libraries. + * Reveals all players' libraries. Useful for abilities like Jace, Architect + * of Thought's -8 that have effects that require information from all + * libraries. * * @param source * @param game @@ -369,23 +372,23 @@ public interface Player extends MageItem, Copyable { /** * Plays a card if possible * - * @param card the card that can be cast + * @param card the card that can be cast * @param game - * @param noMana if it's a spell i can be cast without paying mana + * @param noMana if it's a spell i can be cast without paying mana * @param ignoreTiming if it's cast during the resolution of another spell - * no sorcery or play land timing restriction are checked. For a land it has - * to be the turn of the player playing that card. - * @param reference mage object that allows to play the card + * no sorcery or play land timing restriction are checked. For a land it has + * to be the turn of the player playing that card. + * @param reference mage object that allows to play the card * @return */ boolean playCard(Card card, Game game, boolean noMana, boolean ignoreTiming, MageObjectReference reference); /** - * @param card the land card to play + * @param card the land card to play * @param game * @param ignoreTiming false - it won't be checked if the stack is empty and - * you are able to play a Sorcery. It's still checked, if you are able to - * play a land concerning the number of lands you already played. + * you are able to play a Sorcery. It's still checked, if you are able to + * play a land concerning the number of lands you already played. * @return */ boolean playLand(Card card, Game game, boolean ignoreTiming); @@ -531,11 +534,11 @@ public interface Player extends MageItem, Copyable { /** * Moves the cards from cards to the bottom of the players library. * - * @param cards - list of cards that have to be moved - * @param game - game + * @param cards - list of cards that have to be moved + * @param game - game * @param anyOrder - true if player can determine the order of the cards - * else random order - * @param source - source ability + * else random order + * @param source - source ability * @return */ boolean putCardsOnBottomOfLibrary(Cards cards, Game game, Ability source, boolean anyOrder); @@ -556,10 +559,10 @@ public interface Player extends MageItem, Copyable { /** * Moves the cards from cards to the top of players library. * - * @param cards - list of cards that have to be moved - * @param game - game + * @param cards - list of cards that have to be moved + * @param game - game * @param anyOrder - true if player can determine the order of the cards - * @param source - source ability + * @param source - source ability * @return */ boolean putCardsOnTopOfLibrary(Cards cards, Game game, Ability source, boolean anyOrder); @@ -590,8 +593,8 @@ public interface Player extends MageItem, Copyable { /** * Choose the order in which blockers get damage assigned to * - * @param blockers list of blockers where to choose the next one from - * @param combatGroup the concerning combat group + * @param blockers list of blockers where to choose the next one from + * @param combatGroup the concerning combat group * @param blockerOrder the already set order of blockers * @param game * @return blocker next to add to the blocker order @@ -660,7 +663,8 @@ public interface Player extends MageItem, Copyable { * * @param card * @param game - * @param abilitiesToActivate extra info about abilities that can be activated on NO option + * @param abilitiesToActivate extra info about abilities that can be + * activated on NO option * @return player looked at the card */ boolean lookAtFaceDownCard(Card card, Game game, int abilitiesToActivate); @@ -700,8 +704,8 @@ public interface Player extends MageItem, Copyable { void addCommanderId(UUID commanderId); /** - * Get the commanderIds of the player - * Deprecated, use game.getCommandersIds(xxx) instead + * Get the commanderIds of the player Deprecated, use + * game.getCommandersIds(xxx) instead * * @return */ @@ -733,11 +737,11 @@ public interface Player extends MageItem, Copyable { * @param toZone * @param source * @param game - * @param tapped the cards are tapped on the battlefield - * @param faceDown the cards are face down in the to zone - * @param byOwner the card is moved (or put onto battlefield) by the owner - * of the card and if target zone is battlefield controls the permanent - * (instead of the controller of the source) + * @param tapped the cards are tapped on the battlefield + * @param faceDown the cards are face down in the to zone + * @param byOwner the card is moved (or put onto battlefield) by the owner + * of the card and if target zone is battlefield controls the permanent + * (instead of the controller of the source) * @param appliedEffects * @return */ @@ -773,7 +777,7 @@ public interface Player extends MageItem, Copyable { * list of applied effects is not saved * * @param card - * @param exileId exile zone id (optional) + * @param exileId exile zone id (optional) * @param exileName name of exile zone (optional) * @param sourceId * @param game @@ -815,7 +819,7 @@ public interface Player extends MageItem, Copyable { * @param sourceId * @param game * @param fromZone if null, this info isn't postet - * @param toTop to the top of the library else to the bottom + * @param toTop to the top of the library else to the bottom * @param withName show the card name in the log * @return */ @@ -840,10 +844,10 @@ public interface Player extends MageItem, Copyable { * without mana (null) or the mana set to manaCosts instead of its normal * mana costs. * - * @param sourceId the source that can be cast without mana + * @param sourceId the source that can be cast without mana * @param manaCosts alternate ManaCost, null if it can be cast without mana - * cost - * @param costs alternate other costs you need to pay + * cost + * @param costs alternate other costs you need to pay */ void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts manaCosts, Costs costs); @@ -900,4 +904,5 @@ public interface Player extends MageItem, Copyable { void removePhyrexianFromColors(FilterMana colors); FilterMana getPhyrexianColors(); + } diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 05a7aa386f..b34fe4f901 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -1462,6 +1462,12 @@ public abstract class PlayerImpl implements Player, Serializable { for (Ability ability : object.getAbilities()) { if (ability instanceof SpellAbility) { switch (((SpellAbility) ability).getSpellAbilityType()) { + case BASE_ALTERNATE: + ActivationStatus as = ((SpellAbility) ability).canActivate(playerId, game); + if (as.canActivate()) { + useable.put(ability.getId(), (SpellAbility) ability); // example: Chandra, Torch of Defiance +1 loyal ability + } + return useable; case SPLIT_FUSED: if (zone == Zone.HAND) { if (ability.canChooseTarget(game)) { @@ -1502,7 +1508,7 @@ public abstract class PlayerImpl implements Player, Serializable { // Get the usable activated abilities for a *single card object*, that is, either a card or half of a split card. // Also called on the whole split card but only passing the fuse ability and other whole-split-card shared abilities // as candidates. - private void getUseableActivatedAbilitiesHalfImpl(MageObject object, Zone zone, Game game, Abilities candidateAbilites, + private void getUseableActivatedAbilitiesHalfImpl(MageObject object, Zone zone, Game game, Abilities candidateAbilites, LinkedHashMap output) { boolean canUse = !(object instanceof Permanent) || ((Permanent) object).canUseActivatedAbilities(game); ManaOptions availableMana = null; @@ -4345,4 +4351,9 @@ public abstract class PlayerImpl implements Player, Serializable { public FilterMana getPhyrexianColors() { return this.phyrexianColors; } + + @Override + public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) { + return card.getSpellAbility(); + } } From 1da15ee8fa1f7ca9f246bd61fcb144f509476133 Mon Sep 17 00:00:00 2001 From: jeffwadsworth Date: Mon, 2 Dec 2019 09:38:51 -0600 Subject: [PATCH 06/19] - Fix for last commit. Note comment in that commit. https://github.com/magefree/mage/commit/2de7c136eabd84428771f95786d4a3ae1b3bf60b --- Mage.Sets/src/mage/cards/c/ChandraTorchOfDefiance.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/c/ChandraTorchOfDefiance.java b/Mage.Sets/src/mage/cards/c/ChandraTorchOfDefiance.java index 1ecb741f4b..af11f72d48 100644 --- a/Mage.Sets/src/mage/cards/c/ChandraTorchOfDefiance.java +++ b/Mage.Sets/src/mage/cards/c/ChandraTorchOfDefiance.java @@ -99,7 +99,7 @@ class ChandraTorchOfDefianceEffect extends OneShotEffect { game.getState().setValue("CastFromExileEnabled" + card.getId(), Boolean.TRUE); // enable the card to be cast from the exile zone exiledCardWasCast = controller.cast(controller.chooseAbilityForCast(card, game, false), game, false, new MageObjectReference(sourceObject, game)); - game.getState().setValue("CastFromExileEnabled" + card.getId(), Boolean.FALSE); // reset to false + game.getState().setValue("CastFromExileEnabled" + card.getId(), null); // reset to null } } if (!exiledCardWasCast) { From b4be2e5f4a150c477e86d3a0fc4d0232655fb612 Mon Sep 17 00:00:00 2001 From: jeffwadsworth Date: Mon, 2 Dec 2019 12:00:19 -0600 Subject: [PATCH 07/19] - Fix for Vivien, Arkbow Ranger --- .../src/mage/cards/v/VivienArkbowRanger.java | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/v/VivienArkbowRanger.java b/Mage.Sets/src/mage/cards/v/VivienArkbowRanger.java index e5c72823ab..b6f7107c14 100644 --- a/Mage.Sets/src/mage/cards/v/VivienArkbowRanger.java +++ b/Mage.Sets/src/mage/cards/v/VivienArkbowRanger.java @@ -18,10 +18,13 @@ import mage.counters.CounterType; import mage.filter.StaticFilters; import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetCreatureOrPlaneswalker; +import mage.target.common.TargetCreaturePermanentAmount; import java.util.UUID; import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; import mage.target.common.TargetCreaturePermanent; +import mage.target.targetadjustment.TargetAdjuster; /** * @author TheElk801 @@ -37,13 +40,13 @@ public final class VivienArkbowRanger extends CardImpl { // +1: Distribute two +1/+1 counters among up to two target creatures. They gain trample until end of turn. Ability ability = new LoyaltyAbility(new DistributeCountersEffect( - CounterType.P1P1, 2, false, "up to two target creatures" - ), 1); + CounterType.P1P1, 2, false, "up to two target creatures"), 1); ability.addEffect(new GainAbilityTargetEffect( TrampleAbility.getInstance(), Duration.EndOfTurn, "They gain trample until end of turn" )); ability.addTarget(new TargetCreaturePermanent(0, 2, new FilterCreaturePermanent(), false)); + ability.setTargetAdjuster(VivienArkbowRangerAdjuster.instance); this.addAbility(ability); // −3: Target creature you control deals damage equal to its power to target creature or planeswalker. @@ -67,4 +70,17 @@ public final class VivienArkbowRanger extends CardImpl { public VivienArkbowRanger copy() { return new VivienArkbowRanger(this); } + + enum VivienArkbowRangerAdjuster implements TargetAdjuster { + instance; + + @Override + public void adjustTargets(Ability ability, Game game) { + // if targets are available, switch over to a working target method + if (game.getBattlefield().getAllActivePermanents(new FilterCreaturePermanent(), game).size() > 0) { + ability.getTargets().clear(); + ability.addTarget(new TargetCreaturePermanentAmount(2)); + } + } + } } From a033150de67e341e1cc547812cd6d4e73b8bdd34 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Mon, 2 Dec 2019 17:57:07 -0500 Subject: [PATCH 08/19] updated Pioneer ban list --- .../Mage.Deck.Constructed/src/mage/deck/Pioneer.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Pioneer.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Pioneer.java index 8ecf14fbe7..0769631e64 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Pioneer.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Pioneer.java @@ -28,8 +28,11 @@ public class Pioneer extends Constructed { banned.add("Windswept Heath"); banned.add("Wooded Foothills"); banned.add("Felidar Guardian"); + banned.add("Field of the Dead"); banned.add("Leyline of Abundance"); banned.add("Oath of Nissa"); + banned.add("Once Upon a Time"); + banned.add("Smuggler's Copter"); banned.add("Veil of Summer"); } } From f21151bca596e559797ca1439cf878d00385e310 Mon Sep 17 00:00:00 2001 From: jeffwadsworth Date: Tue, 3 Dec 2019 16:05:30 -0600 Subject: [PATCH 09/19] - Fixed #6065 --- .../decorator/ConditionalInterveningIfTriggeredAbility.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalInterveningIfTriggeredAbility.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalInterveningIfTriggeredAbility.java index 44a96ae386..c84a9871c6 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalInterveningIfTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalInterveningIfTriggeredAbility.java @@ -37,6 +37,7 @@ public class ConditionalInterveningIfTriggeredAbility extends TriggeredAbilityIm this.modes = ability.getModes(); this.condition = condition; this.abilityText = text; + this.watchers = ability.getWatchers(); } public ConditionalInterveningIfTriggeredAbility(final ConditionalInterveningIfTriggeredAbility triggered) { @@ -44,6 +45,7 @@ public class ConditionalInterveningIfTriggeredAbility extends TriggeredAbilityIm this.ability = triggered.ability.copy(); this.condition = triggered.condition; this.abilityText = triggered.abilityText; + this.watchers = triggered.watchers; } @Override @@ -100,5 +102,4 @@ public class ConditionalInterveningIfTriggeredAbility extends TriggeredAbilityIm public boolean isOptional() { return ability.isOptional(); } - } From 156b1a4b68e07a04086ddeef7afe6c32f5e3a1fe Mon Sep 17 00:00:00 2001 From: jeffwadsworth Date: Tue, 3 Dec 2019 17:52:35 -0600 Subject: [PATCH 10/19] - Fixed #6022 --- .../src/mage/cards/l/LostOrderOfJarkeld.java | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/Mage.Sets/src/mage/cards/l/LostOrderOfJarkeld.java b/Mage.Sets/src/mage/cards/l/LostOrderOfJarkeld.java index 012b523cc4..51a7f25df4 100644 --- a/Mage.Sets/src/mage/cards/l/LostOrderOfJarkeld.java +++ b/Mage.Sets/src/mage/cards/l/LostOrderOfJarkeld.java @@ -1,26 +1,30 @@ package mage.cards.l; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.AsEntersBattlefieldAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.ChooseOpponentEffect; import mage.abilities.effects.common.continuous.SetPowerToughnessSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.StaticFilters; -import mage.game.Game; - import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.AdditiveDynamicValue; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.Effect; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.players.Player; /** * @author TheElk801 */ public final class LostOrderOfJarkeld extends CardImpl { + protected FilterCreaturePermanent filter = new FilterCreaturePermanent(); + public LostOrderOfJarkeld(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{W}"); @@ -36,8 +40,7 @@ public final class LostOrderOfJarkeld extends CardImpl { this.addAbility(new SimpleStaticAbility( Zone.ALL, new SetPowerToughnessSourceEffect( - LostOrderOfJarkeldValue.instance, Duration.Custom, SubLayer.CharacteristicDefining_7a - ) + new AdditiveDynamicValue(new CreaturesControlledByChosenPlayer(), new StaticValue(1)), Duration.EndOfGame) )); } @@ -51,28 +54,32 @@ public final class LostOrderOfJarkeld extends CardImpl { } } -enum LostOrderOfJarkeldValue implements DynamicValue { - instance; +class CreaturesControlledByChosenPlayer implements DynamicValue { @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { - if (game.getPermanent(sourceAbility.getSourceId()) == null) { - return 1; + if (sourceAbility != null) { + UUID playerId = (UUID) game.getState().getValue(sourceAbility.getSourceId() + ChooseOpponentEffect.VALUE_KEY); + Player chosenPlayer = game.getPlayer(playerId); + if (chosenPlayer != null) { + return game.getBattlefield().countAll(new FilterCreaturePermanent(), chosenPlayer.getId(), game); + } } - Object obj = game.getState().getValue(sourceAbility.getSourceId().toString() + ChooseOpponentEffect.VALUE_KEY); - if (!(obj instanceof UUID)) { - return 1; - } - return 1 + game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, (UUID) obj, game).size(); + return 0; } @Override public DynamicValue copy() { - return instance; + return new CreaturesControlledByChosenPlayer(); } @Override public String getMessage() { - return "1 plus the number of creatures the chosen player controls."; + return "creatures controlled by chosen player"; + } + + @Override + public String toString() { + return "1"; } } From 3a054c335a7a07c9fa68fa4cdebc38ba9738a984 Mon Sep 17 00:00:00 2001 From: jeffwadsworth Date: Tue, 3 Dec 2019 17:55:07 -0600 Subject: [PATCH 11/19] - text fix Lost Order of the Jarkeld --- Mage.Sets/src/mage/cards/l/LostOrderOfJarkeld.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/l/LostOrderOfJarkeld.java b/Mage.Sets/src/mage/cards/l/LostOrderOfJarkeld.java index 51a7f25df4..44b3f31f12 100644 --- a/Mage.Sets/src/mage/cards/l/LostOrderOfJarkeld.java +++ b/Mage.Sets/src/mage/cards/l/LostOrderOfJarkeld.java @@ -75,7 +75,7 @@ class CreaturesControlledByChosenPlayer implements DynamicValue { @Override public String getMessage() { - return "creatures controlled by chosen player"; + return "1 plus the number of creatures controlled by chosen player"; } @Override From c7acebb47c273fb333634f299f1d9daaf630ac10 Mon Sep 17 00:00:00 2001 From: jeffwadsworth Date: Wed, 4 Dec 2019 16:17:04 -0600 Subject: [PATCH 12/19] - Fixed #6070 --- .../src/mage/cards/o/ObzedatGhostCouncil.java | 40 +++++++++---------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/Mage.Sets/src/mage/cards/o/ObzedatGhostCouncil.java b/Mage.Sets/src/mage/cards/o/ObzedatGhostCouncil.java index c936c96074..8600415009 100644 --- a/Mage.Sets/src/mage/cards/o/ObzedatGhostCouncil.java +++ b/Mage.Sets/src/mage/cards/o/ObzedatGhostCouncil.java @@ -1,7 +1,6 @@ package mage.cards.o; import mage.MageInt; -import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.common.BeginningOfYourEndStepTriggeredAbility; @@ -43,7 +42,8 @@ public final class ObzedatGhostCouncil extends CardImpl { ability.addEffect(new GainLifeEffect(2).concatBy("and")); ability.addTarget(new TargetOpponent()); this.addAbility(ability); - //At the beginning of your end step you may exile Obzedat. If you do, return it to the battlefield under its owner's control at the beginning of your next upkeep. It gains haste. + //At the beginning of your end step you may exile Obzedat. If you do, return it to the battlefield under its owner's + //control at the beginning of your next upkeep. It gains haste. Ability ability2 = new BeginningOfYourEndStepTriggeredAbility(new ObzedatGhostCouncilExileSourceEffect(), true); this.addAbility(ability2); } @@ -62,8 +62,8 @@ class ObzedatGhostCouncilExileSourceEffect extends OneShotEffect { ObzedatGhostCouncilExileSourceEffect() { super(Outcome.Exile); - staticText = "exile {this}. If you do, return it to the battlefield under its owner's control " + - "at the beginning of your next upkeep. It gains haste."; + staticText = "exile {this}. If you do, return it to the battlefield under its owner's control " + + "at the beginning of your next upkeep. It gains haste."; } private ObzedatGhostCouncilExileSourceEffect(final ObzedatGhostCouncilExileSourceEffect effect) { @@ -77,13 +77,14 @@ class ObzedatGhostCouncilExileSourceEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); + Player controller = game.getPlayer(source.getControllerId()); Permanent permanent = game.getPermanent(source.getSourceId()); - if (permanent == null || player == null - || !player.moveCards(permanent, Zone.EXILED, source, game)) { + if (permanent == null + || controller == null + || !controller.moveCards(permanent, Zone.EXILED, source, game)) { return false; } - game.addDelayedTriggeredAbility(new ObzedatGhostCouncilDelayedTriggeredAbility(new MageObjectReference(permanent, game)), source); + game.addDelayedTriggeredAbility(new ObzedatGhostCouncilDelayedTriggeredAbility(permanent), source); return true; } @@ -91,8 +92,8 @@ class ObzedatGhostCouncilExileSourceEffect extends OneShotEffect { class ObzedatGhostCouncilDelayedTriggeredAbility extends DelayedTriggeredAbility { - ObzedatGhostCouncilDelayedTriggeredAbility(MageObjectReference mor) { - super(new ObzedatGhostCouncilReturnEffect(mor)); + ObzedatGhostCouncilDelayedTriggeredAbility(Card card) { + super(new ObzedatGhostCouncilReturnEffect(card)); } private ObzedatGhostCouncilDelayedTriggeredAbility(ObzedatGhostCouncilDelayedTriggeredAbility ability) { @@ -106,7 +107,7 @@ class ObzedatGhostCouncilDelayedTriggeredAbility extends DelayedTriggeredAbility @Override public boolean checkTrigger(GameEvent event, Game game) { - return event.getPlayerId().equals(this.controllerId); + return event.getPlayerId().equals(this.controllerId); // must be the controller who chooses } @Override @@ -122,16 +123,16 @@ class ObzedatGhostCouncilDelayedTriggeredAbility extends DelayedTriggeredAbility class ObzedatGhostCouncilReturnEffect extends OneShotEffect { - private final MageObjectReference mor; + private final Card card; - ObzedatGhostCouncilReturnEffect(MageObjectReference mor) { + ObzedatGhostCouncilReturnEffect(Card card) { super(Outcome.Benefit); - this.mor = mor; + this.card = card; } private ObzedatGhostCouncilReturnEffect(final ObzedatGhostCouncilReturnEffect effect) { super(effect); - this.mor = effect.mor; + this.card = effect.card; } @Override @@ -141,17 +142,12 @@ class ObzedatGhostCouncilReturnEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Card card = mor.getCard(game); if (card == null) { return false; } - Zone zone = game.getState().getZone(source.getSourceId()); - // return it from every public zone - http://www.mtgsalvation.com/forums/magic-fundamentals/magic-rulings/magic-rulings-archives/513186-obzedat-gc-as-edh-commander - if (zone == Zone.BATTLEFIELD || zone == Zone.LIBRARY || zone == Zone.HAND) { - return false; - } Player owner = game.getPlayer(card.getOwnerId()); - if (owner == null || !owner.moveCards(card, Zone.BATTLEFIELD, source, game)) { + if (owner == null + || !owner.moveCards(card, Zone.BATTLEFIELD, source, game)) { // comes back from any zone return false; } game.addEffect(new GainAbilitySourceEffect(HasteAbility.getInstance(), Duration.WhileOnBattlefield), source); From 2e59a45895516436522b737744b5d01336f640bb Mon Sep 17 00:00:00 2001 From: jeffwadsworth Date: Wed, 4 Dec 2019 16:20:15 -0600 Subject: [PATCH 13/19] - Fixed #6068 --- Mage.Sets/src/mage/cards/l/LeadershipVacuum.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Mage.Sets/src/mage/cards/l/LeadershipVacuum.java b/Mage.Sets/src/mage/cards/l/LeadershipVacuum.java index 60f0a0a9cb..c966713619 100644 --- a/Mage.Sets/src/mage/cards/l/LeadershipVacuum.java +++ b/Mage.Sets/src/mage/cards/l/LeadershipVacuum.java @@ -15,7 +15,6 @@ import mage.players.Player; import mage.target.TargetPlayer; import java.util.UUID; -import java.util.stream.Collectors; /** * @author TheElk801 @@ -52,7 +51,7 @@ class LeadershipVacuumEffect extends OneShotEffect { } LeadershipVacuumEffect() { - super(Outcome.Benefit); + super(Outcome.Detriment); staticText = "Target player returns each commander they control from the battlefield to the command zone."; } @@ -76,4 +75,4 @@ class LeadershipVacuumEffect extends OneShotEffect { .map(commander -> commander.moveToZone(Zone.COMMAND, source.getId(), game, true)) .reduce(true, Boolean::logicalAnd); } -} \ No newline at end of file +} From 95d749648e3d8594567e7cf00d3b3034a94ef40f Mon Sep 17 00:00:00 2001 From: jeffwadsworth Date: Wed, 4 Dec 2019 16:25:14 -0600 Subject: [PATCH 14/19] - Fixed #6067 --- Mage.Sets/src/mage/cards/v/VolrathTheShapestealer.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Mage.Sets/src/mage/cards/v/VolrathTheShapestealer.java b/Mage.Sets/src/mage/cards/v/VolrathTheShapestealer.java index 736144713d..880e9a64bc 100644 --- a/Mage.Sets/src/mage/cards/v/VolrathTheShapestealer.java +++ b/Mage.Sets/src/mage/cards/v/VolrathTheShapestealer.java @@ -48,7 +48,7 @@ public final class VolrathTheShapestealer extends CardImpl { // At the beginning of combat on your turn, put a -1/-1 counter on up to one target creature. Ability ability = new BeginningOfCombatTriggeredAbility( - new AddCountersTargetEffect(CounterType.M1M1.createInstance()), TargetController.YOU, false + new AddCountersTargetEffect(CounterType.M1M1.createInstance(), Outcome.Detriment), TargetController.YOU, false ); ability.addTarget(new TargetCreaturePermanent(0, 1)); this.addAbility(ability); @@ -73,8 +73,8 @@ class VolrathTheShapestealerEffect extends OneShotEffect { VolrathTheShapestealerEffect() { super(Outcome.Copy); - staticText = "Until your next turn, {this} becomes a copy of target creature with a counter on it, " + - "except it's 7/5 and it has this ability."; + staticText = "Until your next turn, {this} becomes a copy of target creature with a counter on it, " + + "except it's 7/5 and it has this ability."; } private VolrathTheShapestealerEffect(final VolrathTheShapestealerEffect effect) { From e437577b5ace93ec22cb8b44f2cff8b3e3534851 Mon Sep 17 00:00:00 2001 From: Sarah Souders Date: Wed, 4 Dec 2019 17:27:41 -0500 Subject: [PATCH 15/19] K'rrik fixes, payment for triggered abilities (#6060) * K'rrik fixes, payment for triggered abilities K'rrik's Phyrexian ability is now handled separately from actual Phyrexian mana costs. It can now be used to pay for triggered abilities like Extort. * K'rrik tests added Tests include: - only usable by 1 player - usable with activated/triggered abilities - usable as an alternative to true Phyrexian mana, getting around Trinisphere --- .../src/mage/cards/k/KrrikSonOfYawgmoth.java | 8 +- .../mana/phyrexian/PhyrexianManaTest.java | 92 +++++++++++++++++++ .../main/java/mage/abilities/AbilityImpl.java | 35 ------- .../abilities/costs/mana/ManaCostsImpl.java | 24 ++++- 4 files changed, 115 insertions(+), 44 deletions(-) diff --git a/Mage.Sets/src/mage/cards/k/KrrikSonOfYawgmoth.java b/Mage.Sets/src/mage/cards/k/KrrikSonOfYawgmoth.java index f32e5a1770..20e98faf09 100644 --- a/Mage.Sets/src/mage/cards/k/KrrikSonOfYawgmoth.java +++ b/Mage.Sets/src/mage/cards/k/KrrikSonOfYawgmoth.java @@ -94,13 +94,7 @@ class KrrikSonOfYawgmothPhyrexianEffect extends ContinuousEffectImpl { phyrexianBlack.setBlack(true); if (controller != null && sourcePermanent != null) { - for (UUID playerId: game.getState().getPlayersInRange(controller.getId(), game)) { - Player player = game.getPlayer(playerId); - if (player != null) - { - player.addPhyrexianToColors(phyrexianBlack); - } - } + controller.addPhyrexianToColors(phyrexianBlack); return true; } return false; diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/mana/phyrexian/PhyrexianManaTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/mana/phyrexian/PhyrexianManaTest.java index 56099f7329..69e7f6d90e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/mana/phyrexian/PhyrexianManaTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/mana/phyrexian/PhyrexianManaTest.java @@ -28,5 +28,97 @@ public class PhyrexianManaTest extends CardTestPlayerBase { // can be played only through life pay Assert.assertTrue(life == 20 && hand == 1 || life == 18 && hand == 0); } + + @Test + public void testKrrikOnlyUsableByController() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerA, "K'rrik, Son of Yawgmoth"); + addCard(Zone.HAND, playerA, "Banehound"); + + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 1); + addCard(Zone.HAND, playerB, "Banehound"); + setChoice(playerA, "Yes"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Banehound"); + setChoice(playerB, "Yes"); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Banehound"); + setStopAt(2, PhaseStep.END_TURN); + execute(); + + //PlayerA pays life but PlayerB cannot + assertLife(playerA, 18); + assertLife(playerB, 20); + assertPermanentCount(playerA, "Banehound", 1); + assertPermanentCount(playerB, "Banehound", 1); + } + + @Test + public void testKrrikTriggeredAbility() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.BATTLEFIELD, playerA, "K'rrik, Son of Yawgmoth"); + addCard(Zone.HAND, playerA, "Banehound"); + addCard(Zone.HAND, playerA, "Crypt Ghast"); + + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 1); + + setChoice(playerA, "Yes"); //yes to pay 2 life to cast Crypt Ghast + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Crypt Ghast"); //3 mana used, 2 life paid (18 life total) + setChoice(playerA, "Yes"); //yes to pay 2 life to cast Banehound + setChoice(playerA, "Yes"); //yes to Extort + setChoice(playerA, "Yes"); //yes to pay 2 life to Extort + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Banehound"); //0 mana used, 4 life paid, 1 life gained (15 life total) + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 15); + assertLife(playerB, 19); + assertPermanentCount(playerA, "Banehound", 1); + assertPermanentCount(playerA, "Crypt Ghast", 1); + } + + @Test + public void testKrrikActivatedAbility() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerA, "K'rrik, Son of Yawgmoth"); + addCard(Zone.BATTLEFIELD, playerA, "Frozen Shade"); + + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 1); + + setChoice(playerA, "Yes"); //yes to pay 2 life to activate Frozen Shade's +1/+1 ability + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{B}: {this} gets +1/+1 until end of turn."); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 18); + assertLife(playerB, 20); + assertPowerToughness(playerA, "Frozen Shade", 1, 2); + } + + @Test + public void testKrrikTrinispherePostPay() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + addCard(Zone.BATTLEFIELD, playerA, "K'rrik, Son of Yawgmoth"); + addCard(Zone.BATTLEFIELD, playerA, "Trinisphere"); + addCard(Zone.HAND, playerA, "Dismember"); + + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerB, "Banehound"); + + setChoice(playerA, "No"); //don't pay 2 life for Dismember's Phyrexian cost + setChoice(playerA, "No"); //don't pay 2 life for Dismember's Phyrexian cost + setChoice(playerA, "Yes"); //yes to pay 2 life for Dismember's {B} cost via K'rrik + setChoice(playerA, "Yes"); //yes to pay 2 life for Dismember's {B} cost via K'rrik + + //Dismember costs {1} now + life paid. Normally this would be {3} + life paid with true Phyrexian mana and Trinisphere active. + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dismember", "Banehound"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 16); + assertLife(playerB, 20); + assertTappedCount("Swamp", true, 1); + assertGraveyardCount(playerA, "Dismember", 1); + assertGraveyardCount(playerB, "Banehound", 1); + } } diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index d56a18262a..5ae344cb49 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -549,10 +549,6 @@ public abstract class AbilityImpl implements Ability { Iterator costIterator = manaCostsToPay.iterator(); while (costIterator.hasNext()) { ManaCost cost = costIterator.next(); - PhyrexianManaCost tempPhyrexianCost = null; - Mana mana = cost.getMana(); - - FilterMana phyrexianColors = controller.getPhyrexianColors(); if (cost instanceof PhyrexianManaCost) { PhyrexianManaCost phyrexianManaCost = (PhyrexianManaCost) cost; @@ -563,37 +559,6 @@ public abstract class AbilityImpl implements Ability { costs.add(payLifeCost); } } - /* K'rrik, Son of Yawgmoth ability check */ - else if (phyrexianColors != null) { - int phyrexianEnabledPips = mana.count(phyrexianColors); - if (phyrexianEnabledPips > 0) { - /* find which color mana is in the cost and set it in the temp Phyrexian cost */ - if (phyrexianColors.isWhite() && mana.getWhite() > 0) { - tempPhyrexianCost = new PhyrexianManaCost(ColoredManaSymbol.W); - } - else if (phyrexianColors.isBlue() && mana.getBlue() > 0) { - tempPhyrexianCost = new PhyrexianManaCost(ColoredManaSymbol.U); - } - else if (phyrexianColors.isBlack() && mana.getBlack() > 0) { - tempPhyrexianCost = new PhyrexianManaCost(ColoredManaSymbol.B); - } - else if (phyrexianColors.isRed() && mana.getRed() > 0) { - tempPhyrexianCost = new PhyrexianManaCost(ColoredManaSymbol.R); - } - else if (phyrexianColors.isGreen() && mana.getGreen() > 0) { - tempPhyrexianCost = new PhyrexianManaCost(ColoredManaSymbol.G); - } - - if (tempPhyrexianCost != null) { - PayLifeCost payLifeCost = new PayLifeCost(2); - if (payLifeCost.canPay(this, sourceId, controller.getId(), game) - && controller.chooseUse(Outcome.LoseLife, "Pay 2 life instead of " + tempPhyrexianCost.getBaseText() + '?', this, game)) { - costIterator.remove(); - costs.add(payLifeCost); - } - } - } - } } } diff --git a/Mage/src/main/java/mage/abilities/costs/mana/ManaCostsImpl.java b/Mage/src/main/java/mage/abilities/costs/mana/ManaCostsImpl.java index 7cdfead1aa..a66d7a5702 100644 --- a/Mage/src/main/java/mage/abilities/costs/mana/ManaCostsImpl.java +++ b/Mage/src/main/java/mage/abilities/costs/mana/ManaCostsImpl.java @@ -118,6 +118,7 @@ public class ManaCostsImpl extends ArrayList implements M } Player player = game.getPlayer(controllerId); + handleKrrikPhyrexianManaCosts(controllerId, ability, game); if (!player.getManaPool().isForcedToPay()) { assignPayment(game, ability, player.getManaPool(), this); } @@ -181,8 +182,27 @@ public class ManaCostsImpl extends ArrayList implements M tempCosts.add(payLifeCost); } } + } + + tempCosts.pay(source, game, source.getSourceId(), player.getId(), false, null); + } + + private void handleKrrikPhyrexianManaCosts(UUID payingPlayerId, Ability source, Game game) { + Player player = game.getPlayer(payingPlayerId); + if (this == null || player == null) { + return; // nothing to be done without any mana costs. prevents NRE from occurring here + } + Iterator manaCostIterator = this.iterator(); + Costs tempCosts = new CostsImpl<>(); + + while (manaCostIterator.hasNext()) { + ManaCost manaCost = manaCostIterator.next(); + Mana mana = manaCost.getMana(); + PhyrexianManaCost tempPhyrexianCost = null; + FilterMana phyrexianColors = player.getPhyrexianColors(); + /* K'rrik, Son of Yawgmoth ability check */ - else if (phyrexianColors != null) { + if (phyrexianColors != null) { int phyrexianEnabledPips = mana.count(phyrexianColors); if (phyrexianEnabledPips > 0) { /* find which color mana is in the cost and set it in the temp Phyrexian cost */ @@ -205,7 +225,7 @@ public class ManaCostsImpl extends ArrayList implements M if (tempPhyrexianCost != null) { PayLifeCost payLifeCost = new PayLifeCost(2); if (payLifeCost.canPay(source, source.getSourceId(), player.getId(), game) - && player.chooseUse(Outcome.LoseLife, "Pay 2 life instead of " + tempPhyrexianCost.getBaseText() + '?', source, game)) { + && player.chooseUse(Outcome.LoseLife, "Pay 2 life (using an active ability) instead of " + tempPhyrexianCost.getBaseText() + '?', source, game)) { manaCostIterator.remove(); tempCosts.add(payLifeCost); } From b208dc332f2cddb7ca49d41e1e52f05c3d7932be Mon Sep 17 00:00:00 2001 From: Sarah Souders Date: Wed, 4 Dec 2019 21:59:03 -0500 Subject: [PATCH 16/19] Fixed Rankle's triggered ability (#6064) * Fixed Rankle's triggered ability Any number of available modes can now be selected, including zero. * Update AbilityImpl.java * getTargets now returns empty Target for null Mode Reverted null Target check changes in StackAbility. * Update HumanPlayer.java --- .../Mage.Player.Human/src/mage/player/human/HumanPlayer.java | 4 ++++ Mage.Sets/src/mage/cards/r/RankleMasterOfPranks.java | 2 +- Mage/src/main/java/mage/abilities/AbilityImpl.java | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index 25e018a02e..415573b89f 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -1856,6 +1856,10 @@ public class HumanPlayer extends PlayerImpl { } } } + else if (modes.getSelectedModes().size() >= modes.getMinModes()) { + /* let the player cancel mode selection if they do not need to select any further modes */ + done = true; + } if (source.getAbilityType() != AbilityType.TRIGGERED) { done = true; } diff --git a/Mage.Sets/src/mage/cards/r/RankleMasterOfPranks.java b/Mage.Sets/src/mage/cards/r/RankleMasterOfPranks.java index 9686f52d2c..a823b84f7c 100644 --- a/Mage.Sets/src/mage/cards/r/RankleMasterOfPranks.java +++ b/Mage.Sets/src/mage/cards/r/RankleMasterOfPranks.java @@ -51,7 +51,7 @@ public final class RankleMasterOfPranks extends CardImpl { // • Each player sacrifices a creature. ability.addMode(new Mode(new SacrificeAllEffect(StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT))); - ability.getModes().setMinModes(1); + ability.getModes().setMinModes(0); ability.getModes().setMaxModes(3); ability.getModes().setChooseText("choose any number —"); this.addAbility(ability); diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 5ae344cb49..d5e096a39e 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -889,7 +889,7 @@ public abstract class AbilityImpl implements Ability { if (getModes().getMode() != null) { return getModes().getMode().getTargets(); } - return null; + return new Targets(); } @Override From d8a4ddf04a0d54409cc414a059c48a117bcee420 Mon Sep 17 00:00:00 2001 From: jeffwadsworth Date: Thu, 5 Dec 2019 09:24:56 -0600 Subject: [PATCH 17/19] - Fixed #5989 --- .../src/mage/cards/x/XantchaSleeperAgent.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Mage.Sets/src/mage/cards/x/XantchaSleeperAgent.java b/Mage.Sets/src/mage/cards/x/XantchaSleeperAgent.java index d0a35959e7..f16a7f447a 100644 --- a/Mage.Sets/src/mage/cards/x/XantchaSleeperAgent.java +++ b/Mage.Sets/src/mage/cards/x/XantchaSleeperAgent.java @@ -28,11 +28,9 @@ import java.util.UUID; import static mage.constants.Outcome.Benefit; - /** * @author jesusjbr */ - public final class XantchaSleeperAgent extends CardImpl { public XantchaSleeperAgent(UUID ownerId, CardSetInfo setInfo) { @@ -145,14 +143,18 @@ class XantchaSleeperAgentAttackRestrictionEffect extends RestrictionEffect { boolean allowAttack = true; UUID ownerPlayerId = source.getSourcePermanentIfItStillExists(game).getOwnerId(); - if (defenderId.equals(ownerPlayerId)) { + if (defenderId.equals(ownerPlayerId) + && game.getPlayers().size() == 2) { // if only 2 players are left, it can't attack at all. allowAttack = false; - } else { - Permanent planeswalker = game.getPermanent(defenderId); - if (planeswalker != null && planeswalker.isControlledBy(ownerPlayerId)) { - allowAttack = false; - } } + if (defenderId.equals(ownerPlayerId)) { // can't attack owner + allowAttack = false; + } + Permanent planeswalker = game.getPermanent(defenderId); + if (planeswalker != null && planeswalker.isControlledBy(ownerPlayerId)) { // can't attack the owner's planeswalkers + allowAttack = false; + } + return allowAttack; } } From ba78d410342106c3c3a641971fe7d81b489d4bf3 Mon Sep 17 00:00:00 2001 From: jeffwadsworth Date: Thu, 5 Dec 2019 11:25:38 -0600 Subject: [PATCH 18/19] - Fixed #5969 --- .../GodEternalDiesTriggeredAbility.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/Mage/src/main/java/mage/abilities/common/GodEternalDiesTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/GodEternalDiesTriggeredAbility.java index a7e9205f71..e59ceb2524 100644 --- a/Mage/src/main/java/mage/abilities/common/GodEternalDiesTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/GodEternalDiesTriggeredAbility.java @@ -10,12 +10,15 @@ import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; import mage.players.Player; /** * @author TheElk801 */ public class GodEternalDiesTriggeredAbility extends TriggeredAbilityImpl { + + Boolean applied; public GodEternalDiesTriggeredAbility() { super(Zone.ALL, null, true); @@ -30,20 +33,32 @@ public class GodEternalDiesTriggeredAbility extends TriggeredAbilityImpl { if (event.getType() == GameEvent.EventType.ZONE_CHANGE) { ZoneChangeEvent zEvent = (ZoneChangeEvent) event; return zEvent.getFromZone() == Zone.BATTLEFIELD - && (zEvent.getToZone() == Zone.GRAVEYARD || zEvent.getToZone() == Zone.EXILED); + && (zEvent.getToZone() == Zone.GRAVEYARD + || zEvent.getToZone() == Zone.EXILED); } return false; } @Override public boolean checkTrigger(GameEvent event, Game game) { + applied = false; ZoneChangeEvent zEvent = (ZoneChangeEvent) event; if (zEvent.getTargetId().equals(this.getSourceId())) { + Permanent permanent = game.getPermanentOrLKIBattlefield(this.getSourceId()); + // for cases where its triggered ability is removed, ex: Kasmina's Transmutation + if (permanent != null) { + for (Ability a : permanent.getAbilities()) { + if (a instanceof GodEternalDiesTriggeredAbility) { + applied = true; + } + } + } + if (applied) { this.getEffects().clear(); this.addEffect(new GodEternalEffect(new MageObjectReference(zEvent.getTarget(), game))); - return true; + } } - return false; + return applied; } @Override From 97f066a31abeeacefca1d48eafbfb78f05dbd8ae Mon Sep 17 00:00:00 2001 From: jeffwadsworth Date: Thu, 5 Dec 2019 18:27:51 -0600 Subject: [PATCH 19/19] - Fixed #5948 --- .../src/mage/cards/c/CallerOfTheHunt.java | 55 ++++++++++++------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/Mage.Sets/src/mage/cards/c/CallerOfTheHunt.java b/Mage.Sets/src/mage/cards/c/CallerOfTheHunt.java index b95405da59..11f1193e2c 100644 --- a/Mage.Sets/src/mage/cards/c/CallerOfTheHunt.java +++ b/Mage.Sets/src/mage/cards/c/CallerOfTheHunt.java @@ -1,34 +1,35 @@ - package mage.cards.c; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.CostAdjuster; -import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; -import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.InfoEffect; -import mage.abilities.effects.common.continuous.SetPowerSourceEffect; -import mage.abilities.effects.common.continuous.SetToughnessSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.choices.Choice; import mage.choices.ChoiceCreatureType; import mage.constants.*; -import mage.filter.FilterPermanent; -import mage.filter.predicate.mageobject.ChosenSubtypePredicate; import mage.game.Game; import mage.players.Player; - import java.util.UUID; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.continuous.SetPowerToughnessSourceEffect; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.target.targetpointer.FixedTarget; /** * @author jeffwadsworth */ public final class CallerOfTheHunt extends CardImpl { + FilterCreaturePermanent filter = new FilterCreaturePermanent(); + public CallerOfTheHunt(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); @@ -38,7 +39,9 @@ public final class CallerOfTheHunt extends CardImpl { // Caller of the Hunt's power and toughness are each equal to the number of creatures of the chosen type on the battlefield. this.addAbility(new SimpleStaticAbility(Zone.ALL, new InfoEffect("as an additional cost to cast this spell, choose a creature type. \r" + "{this}'s power and toughness are each equal to the number of creatures of the chosen type on the battlefield"))); + this.getSpellAbility().setCostAdjuster(CallerOfTheHuntAdjuster.instance); + } public CallerOfTheHunt(final CallerOfTheHunt card) { @@ -58,19 +61,27 @@ enum CallerOfTheHuntAdjuster implements CostAdjuster { public void adjustCosts(Ability ability, Game game) { MageObject mageObject = game.getObject(ability.getSourceId()); Effect effect = new ChooseCreatureTypeEffect(Outcome.Benefit); - if (mageObject != null - && effect.apply(game, ability)) { - FilterPermanent filter = new FilterPermanent(); - filter.add(ChosenSubtypePredicate.instance); - ContinuousEffect effectPower = new SetPowerSourceEffect(new PermanentsOnBattlefieldCount(filter), Duration.Custom); - ContinuousEffect effectToughness = new SetToughnessSourceEffect(new PermanentsOnBattlefieldCount(filter), Duration.Custom); - game.addEffect(effectPower, ability); - game.addEffect(effectToughness, ability); + if (mageObject != null) { + effect.apply(game, ability); + } + if (mageObject != null) { + SubType typeChoice = (SubType) game.getState().getValue(mageObject.getId() + "_type"); + if (typeChoice != null) { + FilterCreaturePermanent filter = new FilterCreaturePermanent("chosen creature type"); + filter.add(new SubtypePredicate(typeChoice)); + ContinuousEffect effectPowerToughness = new SetPowerToughnessSourceEffect( + new PermanentsOnBattlefieldCount(filter), Duration.EndOfGame); + effectPowerToughness.setText(""); + SimpleStaticAbility sa = new SimpleStaticAbility(Zone.ALL, effectPowerToughness); + GainAbilityTargetEffect effectTest = new GainAbilityTargetEffect(sa, Duration.EndOfGame); + effectTest.setTargetPointer(new FixedTarget(ability.getSourceId())); + game.getState().addEffect(effectTest, ability); + } } } } -class ChooseCreatureTypeEffect extends OneShotEffect { // code by LevelX2, but that other version is not compatible with this card +class ChooseCreatureTypeEffect extends OneShotEffect { public ChooseCreatureTypeEffect(Outcome outcome) { super(outcome); @@ -86,11 +97,15 @@ class ChooseCreatureTypeEffect extends OneShotEffect { // code by LevelX2, but t Player controller = game.getPlayer(source.getControllerId()); MageObject mageObject = game.getObject(source.getSourceId()); Choice typeChoice = new ChoiceCreatureType(mageObject); - if (controller != null && mageObject != null && controller.choose(outcome, typeChoice, game)) { + if (controller != null + && mageObject != null + && controller.choose(outcome, typeChoice, game)) { if (!game.isSimulation()) { - game.informPlayers(mageObject.getName() + ": " + controller.getLogName() + " has chosen " + typeChoice.getChoice()); + game.informPlayers(mageObject.getName() + ": " + + controller.getLogName() + " has chosen " + typeChoice.getChoice()); } - game.getState().setValue(mageObject.getId() + "_type", SubType.byDescription(typeChoice.getChoice())); + game.getState().setValue(mageObject.getId() + + "_type", SubType.byDescription(typeChoice.getChoice())); return true; } return false;