diff --git a/Mage.Sets/src/mage/sets/commander2015/MirrorMatch.java b/Mage.Sets/src/mage/sets/commander2015/MirrorMatch.java new file mode 100644 index 00000000000..7ce33e3fc08 --- /dev/null +++ b/Mage.Sets/src/mage/sets/commander2015/MirrorMatch.java @@ -0,0 +1,167 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.commander2015; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.delayed.AtTheEndOfCombatDelayedTriggeredAbility; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.PutTokenOntoBattlefieldCopyTargetEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.PhaseStep; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.combat.CombatGroup; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author LevelX2 + */ +public class MirrorMatch extends CardImpl { + + public MirrorMatch(UUID ownerId) { + super(ownerId, 13, "Mirror Match", Rarity.UNCOMMON, new CardType[]{CardType.INSTANT}, "{4}{U}{U}"); + this.expansionSetCode = "C15"; + + // Cast Mirror Match only during the declare blockers step. + Ability ability = new SimpleStaticAbility(Zone.ALL, new MirrorMatchRuleModifyingEffect()); + ability.setRuleAtTheTop(true); + this.addAbility(ability); + + // For each creature attacking you or a planeswalker you control, put a token that's a copy of that creature onto the battlefield blocking that creature. Exile those tokens at end of combat. + this.getSpellAbility().addEffect(new MirrorMatchEffect()); + + } + + public MirrorMatch(final MirrorMatch card) { + super(card); + } + + @Override + public MirrorMatch copy() { + return new MirrorMatch(this); + } +} + +class MirrorMatchRuleModifyingEffect extends ContinuousRuleModifyingEffectImpl { + + MirrorMatchRuleModifyingEffect() { + super(Duration.EndOfGame, Outcome.Detriment); + staticText = "Cast {this} only during the declare blockers step"; + } + + MirrorMatchRuleModifyingEffect(final MirrorMatchRuleModifyingEffect effect) { + super(effect); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return GameEvent.EventType.CAST_SPELL.equals(event.getType()); + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (event.getSourceId().equals(source.getSourceId())) { + return !game.getTurn().getStepType().equals(PhaseStep.DECLARE_BLOCKERS); + } + return false; + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public MirrorMatchRuleModifyingEffect copy() { + return new MirrorMatchRuleModifyingEffect(this); + } +} + +class MirrorMatchEffect extends OneShotEffect { + + public MirrorMatchEffect() { + super(Outcome.Benefit); + this.staticText = "For each creature attacking you or a planeswalker you control, put a token that's a copy of that creature onto the battlefield blocking that creature. Exile those tokens at end of combat"; + } + + public MirrorMatchEffect(final MirrorMatchEffect effect) { + super(effect); + } + + @Override + public MirrorMatchEffect copy() { + return new MirrorMatchEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + for (UUID attackerId : game.getCombat().getAttackers()) { + Permanent attacker = game.getPermanent(attackerId); + if (attacker != null) { + if (source.getControllerId().equals(game.getCombat().getDefendingPlayerId(attackerId, game))) { + PutTokenOntoBattlefieldCopyTargetEffect effect = new PutTokenOntoBattlefieldCopyTargetEffect(source.getControllerId(), null, false); + effect.setTargetPointer(new FixedTarget(attacker, game)); + effect.apply(game, source); + for (Permanent addedToken : effect.getAddedPermanent()) { + if (addedToken.getCardType().contains(CardType.CREATURE)) { + CombatGroup group = game.getCombat().findGroup(attacker.getId()); + if (group != null) { + group.addBlockerToGroup(addedToken.getId(), attackerId, game); + } + } + ExileTargetEffect sacrificeEffect = new ExileTargetEffect("Exile the token at end of combat"); + sacrificeEffect.setTargetPointer(new FixedTarget(addedToken, game)); + DelayedTriggeredAbility delayedAbility = new AtTheEndOfCombatDelayedTriggeredAbility(sacrificeEffect); + delayedAbility.setSourceId(source.getSourceId()); + delayedAbility.setControllerId(source.getControllerId()); + delayedAbility.setSourceObject(source.getSourceObject(game), game); + game.addDelayedTriggeredAbility(delayedAbility); + } + } + } + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/commander2015/MizzixsMastery.java b/Mage.Sets/src/mage/sets/commander2015/MizzixsMastery.java new file mode 100644 index 00000000000..dfaf1c3b7ed --- /dev/null +++ b/Mage.Sets/src/mage/sets/commander2015/MizzixsMastery.java @@ -0,0 +1,168 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.commander2015; + +import java.util.Set; +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileSpellEffect; +import mage.abilities.keyword.OverloadAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.TimingRule; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.common.FilterInstantOrSorceryCard; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInYourGraveyard; + +/** + * + * @author LevelX2 + */ +public class MizzixsMastery extends CardImpl { + + public MizzixsMastery(UUID ownerId) { + super(ownerId, 29, "Mizzix's Mastery", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{3}{R}"); + this.expansionSetCode = "C15"; + + // Exile target card that's an instant or sorcery from your graveyard. For each card exiled this way, copy it, and you may cast the copy without paying its mana cost. Exile Mizzix's Mastery. + this.getSpellAbility().addEffect(new MizzixsMasteryEffect()); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(new FilterInstantOrSorceryCard("card that's an instant or sorcery from your graveyard"))); + this.getSpellAbility().addEffect(ExileSpellEffect.getInstance()); + + // Overload {5}{R}{R}{R} + Ability ability = new OverloadAbility(this, new MizzixsMasteryOverloadEffect(), new ManaCostsImpl("{5}{R}{R}{R}"), TimingRule.SORCERY); + ability.addEffect(ExileSpellEffect.getInstance()); + this.addAbility(ability); + } + + public MizzixsMastery(final MizzixsMastery card) { + super(card); + } + + @Override + public MizzixsMastery copy() { + return new MizzixsMastery(this); + } +} + +class MizzixsMasteryEffect extends OneShotEffect { + + public MizzixsMasteryEffect() { + super(Outcome.PlayForFree); + this.staticText = "Exile target card that's an instant or sorcery from your graveyard. For each card exiled this way, copy it, and you may cast the copy without paying its mana cost. Exile {this}"; + } + + public MizzixsMasteryEffect(final MizzixsMasteryEffect effect) { + super(effect); + } + + @Override + public MizzixsMasteryEffect copy() { + return new MizzixsMasteryEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (card != null) { + if (controller.moveCards(card, Zone.EXILED, source, game)) { + Card cardCopy = game.copyCard(card, source, source.getControllerId()); + if (cardCopy.getSpellAbility().canChooseTarget(game) + && controller.chooseUse(outcome, "Cast copy of " + card.getName() + " without paying its mana cost?", source, game)) { + controller.cast(cardCopy.getSpellAbility(), game, true); + } + } + } + return true; + } + return false; + } +} + +class MizzixsMasteryOverloadEffect extends OneShotEffect { + + public MizzixsMasteryOverloadEffect() { + super(Outcome.PlayForFree); + this.staticText = "Exile each card that's an instant or sorcery from your graveyard. For each card exiled this way, copy it, and you may cast the copy without paying its mana cost. Exile {this}"; + } + + public MizzixsMasteryOverloadEffect(final MizzixsMasteryOverloadEffect effect) { + super(effect); + } + + @Override + public MizzixsMasteryOverloadEffect copy() { + return new MizzixsMasteryOverloadEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Set cardsToExile = controller.getGraveyard().getCards(new FilterInstantOrSorceryCard(), source.getId(), source.getControllerId(), game); + if (!cardsToExile.isEmpty()) { + if (controller.moveCards(cardsToExile, Zone.EXILED, source, game)) { + Cards copiedCards = new CardsImpl(); + for (Card card : cardsToExile) { + copiedCards.add(game.copyCard(card, source, source.getControllerId())); + } + boolean continueCasting = true; + while (continueCasting) { + TargetCard targetCard = new TargetCard(0, 1, Zone.EXILED, new FilterCard("copied card to cast without paying its mana cost?")); + targetCard.setNotTarget(true); + if (controller.choose(outcome, copiedCards, targetCard, game)) { + Card selectedCard = game.getCard(targetCard.getFirstTarget()); + if (selectedCard != null && selectedCard.getSpellAbility().canChooseTarget(game)) { + if (controller.cast(selectedCard.getSpellAbility(), game, true)) { + copiedCards.remove(selectedCard); + } + } + } + continueCasting = copiedCards.size() > 0 + && controller.chooseUse(outcome, "Cast one of the copied cards without paying its mana cost?", source, game); + } + } + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/commander2015/SyntheticDestiny.java b/Mage.Sets/src/mage/sets/commander2015/SyntheticDestiny.java new file mode 100644 index 00000000000..142ddc269ad --- /dev/null +++ b/Mage.Sets/src/mage/sets/commander2015/SyntheticDestiny.java @@ -0,0 +1,158 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.commander2015; + +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.UUID; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.players.Player; + +/** + * + * @author LevelX2 + */ +public class SyntheticDestiny extends CardImpl { + + public SyntheticDestiny(UUID ownerId) { + super(ownerId, 15, "Synthetic Destiny", Rarity.RARE, new CardType[]{CardType.INSTANT}, "{4}{U}{U}"); + this.expansionSetCode = "C15"; + + // Exile all creatures you control. At the beginning of the next end step, reveal cards from the top of your library until you reveal that many creature cards, put all creature cards revealed this way onto the battlefield, then shuffle the rest of the revealed cards into your library. + getSpellAbility().addEffect(new SyntheticDestinyEffect()); + } + + public SyntheticDestiny(final SyntheticDestiny card) { + super(card); + } + + @Override + public SyntheticDestiny copy() { + return new SyntheticDestiny(this); + } +} + +class SyntheticDestinyEffect extends OneShotEffect { + + public SyntheticDestinyEffect() { + super(Outcome.Detriment); + this.staticText = "Exile all creatures you control. At the beginning of the next end step, reveal cards from the top of your library until you reveal that many creature cards, put all creature cards revealed this way onto the battlefield, then shuffle the rest of the revealed cards into your library"; + } + + public SyntheticDestinyEffect(final SyntheticDestinyEffect effect) { + super(effect); + } + + @Override + public SyntheticDestinyEffect copy() { + return new SyntheticDestinyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Set cardsToExile = new HashSet<>(); + cardsToExile.addAll(game.getBattlefield().getAllActivePermanents(new FilterCreaturePermanent(), controller.getId(), game)); + controller.moveCards(cardsToExile, Zone.EXILED, source, game); + //Delayed ability + Effect effect = new SyntheticDestinyDelayedEffect(cardsToExile.size()); + DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect); + delayedAbility.setSourceId(source.getSourceId()); + delayedAbility.setControllerId(source.getControllerId()); + delayedAbility.setSourceObject(source.getSourceObject(game), game); + game.addDelayedTriggeredAbility(delayedAbility); + + return true; + } + return false; + } +} + +class SyntheticDestinyDelayedEffect extends OneShotEffect { + + protected int numberOfCards; + + public SyntheticDestinyDelayedEffect(int numberOfCards) { + super(Outcome.PutCreatureInPlay); + this.numberOfCards = numberOfCards; + this.staticText = "reveal cards from the top of your library until you reveal that many creature cards, put all creature cards revealed this way onto the battlefield, then shuffle the rest of the revealed cards into your library"; + } + + public SyntheticDestinyDelayedEffect(final SyntheticDestinyDelayedEffect effect) { + super(effect); + this.numberOfCards = effect.numberOfCards; + } + + @Override + public SyntheticDestinyDelayedEffect copy() { + return new SyntheticDestinyDelayedEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObject(game); + if (controller != null && sourceObject != null) { + Cards revealed = new CardsImpl(); + Set creatureCards = new LinkedHashSet<>(); + Cards nonCreatureCards = new CardsImpl(); + while (creatureCards.size() < numberOfCards && controller.getLibrary().size() > 0) { + Card card = controller.getLibrary().removeFromTop(game); + revealed.add(card); + if (card.getCardType().contains(CardType.CREATURE)) { + creatureCards.add(card); + } else { + nonCreatureCards.add(card); + } + } + controller.revealCards(sourceObject.getIdName(), revealed, game); + controller.moveCards(creatureCards, Zone.BATTLEFIELD, source, game, false, false, true, null); + controller.putCardsOnTopOfLibrary(nonCreatureCards, game, source, false); + controller.shuffleLibrary(game); + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/magic2011/MassPolymorph.java b/Mage.Sets/src/mage/sets/magic2011/MassPolymorph.java index 95bb22a3635..cd3743fde26 100644 --- a/Mage.Sets/src/mage/sets/magic2011/MassPolymorph.java +++ b/Mage.Sets/src/mage/sets/magic2011/MassPolymorph.java @@ -58,6 +58,8 @@ public class MassPolymorph extends CardImpl { super(ownerId, 64, "Mass Polymorph", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{5}{U}"); this.expansionSetCode = "M11"; + // Exile all creatures you control, then reveal cards from the top of your library until you reveal that many creature cards. + // Put all creature cards revealed this way onto the battlefield, then shuffle the rest of the revealed cards into your library. this.getSpellAbility().addEffect(new MassPolymorphEffect()); } @@ -93,7 +95,7 @@ class MassPolymorphEffect extends OneShotEffect { Set creaturesToExile = new HashSet<>(); creaturesToExile.addAll(creatures); count = creatures.size(); - controller.moveCards(creaturesToExile, null, Zone.HAND, source, game); + controller.moveCards(creaturesToExile, Zone.HAND, source, game); Cards revealed = new CardsImpl(); Set creatureCards = new LinkedHashSet<>(); diff --git a/Mage/src/mage/abilities/effects/common/ExileSpellEffect.java b/Mage/src/mage/abilities/effects/common/ExileSpellEffect.java index 2725630dc30..f74c0d9fd30 100644 --- a/Mage/src/mage/abilities/effects/common/ExileSpellEffect.java +++ b/Mage/src/mage/abilities/effects/common/ExileSpellEffect.java @@ -64,7 +64,7 @@ public class ExileSpellEffect extends OneShotEffect implements MageSingleton { if (controller != null) { Card spellCard = game.getStack().getSpell(source.getSourceId()).getCard(); if (spellCard != null) { - controller.moveCards(spellCard, null, Zone.EXILED, source, game); + controller.moveCards(spellCard, Zone.EXILED, source, game); } return true; } diff --git a/Mage/src/mage/game/combat/CombatGroup.java b/Mage/src/mage/game/combat/CombatGroup.java index e7cf83c8a75..51c1c196256 100644 --- a/Mage/src/mage/game/combat/CombatGroup.java +++ b/Mage/src/mage/game/combat/CombatGroup.java @@ -411,12 +411,30 @@ public class CombatGroup implements Serializable, Copyable { return true; } + /** + * + * @param blockerId + * @param playerId controller of the blocking creature + * @param game + */ public void addBlocker(UUID blockerId, UUID playerId, Game game) { for (UUID attackerId : attackers) { if (game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARE_BLOCKER, attackerId, blockerId, playerId))) { return; } } + addBlockerToGroup(blockerId, playerId, game); + } + + /** + * Adds a blocker to a combat group without creating a DECLARE_BLOCKER + * event. + * + * @param blockerId + * @param playerId controller of the blocking creature + * @param game + */ + public void addBlockerToGroup(UUID blockerId, UUID playerId, Game game) { Permanent blocker = game.getPermanent(blockerId); if (blockerId != null && blocker != null) { blocker.setBlocking(blocker.getBlocking() + 1);