mirror of
https://github.com/correl/mage.git
synced 2024-12-25 03:00:15 +00:00
Implemented banding (#41)
This commit is contained in:
parent
adec5cf88b
commit
e7301e2c08
24 changed files with 708 additions and 136 deletions
|
@ -423,6 +423,7 @@ public class Card extends MagePermanent implements MouseMotionListener, MouseLis
|
|||
tooltipShowing = false;
|
||||
ArrowBuilder.getBuilder().removeArrowsByType(gameId, ArrowBuilder.Type.TARGET);
|
||||
ArrowBuilder.getBuilder().removeArrowsByType(gameId, ArrowBuilder.Type.PAIRED);
|
||||
ArrowBuilder.getBuilder().removeArrowsByType(gameId, ArrowBuilder.Type.BANDED);
|
||||
ArrowBuilder.getBuilder().removeArrowsByType(gameId, ArrowBuilder.Type.SOURCE);
|
||||
ArrowBuilder.getBuilder().removeArrowsByType(gameId, ArrowBuilder.Type.ENCHANT_PLAYERS);
|
||||
}
|
||||
|
|
|
@ -379,6 +379,7 @@ public class MageActionCallback implements ActionCallback {
|
|||
ArrowUtil.drawArrowsForTargets(data, parentPoint);
|
||||
ArrowUtil.drawArrowsForSource(data, parentPoint);
|
||||
ArrowUtil.drawArrowsForPairedCards(data, parentPoint);
|
||||
ArrowUtil.drawArrowsForBandedCards(data, parentPoint);
|
||||
ArrowUtil.drawArrowsForEnchantPlayers(data, parentPoint);
|
||||
tooltipCard = data.card;
|
||||
showTooltipPopup(data, parentComponent, parentPoint);
|
||||
|
@ -441,6 +442,7 @@ public class MageActionCallback implements ActionCallback {
|
|||
public void hideGameUpdate(UUID gameId) {
|
||||
ArrowBuilder.getBuilder().removeArrowsByType(gameId, ArrowBuilder.Type.TARGET);
|
||||
ArrowBuilder.getBuilder().removeArrowsByType(gameId, ArrowBuilder.Type.PAIRED);
|
||||
ArrowBuilder.getBuilder().removeArrowsByType(gameId, ArrowBuilder.Type.BANDED);
|
||||
ArrowBuilder.getBuilder().removeArrowsByType(gameId, ArrowBuilder.Type.SOURCE);
|
||||
ArrowBuilder.getBuilder().removeArrowsByType(gameId, ArrowBuilder.Type.ENCHANT_PLAYERS);
|
||||
}
|
||||
|
@ -452,6 +454,7 @@ public class MageActionCallback implements ActionCallback {
|
|||
if (gameId != null) {
|
||||
ArrowBuilder.getBuilder().removeArrowsByType(gameId, ArrowBuilder.Type.TARGET);
|
||||
ArrowBuilder.getBuilder().removeArrowsByType(gameId, ArrowBuilder.Type.PAIRED);
|
||||
ArrowBuilder.getBuilder().removeArrowsByType(gameId, ArrowBuilder.Type.BANDED);
|
||||
ArrowBuilder.getBuilder().removeArrowsByType(gameId, ArrowBuilder.Type.SOURCE);
|
||||
ArrowBuilder.getBuilder().removeArrowsByType(gameId, ArrowBuilder.Type.ENCHANT_PLAYERS);
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ public class ArrowBuilder {
|
|||
private int currentHeight;
|
||||
|
||||
public enum Type {
|
||||
PAIRED, SOURCE, TARGET, COMBAT, ENCHANT_PLAYERS
|
||||
PAIRED, BANDED, SOURCE, TARGET, COMBAT, ENCHANT_PLAYERS
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -34,6 +34,23 @@ public final class ArrowUtil {
|
|||
}
|
||||
}
|
||||
|
||||
public static void drawArrowsForBandedCards(TransferData data, Point parentPoint) {
|
||||
if (data.card.getBandedCards() != null && !data.card.getBandedCards().isEmpty()) {
|
||||
Point me = new Point(data.locationOnScreen);
|
||||
me.translate(-parentPoint.x, -parentPoint.y);
|
||||
for (PlayAreaPanel pa : MageFrame.getGame(data.gameId).getPlayers().values()) {
|
||||
for (UUID uuid : data.card.getBandedCards()) {
|
||||
MagePermanent permanent = pa.getBattlefieldPanel().getPermanents().get(uuid);
|
||||
if (permanent != null) {
|
||||
Point target = permanent.getLocationOnScreen();
|
||||
target.translate(-parentPoint.x, -parentPoint.y);
|
||||
ArrowBuilder.getBuilder().addArrow(data.gameId, (int) me.getX() + 35, (int) me.getY(), (int) target.getX() + 40, (int) target.getY() + 10, Color.yellow, ArrowBuilder.Type.BANDED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void drawArrowsForEnchantPlayers(TransferData data, Point parentPoint) {
|
||||
if (data.gameId != null && MageFrame.getGame(data.gameId) != null) {
|
||||
for (PlayAreaPanel pa : MageFrame.getGame(data.gameId).getPlayers().values()) {
|
||||
|
|
|
@ -113,6 +113,7 @@ public class CardView extends SimpleCardView {
|
|||
protected List<UUID> targets;
|
||||
|
||||
protected UUID pairedCard;
|
||||
protected List<UUID> bandedCards;
|
||||
protected boolean paid;
|
||||
protected List<CounterView> counters;
|
||||
|
||||
|
@ -202,6 +203,7 @@ public class CardView extends SimpleCardView {
|
|||
this.targets = null;
|
||||
|
||||
this.pairedCard = cardView.pairedCard;
|
||||
this.bandedCards = null;
|
||||
this.paid = cardView.paid;
|
||||
this.counters = null;
|
||||
|
||||
|
@ -353,6 +355,10 @@ public class CardView extends SimpleCardView {
|
|||
if (permanent.getCounters(game) != null && !permanent.getCounters(game).isEmpty()) {
|
||||
this.loyalty = Integer.toString(permanent.getCounters(game).getCount(CounterType.LOYALTY));
|
||||
this.pairedCard = permanent.getPairedCard() != null ? permanent.getPairedCard().getSourceId() : null;
|
||||
this.bandedCards = new ArrayList<>();
|
||||
for (UUID bandedCard : permanent.getBandedCards()) {
|
||||
bandedCards.add(bandedCard);
|
||||
}
|
||||
counters = new ArrayList<>();
|
||||
for (Counter counter : permanent.getCounters(game).values()) {
|
||||
counters.add(new CounterView(counter));
|
||||
|
@ -885,6 +891,10 @@ public class CardView extends SimpleCardView {
|
|||
return pairedCard;
|
||||
}
|
||||
|
||||
public List<UUID> getBandedCards() {
|
||||
return bandedCards;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
|
|
@ -121,7 +121,7 @@ class BalduvianWarlordUnblockEffect extends OneShotEffect {
|
|||
if (combatGroups != null) {
|
||||
for (CombatGroup combatGroup : combatGroups) {
|
||||
if (combatGroup != null) {
|
||||
combatGroup.setBlocked(false);
|
||||
combatGroup.setBlocked(false, game);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,7 +108,7 @@ class CurtainOfLightEffect extends OneShotEffect {
|
|||
if (controller != null && permanent != null) {
|
||||
CombatGroup combatGroup = game.getCombat().findGroup(permanent.getId());
|
||||
if (combatGroup != null) {
|
||||
combatGroup.setBlocked(true);
|
||||
combatGroup.setBlocked(true, game);
|
||||
game.informPlayers(permanent.getLogName() + " has become blocked");
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -109,7 +109,7 @@ class DazzlingBeautyEffect extends OneShotEffect {
|
|||
if (controller != null && permanent != null) {
|
||||
CombatGroup combatGroup = game.getCombat().findGroup(permanent.getId());
|
||||
if (combatGroup != null) {
|
||||
combatGroup.setBlocked(true);
|
||||
combatGroup.setBlocked(true, game);
|
||||
game.informPlayers(permanent.getLogName() + " has become blocked");
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -134,7 +134,7 @@ class FalseOrdersUnblockEffect extends OneShotEffect {
|
|||
if (combatGroups != null) {
|
||||
for (CombatGroup combatGroup : combatGroups) {
|
||||
if (combatGroup != null) {
|
||||
combatGroup.setBlocked(false);
|
||||
combatGroup.setBlocked(false, game);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,22 +113,36 @@ class GeneralJarkeldSwitchBlockersEffect extends OneShotEffect {
|
|||
Set<Permanent> blockers2 = new HashSet<>();
|
||||
Set<Permanent> multiBlockers = new HashSet<>();
|
||||
|
||||
blockerSearch1:
|
||||
for (UUID blockerId : chosenGroup1.getBlockers()) {
|
||||
Permanent blocker = game.getPermanent(blockerId);
|
||||
if (blocker != null) {
|
||||
if (blocker.getBlocking() > 1) {
|
||||
multiBlockers.add(blocker);
|
||||
if (game.getCombat().blockingGroupsContains(blocker.getId())) { // if (blocker.getBlocking() > 1) {
|
||||
for (CombatGroup group : game.getCombat().getBlockingGroups()) {
|
||||
if (group.getAttackers().size() > 1) {
|
||||
multiBlockers.add(blocker);
|
||||
continue blockerSearch1;
|
||||
}
|
||||
}
|
||||
blockers1.add(blocker); // this should not happen
|
||||
} else {
|
||||
blockers1.add(blocker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blockerSearch2:
|
||||
for (UUID blockerId : chosenGroup2.getBlockers()) {
|
||||
Permanent blocker = game.getPermanent(blockerId);
|
||||
if (blocker != null) {
|
||||
if (blocker.getBlocking() > 1) {
|
||||
multiBlockers.add(blocker);
|
||||
if (game.getCombat().blockingGroupsContains(blocker.getId())) { // if (blocker.getBlocking() > 1) {
|
||||
for (CombatGroup group : game.getCombat().getBlockingGroups()) {
|
||||
if (group.getAttackers().size() > 1) {
|
||||
multiBlockers.add(blocker);
|
||||
continue blockerSearch2;
|
||||
}
|
||||
}
|
||||
blockers2.add(blocker); // this should not happen
|
||||
} else {
|
||||
blockers2.add(blocker);
|
||||
}
|
||||
|
@ -141,11 +155,11 @@ class GeneralJarkeldSwitchBlockersEffect extends OneShotEffect {
|
|||
|
||||
// the ability doesn't unblock a group that loses all blockers, however it will newly block a previously unblocked group if it gains a blocker this way
|
||||
if (!(chosenGroup1.getBlockers().isEmpty())) {
|
||||
chosenGroup1.setBlocked(true);
|
||||
chosenGroup1.setBlocked(true, game);
|
||||
chosenGroup1.pickBlockerOrder(attacker1.getControllerId(), game);
|
||||
}
|
||||
if (!(chosenGroup2.getBlockers().isEmpty())) {
|
||||
chosenGroup2.setBlocked(true);
|
||||
chosenGroup2.setBlocked(true, game);
|
||||
chosenGroup2.pickBlockerOrder(attacker2.getControllerId(), game);
|
||||
}
|
||||
return true;
|
||||
|
@ -168,42 +182,40 @@ class GeneralJarkeldSwitchBlockersEffect extends OneShotEffect {
|
|||
// for handling multi-blockers (Two Headed Giant of Foriys, etc.)
|
||||
blockerIteration:
|
||||
for (Permanent blocker : blockers) {
|
||||
if (blocker.getBlocking() > 1) {
|
||||
CombatGroup blockGroup = null;
|
||||
for (CombatGroup group : game.getCombat().getBlockingGroups()) {
|
||||
if (group.getBlockers().contains(blocker.getId())) {
|
||||
blockGroup = group;
|
||||
break;
|
||||
CombatGroup blockGroup = null;
|
||||
for (CombatGroup group : game.getCombat().getBlockingGroups()) {
|
||||
if (group.getBlockers().contains(blocker.getId())) {
|
||||
blockGroup = group;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (blockGroup != null) {
|
||||
CombatGroup chosenGroup = null;
|
||||
boolean sameBlocked = false;
|
||||
for (CombatGroup group : game.getCombat().getGroups()) {
|
||||
if (group.getBlocked() && group.getBlockers().contains(blocker.getId())) {
|
||||
if (group == chosenGroup1 || group == chosenGroup2) {
|
||||
if (sameBlocked) {
|
||||
continue blockerIteration;
|
||||
}
|
||||
sameBlocked = true;
|
||||
chosenGroup = group;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (blockGroup != null) {
|
||||
CombatGroup chosenGroup = null;
|
||||
boolean sameBlocked = false;
|
||||
for (CombatGroup group : game.getCombat().getGroups()) {
|
||||
if (group.getBlocked() && group.getBlockers().contains(blocker.getId())) {
|
||||
if (group == chosenGroup1 || group == chosenGroup2) {
|
||||
if (sameBlocked) {
|
||||
continue blockerIteration;
|
||||
}
|
||||
sameBlocked = true;
|
||||
chosenGroup = group;
|
||||
}
|
||||
}
|
||||
|
||||
if (sameBlocked && chosenGroup != null) { // if none (should not happen) or all the blockers correspond to Jarkeld's targets, the blockers remain the same
|
||||
CombatGroup otherGroup = (chosenGroup.equals(chosenGroup1) ? chosenGroup2 : chosenGroup1);
|
||||
chosenGroup.remove(blocker.getId());
|
||||
for (UUID attacker : chosenGroup.getAttackers()) {
|
||||
blockGroup.remove(attacker);
|
||||
}
|
||||
|
||||
if (sameBlocked && chosenGroup != null) { // if none (should not happen) or all the blockers correspond to Jarkeld's targets, the blockers remain the same
|
||||
CombatGroup otherGroup = (chosenGroup.equals(chosenGroup1) ? chosenGroup2 : chosenGroup1);
|
||||
chosenGroup.remove(blocker.getId());
|
||||
for (UUID attacker : chosenGroup.getAttackers()) {
|
||||
blockGroup.remove(attacker);
|
||||
}
|
||||
otherGroup.addBlockerToGroup(blocker.getId(), controller.getId(), game);
|
||||
for (UUID attacker : otherGroup.getAttackers()) {
|
||||
// 10/4/2004 The new blocker does not trigger any abilities which trigger on creatures becoming blockers, because the creatures were already blockers and the simple change of who is blocking does not trigger such abilities.
|
||||
game.getCombat().addBlockingGroup(blocker.getId(), attacker, controller.getId(), game);
|
||||
}
|
||||
blockGroup.pickAttackerOrder(blocker.getControllerId(), game);
|
||||
otherGroup.addBlockerToGroup(blocker.getId(), controller.getId(), game);
|
||||
for (UUID attacker : otherGroup.getAttackers()) {
|
||||
// 10/4/2004 The new blocker does not trigger any abilities which trigger on creatures becoming blockers, because the creatures were already blockers and the simple change of who is blocking does not trigger such abilities.
|
||||
game.getCombat().addBlockingGroup(blocker.getId(), attacker, controller.getId(), game);
|
||||
}
|
||||
blockGroup.pickAttackerOrder(blocker.getControllerId(), game);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
75
Mage.Sets/src/mage/cards/i/IcatianInfantry.java
Normal file
75
Mage.Sets/src/mage/cards/i/IcatianInfantry.java
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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.cards.i;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.MageInt;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.common.continuous.GainAbilitySourceEffect;
|
||||
import mage.abilities.keyword.BandingAbility;
|
||||
import mage.abilities.keyword.FirstStrikeAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Zone;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author L_J
|
||||
*/
|
||||
public class IcatianInfantry extends CardImpl {
|
||||
|
||||
public IcatianInfantry(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{W}");
|
||||
this.subtype.add(SubType.HUMAN);
|
||||
this.subtype.add(SubType.SOLDIER);
|
||||
|
||||
this.power = new MageInt(1);
|
||||
this.toughness = new MageInt(1);
|
||||
|
||||
// {1}: Icatian Infantry gains first strike until end of turn.
|
||||
this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new GainAbilitySourceEffect(FirstStrikeAbility.getInstance(), Duration.EndOfTurn), new ManaCostsImpl("{1}")));
|
||||
|
||||
// {1}: Icatian Infantry gains banding until end of turn.
|
||||
this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new GainAbilitySourceEffect(BandingAbility.getInstance(), Duration.EndOfTurn), new ManaCostsImpl("{1}")));
|
||||
}
|
||||
|
||||
public IcatianInfantry(final IcatianInfantry card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IcatianInfantry copy() {
|
||||
return new IcatianInfantry(this);
|
||||
}
|
||||
|
||||
}
|
|
@ -177,7 +177,7 @@ class ImprisonUnblockEffect extends OneShotEffect {
|
|||
if (combatGroups != null) {
|
||||
for (CombatGroup combatGroup : combatGroups) {
|
||||
if (combatGroup != null) {
|
||||
combatGroup.setBlocked(false);
|
||||
combatGroup.setBlocked(false, game);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@ public class SorrowsPath extends CardImpl {
|
|||
this.addAbility(ability);
|
||||
|
||||
// Whenever Sorrow's Path becomes tapped, it deals 2 damage to you and each creature you control.
|
||||
Ability ability2 = new BecomesTappedSourceTriggeredAbility(new DamageControllerEffect(2 , "it"));
|
||||
Ability ability2 = new BecomesTappedSourceTriggeredAbility(new DamageControllerEffect(2));
|
||||
ability2.addEffect(new DamageAllEffect(2, new FilterControlledCreaturePermanent()).setText("and each creature you control"));
|
||||
this.addAbility(ability2);
|
||||
}
|
||||
|
@ -152,7 +152,7 @@ class SorrowsPathSwitchBlockersEffect extends OneShotEffect {
|
|||
}
|
||||
|
||||
private CombatGroup findBlockingGroup(Permanent blocker, Game game) {
|
||||
if (blocker.getBlocking() > 1) {
|
||||
if (game.getCombat().blockingGroupsContains(blocker.getId())) { // if (blocker.getBlocking() > 1) {
|
||||
for (CombatGroup group : game.getCombat().getBlockingGroups()) {
|
||||
if (group.getBlockers().contains(blocker.getId())) {
|
||||
return group;
|
||||
|
@ -176,7 +176,7 @@ class SorrowsPathSwitchBlockersEffect extends OneShotEffect {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void reassignBlocker(Permanent blocker, Set<Permanent> attackers, Game game) {
|
||||
for (Permanent attacker : attackers) {
|
||||
CombatGroup group = game.getCombat().findGroup(attacker.getId());
|
||||
|
|
|
@ -114,7 +114,7 @@ class TrapRunnerEffect extends OneShotEffect {
|
|||
if (controller != null && permanent != null) {
|
||||
CombatGroup combatGroup = game.getCombat().findGroup(permanent.getId());
|
||||
if (combatGroup != null) {
|
||||
combatGroup.setBlocked(true);
|
||||
combatGroup.setBlocked(true, game);
|
||||
game.informPlayers(permanent.getLogName() + " has become blocked");
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@ class YdwenEfreetEffect extends OneShotEffect {
|
|||
if (combatGroups != null) {
|
||||
for (CombatGroup combatGroup : combatGroups) {
|
||||
if (combatGroup != null) {
|
||||
combatGroup.setBlocked(false);
|
||||
combatGroup.setBlocked(false, game);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ import mage.cards.h.HighTide;
|
|||
import mage.cards.h.Homarid;
|
||||
import mage.cards.h.HomaridWarrior;
|
||||
import mage.cards.h.HymnToTourach;
|
||||
import mage.cards.i.IcatianInfantry;
|
||||
import mage.cards.i.IcatianJavelineers;
|
||||
import mage.cards.i.IcatianMoneychanger;
|
||||
import mage.cards.i.IcatianScout;
|
||||
|
@ -170,6 +171,10 @@ public class FallenEmpires extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Hymn to Tourach", 13, Rarity.COMMON, HymnToTourach.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Hymn to Tourach", 14, Rarity.COMMON, HymnToTourach.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Hymn to Tourach", 15, Rarity.COMMON, HymnToTourach.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Icatian Infantry", 144, Rarity.COMMON, IcatianInfantry.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Icatian Infantry", 145, Rarity.COMMON, IcatianInfantry.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Icatian Infantry", 146, Rarity.COMMON, IcatianInfantry.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Icatian Infantry", 147, Rarity.COMMON, IcatianInfantry.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Icatian Javelineers", 148, Rarity.COMMON, IcatianJavelineers.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Icatian Javelineers", 149, Rarity.COMMON, IcatianJavelineers.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Icatian Javelineers", 150, Rarity.COMMON, IcatianJavelineers.class, NON_FULL_USE_VARIOUS));
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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.abilities.keyword;
|
||||
|
||||
import java.io.ObjectStreamException;
|
||||
import mage.abilities.MageSingleton;
|
||||
import mage.abilities.StaticAbility;
|
||||
import mage.constants.Zone;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author L_J
|
||||
*/
|
||||
public class BandingAbility extends StaticAbility implements MageSingleton {
|
||||
|
||||
private static final BandingAbility instance = new BandingAbility();
|
||||
|
||||
private Object readResolve() throws ObjectStreamException {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static BandingAbility getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private BandingAbility() {
|
||||
super(Zone.BATTLEFIELD, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "banding";
|
||||
}
|
||||
|
||||
@Override
|
||||
public BandingAbility copy() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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.filter.predicate.permanent;
|
||||
|
||||
import mage.filter.predicate.Predicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.combat.CombatGroup;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author L_J
|
||||
*/
|
||||
public class AttackingSameNotBandedPredicate implements Predicate<Permanent> {
|
||||
|
||||
private final UUID defenderId;
|
||||
|
||||
public AttackingSameNotBandedPredicate(UUID defenderId) {
|
||||
this.defenderId = defenderId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Permanent input, Game game) {
|
||||
CombatGroup combatGroup = game.getCombat().findGroup(input.getId());
|
||||
if (combatGroup != null) {
|
||||
return input.isAttacking()
|
||||
&& input.getBandedCards().isEmpty()
|
||||
&& combatGroup.getDefenderId().equals(defenderId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1803,12 +1803,24 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
Permanent paired = perm.getPairedCard().getPermanent(this);
|
||||
if (paired == null || !perm.getControllerId().equals(paired.getControllerId()) || paired.getPairedCard() == null) {
|
||||
perm.setPairedCard(null);
|
||||
if (paired != null) {
|
||||
if (paired != null && paired.getPairedCard() != null) {
|
||||
paired.setPairedCard(null);
|
||||
}
|
||||
somethingHappened = true;
|
||||
}
|
||||
}
|
||||
if (perm.getBandedCards() != null && !perm.getBandedCards().isEmpty()) {
|
||||
for (UUID bandedId : new ArrayList<>(perm.getBandedCards())) {
|
||||
Permanent banded = getPermanent(bandedId);
|
||||
if (banded == null || !perm.getControllerId().equals(banded.getControllerId()) || !banded.getBandedCards().contains(perm.getId())) {
|
||||
perm.removeBandedCard(bandedId);
|
||||
if (banded != null && banded.getBandedCards().contains(perm.getId())) {
|
||||
banded.removeBandedCard(perm.getId());
|
||||
}
|
||||
somethingHappened = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (perm.getPairedCard() != null) {
|
||||
//702.93e.: ...stops being a creature
|
||||
Permanent paired = perm.getPairedCard().getPermanent(this);
|
||||
|
@ -1817,6 +1829,15 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
paired.setPairedCard(null);
|
||||
}
|
||||
somethingHappened = true;
|
||||
} else if (perm.getBandedCards() != null && !perm.getBandedCards().isEmpty()) {
|
||||
perm.clearBandedCards();
|
||||
for (UUID bandedId : perm.getBandedCards()) {
|
||||
Permanent banded = getPermanent(bandedId);
|
||||
if (banded != null) {
|
||||
banded.removeBandedCard(perm.getId());
|
||||
}
|
||||
somethingHappened = true;
|
||||
}
|
||||
}
|
||||
if (perm.isPlaneswalker()) {
|
||||
//20091005 - 704.5i
|
||||
|
|
|
@ -29,23 +29,31 @@ package mage.game.combat;
|
|||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.RequirementEffect;
|
||||
import mage.abilities.effects.RestrictionEffect;
|
||||
import mage.abilities.keyword.BandingAbility;
|
||||
import mage.abilities.keyword.VigilanceAbility;
|
||||
import mage.abilities.keyword.special.JohanVigilanceAbility;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.common.FilterControlledCreaturePermanent;
|
||||
import mage.filter.common.FilterCreatureForCombatBlock;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.filter.predicate.mageobject.AbilityPredicate;
|
||||
import mage.filter.predicate.permanent.AttackingSameNotBandedPredicate;
|
||||
import mage.filter.predicate.permanent.PermanentIdPredicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.events.GameEvent.EventType;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.players.PlayerList;
|
||||
import mage.target.common.TargetControlledPermanent;
|
||||
import mage.target.common.TargetDefender;
|
||||
import mage.util.CardUtil;
|
||||
import mage.util.Copyable;
|
||||
|
@ -116,6 +124,10 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
return blockingGroups.values();
|
||||
}
|
||||
|
||||
public boolean blockingGroupsContains(UUID blockerId) {
|
||||
return blockingGroups.containsKey(blockerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all possible defender (players and plainwalkers) That does not mean
|
||||
* neccessarly mean that they are really attacked
|
||||
|
@ -285,7 +297,8 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
attackingPermanent.tap(game); // to tap with event finally here is needed to prevent abusing of Vampire Envoy like cards
|
||||
}
|
||||
}
|
||||
// This can only be used to modify the event, the ttack can't be replaced here
|
||||
handleBanding(attacker, game);
|
||||
// This can only be used to modify the event, the attack can't be replaced here
|
||||
game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.ATTACKER_DECLARED, group.defenderId, attacker, attackingPlayerId));
|
||||
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.ATTACKER_DECLARED, group.defenderId, attacker, attackingPlayerId));
|
||||
}
|
||||
|
@ -301,6 +314,66 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
}
|
||||
}
|
||||
|
||||
private void handleBanding(UUID creatureId, Game game) {
|
||||
Player player = game.getPlayer(attackingPlayerId);
|
||||
Permanent attacker = game.getPermanent(creatureId);
|
||||
if (attacker != null && player != null) {
|
||||
CombatGroup combatGroup = findGroup(attacker.getId());
|
||||
if (combatGroup != null && attacker.getAbilities().containsKey(BandingAbility.getInstance().getId()) && attacker.getBandedCards().isEmpty() && getAttackers().size() > 1) {
|
||||
boolean isBanded = false;
|
||||
FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("attacking creature to band with " + attacker.getLogName());
|
||||
filter.add(Predicates.not(new PermanentIdPredicate(creatureId)));
|
||||
filter.add(new AttackingSameNotBandedPredicate(combatGroup.getDefenderId())); // creature that isn't already banded, and is attacking the same player or planeswalker
|
||||
while (player.canRespond()) {
|
||||
TargetControlledPermanent target = new TargetControlledPermanent(1, 1, filter, true);
|
||||
target.setRequired(false);
|
||||
if (!target.canChoose(attackingPlayerId, game)
|
||||
|| game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARING_ATTACKERS, attackingPlayerId, attackingPlayerId))
|
||||
|| !player.chooseUse(Outcome.AIDontUseIt, "Do you wish to " + (isBanded ? "band " + attacker.getLogName() + " with another " : "form a band with " + attacker.getLogName() + " and an " ) + "attacking creature?", null, game)) {
|
||||
break;
|
||||
}
|
||||
if (target.choose(Outcome.Benefit, attackingPlayerId, null, game)) {
|
||||
isBanded = true;
|
||||
for (UUID targetId: target.getTargets()) {
|
||||
Permanent permanent = game.getPermanent(targetId);
|
||||
if (permanent != null) {
|
||||
if (permanent != null) {
|
||||
|
||||
for (UUID bandedId : attacker.getBandedCards()) {
|
||||
permanent.addBandedCard(bandedId);
|
||||
Permanent banded = game.getPermanent(bandedId);
|
||||
if (banded != null) {
|
||||
banded.addBandedCard(targetId);
|
||||
}
|
||||
}
|
||||
permanent.addBandedCard(creatureId);
|
||||
attacker.addBandedCard(targetId);
|
||||
if (!permanent.getAbilities().containsKey(BandingAbility.getInstance().getId())) {
|
||||
filter.add(new AbilityPredicate(BandingAbility.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isBanded) {
|
||||
StringBuilder sb = new StringBuilder(player.getLogName()).append(" formed a band with ").append((attacker.getBandedCards().size() + 1) + " creatures: ");
|
||||
sb.append(attacker.getLogName());
|
||||
int i = 0;
|
||||
for (UUID id : attacker.getBandedCards()) {
|
||||
i++;
|
||||
sb.append(", ");
|
||||
Permanent permanent = game.getPermanent(id);
|
||||
if (permanent != null) {
|
||||
sb.append(permanent.getLogName());
|
||||
}
|
||||
}
|
||||
game.informPlayers(sb.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkAttackRequirements(Player player, Game game) {
|
||||
//20101001 - 508.1d
|
||||
for (Permanent creature : player.getAvailableAttackers(game)) {
|
||||
|
@ -1094,14 +1167,16 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
@SuppressWarnings("deprecation")
|
||||
public boolean declareAttacker(UUID creatureId, UUID defenderId, UUID playerId, Game game) {
|
||||
Permanent attacker = game.getPermanent(creatureId);
|
||||
if (!attacker.getAbilities().containsKey(VigilanceAbility.getInstance().getId()) && !attacker.getAbilities().containsKey(JohanVigilanceAbility.getInstance().getId())) {
|
||||
if (!attacker.isTapped()) {
|
||||
attacker.setTapped(true);
|
||||
attackersTappedByAttack.add(attacker.getId());
|
||||
if (attacker != null) {
|
||||
if (!attacker.getAbilities().containsKey(VigilanceAbility.getInstance().getId()) && !attacker.getAbilities().containsKey(JohanVigilanceAbility.getInstance().getId())) {
|
||||
if (!attacker.isTapped()) {
|
||||
attacker.setTapped(true);
|
||||
attackersTappedByAttack.add(attacker.getId());
|
||||
}
|
||||
}
|
||||
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARE_ATTACKER, defenderId, creatureId, playerId))) {
|
||||
return addAttackerToCombat(creatureId, defenderId, game);
|
||||
}
|
||||
}
|
||||
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARE_ATTACKER, defenderId, creatureId, playerId))) {
|
||||
return addAttackerToCombat(creatureId, defenderId, game);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -1158,17 +1233,37 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
return true;
|
||||
}
|
||||
|
||||
// add blocking group for creatures that block more than one creature
|
||||
/**
|
||||
* Add blocking group for creatures that already block more than one creature
|
||||
*
|
||||
* @param blockerId
|
||||
* @param attackerId
|
||||
* @param playerId
|
||||
* @param game
|
||||
*/
|
||||
public void addBlockingGroup(UUID blockerId, UUID attackerId, UUID playerId, Game game) {
|
||||
addBlockingGroup(blockerId, attackerId, playerId, game, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the previous addBlockingGroup instead (solveBanding should always be true
|
||||
* outside this method)
|
||||
*
|
||||
* @param blockerId
|
||||
* @param attackerId
|
||||
* @param playerId
|
||||
* @param game
|
||||
* @param solveBanding check whether also add creatures banded with attackerId
|
||||
*/
|
||||
public void addBlockingGroup(UUID blockerId, UUID attackerId, UUID playerId, Game game, boolean solveBanding) {
|
||||
Permanent blocker = game.getPermanent(blockerId);
|
||||
if (blockerId != null && blocker != null && blocker.getBlocking() > 1) {
|
||||
if (!blockingGroups.containsKey(blockerId)) {
|
||||
if (!blockingGroupsContains(blockerId)) {
|
||||
CombatGroup newGroup = new CombatGroup(playerId, false, playerId);
|
||||
newGroup.blockers.add(blockerId);
|
||||
// add all blocked attackers
|
||||
for (CombatGroup group : groups) {
|
||||
if (group.getBlockers().contains(blockerId)) {
|
||||
// take into account banding
|
||||
for (UUID attacker : group.attackers) {
|
||||
newGroup.attackers.add(attacker);
|
||||
}
|
||||
|
@ -1176,10 +1271,27 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
}
|
||||
blockingGroups.put(blockerId, newGroup);
|
||||
} else {
|
||||
//TODO: handle banding
|
||||
blockingGroups.get(blockerId).attackers.add(attackerId);
|
||||
}
|
||||
// "blocker.setBlocking(blocker.getBlocking() + 1)" is handled by the attacking combat group
|
||||
// "blocker.setBlocking(blocker.getBlocking() + 1)" is handled by the attacking combat group (in addBlockerToGroup)
|
||||
}
|
||||
if (solveBanding) {
|
||||
Permanent attacker = game.getPermanent(attackerId);
|
||||
if (attacker != null) {
|
||||
for (UUID bandedId : attacker.getBandedCards()) {
|
||||
if (!bandedId.equals(attackerId)) {
|
||||
if (blockingGroups.get(blockerId) == null || !blockingGroups.get(blockerId).attackers.contains(bandedId)) {
|
||||
Permanent banded = game.getPermanent(bandedId);
|
||||
CombatGroup bandedGroup = findGroup(bandedId);
|
||||
if (banded != null && bandedGroup != null) {
|
||||
bandedGroup.addBlockerToGroup(blockerId, playerId, game);
|
||||
addBlockingGroup(blockerId, bandedId, playerId, game, false);
|
||||
blocker.setBlocking(blocker.getBlocking() - 1); // this intends to offset the blocking addition from bandedGroup.addBlockerToGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1202,8 +1314,18 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
creature.setBlocking(0);
|
||||
creature.setRemovedFromCombat(true);
|
||||
for (CombatGroup group : groups) {
|
||||
for (UUID attackerId : group.attackers) {
|
||||
Permanent attacker = game.getPermanent(attackerId);
|
||||
if (attacker != null) {
|
||||
attacker.removeBandedCard(creatureId);
|
||||
}
|
||||
}
|
||||
result |= group.remove(creatureId);
|
||||
}
|
||||
for (CombatGroup blockingGroup : getBlockingGroups()) {
|
||||
result |= blockingGroup.remove(creatureId);
|
||||
}
|
||||
creature.clearBandedCards();
|
||||
blockingGroups.remove(creatureId);
|
||||
if (result && withInfo) {
|
||||
game.informPlayers(creature.getLogName() + " removed from combat");
|
||||
|
@ -1220,6 +1342,7 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
if (creature != null) {
|
||||
creature.setAttacking(false);
|
||||
creature.setBlocking(0);
|
||||
creature.clearBandedCards();
|
||||
}
|
||||
}
|
||||
for (UUID blocker : group.blockers) {
|
||||
|
@ -1227,6 +1350,7 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
if (creature != null) {
|
||||
creature.setAttacking(false);
|
||||
creature.setBlocking(0);
|
||||
creature.clearBandedCards();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1391,48 +1515,80 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manual player action for undoing one declared blocker
|
||||
* (used for multi-blocker creatures)
|
||||
*
|
||||
* @param blockerId
|
||||
* @param groupToUnblock
|
||||
* @param game
|
||||
*/
|
||||
public void removeBlockerGromGroup(UUID blockerId, CombatGroup groupToUnblock, Game game) {
|
||||
// Manual player action for undoing one declared blocker (used for multi-blocker creatures)
|
||||
Permanent creature = game.getPermanent(blockerId);
|
||||
if (creature != null) {
|
||||
List<CombatGroup> groupsToCheck = new ArrayList<>();
|
||||
for (CombatGroup group : groups) {
|
||||
if (group.equals(groupToUnblock) && group.blockers.contains(blockerId)) {
|
||||
group.blockers.remove(blockerId);
|
||||
group.blockerOrder.remove(blockerId);
|
||||
if (group.blockers.isEmpty()) {
|
||||
group.blocked = false;
|
||||
groupsToCheck.add(group);
|
||||
for (UUID attackerId : group.getAttackers()) {
|
||||
Permanent attacker = game.getPermanent(attackerId);
|
||||
if (attacker != null) {
|
||||
for (UUID bandedId : attacker.getBandedCards()) {
|
||||
if (!bandedId.equals(attackerId)) {
|
||||
CombatGroup bandedGroup = findGroup(bandedId);
|
||||
if (bandedGroup != null) {
|
||||
groupsToCheck.add(bandedGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (creature.getBlocking() > 0) {
|
||||
}
|
||||
}
|
||||
for (CombatGroup group : groupsToCheck) {
|
||||
group.blockers.remove(blockerId);
|
||||
group.blockerOrder.remove(blockerId);
|
||||
if (group.blockers.isEmpty()) {
|
||||
group.blocked = false;
|
||||
}
|
||||
if (creature.getBlocking() > 0) {
|
||||
if (group.equals(groupToUnblock)) {
|
||||
creature.setBlocking(creature.getBlocking() - 1);
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Trying to unblock creature, but blocking number value of creature < 1");
|
||||
}
|
||||
boolean canRemove = false;
|
||||
for (CombatGroup blockGroup : getBlockingGroups()) {
|
||||
if (blockGroup.blockers.contains(blockerId)) {
|
||||
for (UUID attackerId : group.getAttackers()) {
|
||||
blockGroup.attackers.remove(attackerId);
|
||||
blockGroup.attackerOrder.remove(attackerId);
|
||||
}
|
||||
if (creature.getBlocking() == 0) {
|
||||
blockGroup.blockers.remove(blockerId);
|
||||
blockGroup.attackerOrder.clear();
|
||||
}
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Trying to unblock creature, but blocking number value of creature < 1");
|
||||
}
|
||||
boolean canRemove = false;
|
||||
for (CombatGroup blockGroup : getBlockingGroups()) {
|
||||
if (blockGroup.blockers.contains(blockerId)) {
|
||||
for (UUID attackerId : group.getAttackers()) {
|
||||
blockGroup.attackers.remove(attackerId);
|
||||
blockGroup.attackerOrder.remove(attackerId);
|
||||
}
|
||||
if (blockGroup.blockers.isEmpty()) {
|
||||
canRemove = true;
|
||||
if (creature.getBlocking() == 0) {
|
||||
blockGroup.blockers.remove(blockerId);
|
||||
blockGroup.attackerOrder.clear();
|
||||
}
|
||||
}
|
||||
if (canRemove) {
|
||||
blockingGroups.remove(blockerId);
|
||||
if (blockGroup.blockers.isEmpty()) {
|
||||
canRemove = true;
|
||||
}
|
||||
}
|
||||
if (canRemove) {
|
||||
blockingGroups.remove(blockerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manual player action for undoing all declared blockers
|
||||
* (used for single-blocker creatures and multi-blockers exceeding blocking limit)
|
||||
*
|
||||
* @param blockerId
|
||||
* @param game
|
||||
*/
|
||||
public void removeBlocker(UUID blockerId, Game game) {
|
||||
// Manual player action for undoing all declared blockers (used for single-blocker creatures and multi-blockers exceeding blocking limit)
|
||||
for (CombatGroup group : groups) {
|
||||
if (group.blockers.contains(blockerId)) {
|
||||
group.blockers.remove(blockerId);
|
||||
|
|
|
@ -36,6 +36,7 @@ import java.util.UUID;
|
|||
import mage.abilities.common.ControllerAssignCombatDamageToBlockersAbility;
|
||||
import mage.abilities.common.ControllerDivideCombatDamageAbility;
|
||||
import mage.abilities.common.DamageAsThoughNotBlockedAbility;
|
||||
import mage.abilities.keyword.BandingAbility;
|
||||
import mage.abilities.keyword.CantBlockAloneAbility;
|
||||
import mage.abilities.keyword.DeathtouchAbility;
|
||||
import mage.abilities.keyword.DoubleStrikeAbility;
|
||||
|
@ -140,15 +141,19 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
return perm.getAbilities().containsKey(TrampleAbility.getInstance().getId());
|
||||
}
|
||||
|
||||
private boolean hasBanding(Permanent perm) {
|
||||
return perm.getAbilities().containsKey(BandingAbility.getInstance().getId());
|
||||
}
|
||||
|
||||
public void assignDamageToBlockers(boolean first, Game game) {
|
||||
if (!attackers.isEmpty() && (!first || hasFirstOrDoubleStrike(game))) {
|
||||
Permanent attacker = game.getPermanent(attackers.get(0));
|
||||
if (!assignsDefendingPlayerAndOrDefendingCreaturesDividedDamage(attacker, attacker.getControllerId(), first, game, true)) {
|
||||
if (attacker != null && !assignsDefendingPlayerAndOrDefendingCreaturesDividedDamage(attacker, attacker.getControllerId(), first, game, true)) {
|
||||
if (blockers.isEmpty()) {
|
||||
unblockedDamage(first, game);
|
||||
return;
|
||||
} else {
|
||||
Player player = game.getPlayer(defenderControlsDefensiveFormation(game) ? defendingPlayerId : attacker.getControllerId());
|
||||
Player player = game.getPlayer(defenderAssignsCombatDamage(game) ? defendingPlayerId : attacker.getControllerId());
|
||||
if (attacker.getAbilities().containsKey(DamageAsThoughNotBlockedAbility.getInstance().getId())) { // for handling creatures like Thorn Elemental
|
||||
if (player.chooseUse(Outcome.Damage, "Do you wish to assign damage for " + attacker.getLogName() + " as though it weren't blocked?", null, game)) {
|
||||
blocked = false;
|
||||
|
@ -244,7 +249,6 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
}
|
||||
|
||||
private void singleBlockerDamage(Player player, boolean first, Game game) {
|
||||
//TODO: handle banding
|
||||
Permanent blocker = game.getPermanent(blockers.get(0));
|
||||
Permanent attacker = game.getPermanent(attackers.get(0));
|
||||
if (blocker != null && attacker != null) {
|
||||
|
@ -273,7 +277,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
}
|
||||
}
|
||||
if (canDamage(blocker, first)) {
|
||||
if (blocker.getBlocking() == 1) { // blocking several creatures handled separately
|
||||
if (checkSoleBlockerAfter(blocker, game)) { // blocking several creatures handled separately
|
||||
if (!assignsDefendingPlayerAndOrDefendingCreaturesDividedDamage(blocker, blocker.getControllerId(), first, game, false)) {
|
||||
attacker.markDamage(blockerDamage, blocker.getId(), game, true, true);
|
||||
}
|
||||
|
@ -283,7 +287,6 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
}
|
||||
|
||||
private void multiBlockerDamage(Player player, boolean first, Game game) {
|
||||
//TODO: handle banding
|
||||
Permanent attacker = game.getPermanent(attackers.get(0));
|
||||
if (attacker == null) {
|
||||
return;
|
||||
|
@ -296,7 +299,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
for (UUID blockerId : blockerOrder) {
|
||||
Permanent blocker = game.getPermanent(blockerId);
|
||||
if (canDamage(blocker, first)) {
|
||||
if (blocker.getBlocking() == 1) { // blocking several creatures handled separately
|
||||
if (checkSoleBlockerAfter(blocker, game)) { // blocking several creatures handled separately
|
||||
blockerPower.put(blockerId, getDamageValueFromPermanent(blocker, game));
|
||||
}
|
||||
}
|
||||
|
@ -304,7 +307,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
Map<UUID, Integer> assigned = new HashMap<>();
|
||||
if (blocked) {
|
||||
boolean excessDamageToDefender = true;
|
||||
for (UUID blockerId : new ArrayList<>(blockerOrder)) { // prevent ConcurrentModificationException
|
||||
for (UUID blockerId : blockerOrder) {
|
||||
Permanent blocker = game.getPermanent(blockerId);
|
||||
if (blocker != null) {
|
||||
int lethalDamage;
|
||||
|
@ -381,7 +384,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
for (UUID blockerId : blockerOrder) {
|
||||
Permanent blocker = game.getPermanent(blockerId);
|
||||
if (canDamage(blocker, first)) {
|
||||
if (blocker.getBlocking() == 1) { // blocking several creatures handled separately
|
||||
if (checkSoleBlockerAfter(blocker, game)) { // blocking several creatures handled separately
|
||||
blockerPower.put(blockerId, getDamageValueFromPermanent(blocker, game));
|
||||
}
|
||||
}
|
||||
|
@ -435,6 +438,24 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
}
|
||||
}
|
||||
|
||||
public boolean checkSoleBlockerAfter (Permanent blocker, Game game) {
|
||||
// this solves some corner cases (involving banding) when finding out whether a blocker is blocking alone or not
|
||||
if (blocker.getBlocking() == 1) {
|
||||
if (game.getCombat().blockingGroups.get(blocker.getId()) == null) {
|
||||
return true;
|
||||
} else {
|
||||
for (CombatGroup group : game.getCombat().getBlockingGroups()) {
|
||||
if (group.blockers.contains(blocker.getId())) {
|
||||
if (group.attackers.size() == 1) {
|
||||
return true; // if blocker is blocking a band, this won't be true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Damages attacking creatures by a creature that blocked several ones
|
||||
* Damages only attackers as blocker was damage in
|
||||
|
@ -471,7 +492,8 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
if (blocker == null) {
|
||||
return;
|
||||
}
|
||||
Player player = game.getPlayer(blocker.getControllerId());
|
||||
boolean oldRuleDamage = attackerAssignsCombatDamage(game); // handles banding
|
||||
Player player = game.getPlayer(oldRuleDamage ? game.getCombat().getAttackingPlayerId() : blocker.getControllerId());
|
||||
int damage = getDamageValueFromPermanent(blocker, game);
|
||||
|
||||
if (canDamage(blocker, first)) {
|
||||
|
@ -486,11 +508,20 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
lethalDamage = Math.max(attacker.getToughness().getValue() - attacker.getDamage(), 0);
|
||||
}
|
||||
if (lethalDamage >= damage) {
|
||||
assigned.put(attackerId, damage);
|
||||
damage = 0;
|
||||
break;
|
||||
if (!oldRuleDamage) {
|
||||
assigned.put(attackerId, damage);
|
||||
damage = 0;
|
||||
break;
|
||||
} else if (damage == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
int damageAssigned = 0;
|
||||
if (!oldRuleDamage) {
|
||||
damageAssigned = player.getAmount(lethalDamage, damage, "Assign damage to " + attacker.getName(), game);
|
||||
} else {
|
||||
damageAssigned = player.getAmount(0, damage, "Assign damage to " + attacker.getName(), game);
|
||||
}
|
||||
int damageAssigned = player.getAmount(lethalDamage, damage, "Assign damage to " + attacker.getName(), game);
|
||||
assigned.put(attackerId, damageAssigned);
|
||||
damage -= damageAssigned;
|
||||
}
|
||||
|
@ -572,7 +603,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
if (blockers.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Player player = game.getPlayer(defenderControlsDefensiveFormation(game) ? defendingPlayerId : playerId);
|
||||
Player player = game.getPlayer(playerId); // game.getPlayer(defenderAssignsCombatDamage(game) ? defendingPlayerId : playerId); // this was incorrect because defenderAssignsCombatDamage might be false by the time damage is dealt
|
||||
List<UUID> blockerList = new ArrayList<>(blockers);
|
||||
blockerOrder.clear();
|
||||
while (player.canRespond()) {
|
||||
|
@ -717,7 +748,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
}
|
||||
blockWasLegal = false;
|
||||
}
|
||||
// Check if there are to many blockers (maxBlockedBy = 0 means no restrictions)
|
||||
// Check if there are too many blockers (maxBlockedBy = 0 means no restrictions)
|
||||
if (attacker != null && this.blocked && attacker.getMaxBlockedBy() > 0 && attacker.getMaxBlockedBy() < blockers.size()) {
|
||||
for (UUID blockerId : blockers) {
|
||||
Permanent blocker = game.getPermanent(blockerId);
|
||||
|
@ -759,15 +790,36 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
}
|
||||
|
||||
/**
|
||||
* There are effects, that set an attacker to be blcoked. Therefore this
|
||||
* There are effects, that set an attacker to be blocked. Therefore this
|
||||
* setter can be used.
|
||||
*
|
||||
* This method lacks a band check, use setBlocked(blocked, game) instead.
|
||||
*
|
||||
* @param blocked
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public void setBlocked(boolean blocked) {
|
||||
this.blocked = blocked;
|
||||
}
|
||||
|
||||
public void setBlocked(boolean blocked, Game game) {
|
||||
this.blocked = blocked;
|
||||
for (UUID attackerId : attackers) {
|
||||
Permanent attacker = game.getPermanent(attackerId);
|
||||
if (attacker != null) {
|
||||
for (UUID bandedId : attacker.getBandedCards()) {
|
||||
if (!bandedId.equals(attackerId)) {
|
||||
CombatGroup bandedGroup = game.getCombat().findGroup(bandedId);
|
||||
if (bandedGroup != null) {
|
||||
bandedGroup.blocked = blocked;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getBlocked() {
|
||||
return blocked;
|
||||
}
|
||||
|
@ -778,26 +830,70 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
}
|
||||
|
||||
public boolean changeDefenderPostDeclaration(UUID newDefenderId, Game game) {
|
||||
Permanent permanent = game.getPermanent(newDefenderId);
|
||||
if (permanent != null) {
|
||||
defenderId = newDefenderId;
|
||||
defendingPlayerId = permanent.getControllerId();
|
||||
defenderIsPlaneswalker = true;
|
||||
return true;
|
||||
} else {
|
||||
Player defender = game.getPlayer(newDefenderId);
|
||||
if (defender != null) {
|
||||
if (!defenderId.equals(newDefenderId)) {
|
||||
for (UUID attackerId : attackers) { // changing defender will remove a banded attacker from its current band
|
||||
Permanent attacker = game.getPermanent(attackerId);
|
||||
if (attacker != null && attacker.getBandedCards() != null) {
|
||||
for (UUID bandedId : attacker.getBandedCards()) {
|
||||
Permanent banded = game.getPermanent(bandedId);
|
||||
if (banded != null) {
|
||||
banded.removeBandedCard(attackerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
attacker.clearBandedCards();
|
||||
}
|
||||
Permanent permanent = game.getPermanent(newDefenderId);
|
||||
if (permanent != null) {
|
||||
defenderId = newDefenderId;
|
||||
defendingPlayerId = newDefenderId;
|
||||
defenderIsPlaneswalker = false;
|
||||
defendingPlayerId = permanent.getControllerId();
|
||||
defenderIsPlaneswalker = true;
|
||||
return true;
|
||||
} else {
|
||||
Player defender = game.getPlayer(newDefenderId);
|
||||
if (defender != null) {
|
||||
defenderId = newDefenderId;
|
||||
defendingPlayerId = newDefenderId;
|
||||
defenderIsPlaneswalker = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean defenderControlsDefensiveFormation(Game game) {
|
||||
// for handling Defensive Formation
|
||||
/**
|
||||
* Decides damage distribution for attacking banding creatures.
|
||||
*
|
||||
* @param game
|
||||
*/
|
||||
public boolean attackerAssignsCombatDamage(Game game) {
|
||||
for (UUID attackerId : attackers) {
|
||||
Permanent attacker = game.getPermanent(attackerId);
|
||||
if (attacker != null) {
|
||||
if (hasBanding(attacker)) { // 702.21k - only one attacker with banding necessary
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides damage distribution for blocking creatures with banding or
|
||||
* if defending player controls the Defensive Formation enchantment.
|
||||
*
|
||||
* @param game
|
||||
*/
|
||||
public boolean defenderAssignsCombatDamage(Game game) {
|
||||
for (UUID blockerId : blockers) {
|
||||
Permanent blocker = game.getPermanent(blockerId);
|
||||
if (blocker != null) {
|
||||
if (hasBanding(blocker)) { // 702.21j - only one blocker with banding necessary
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Permanent defensiveFormation : game.getBattlefield().getAllActivePermanents(defendingPlayerId)) {
|
||||
if (defensiveFormation.getAbilities().containsKey(ControllerAssignCombatDamageToBlockersAbility.getInstance().getId())) {
|
||||
return true;
|
||||
|
@ -809,7 +905,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
public boolean assignsDefendingPlayerAndOrDefendingCreaturesDividedDamage(Permanent creature, UUID playerId, boolean first, Game game, boolean isAttacking) {
|
||||
// for handling Butcher Orgg
|
||||
if (creature.getAbilities().containsKey(ControllerDivideCombatDamageAbility.getInstance().getId())) {
|
||||
Player player = game.getPlayer(defenderControlsDefensiveFormation(game) ? defendingPlayerId : playerId);
|
||||
Player player = game.getPlayer(defenderAssignsCombatDamage(game) ? defendingPlayerId : (!isAttacking && attackerAssignsCombatDamage(game) ? game.getCombat().getAttackingPlayerId() : playerId));
|
||||
// 10/4/2004 If it is blocked but then all of its blockers are removed before combat damage is assigned, then it won’t be able to deal combat damage and you won’t be able to use its ability.
|
||||
// (same principle should apply if it's blocking and its blocked attacker is removed from combat)
|
||||
if (!((blocked && blockers.isEmpty() && isAttacking) || (attackers.isEmpty() && !isAttacking)) && canDamage(creature, first)) {
|
||||
|
|
|
@ -355,6 +355,14 @@ public interface Permanent extends Card, Controllable {
|
|||
*/
|
||||
void clearPairedCard();
|
||||
|
||||
void addBandedCard(UUID bandedCard);
|
||||
|
||||
void removeBandedCard(UUID bandedCard);
|
||||
|
||||
List<UUID> getBandedCards();
|
||||
|
||||
void clearBandedCards();
|
||||
|
||||
void setMorphed(boolean value);
|
||||
|
||||
boolean isMorphed();
|
||||
|
|
|
@ -113,6 +113,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
protected UUID attachedTo;
|
||||
protected int attachedToZoneChangeCounter;
|
||||
protected MageObjectReference pairedPermanent;
|
||||
protected List<UUID> bandedCards = new ArrayList<>();
|
||||
protected Counters counters;
|
||||
protected List<MarkedDamageInfo> markedDamage;
|
||||
protected int timesLoyaltyUsed = 0;
|
||||
|
@ -177,6 +178,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
this.monstrous = permanent.monstrous;
|
||||
this.renowned = permanent.renowned;
|
||||
this.pairedPermanent = permanent.pairedPermanent;
|
||||
this.bandedCards.addAll(permanent.bandedCards);
|
||||
this.timesLoyaltyUsed = permanent.timesLoyaltyUsed;
|
||||
|
||||
this.morphed = permanent.morphed;
|
||||
|
@ -1119,32 +1121,44 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
if (tapped && !game.getState().getContinuousEffects().asThough(this.getId(), AsThoughEffectType.BLOCK_TAPPED, this.getControllerId(), game)) {
|
||||
return false;
|
||||
}
|
||||
Permanent attacker = game.getPermanent(attackerId);
|
||||
if (attacker == null) {
|
||||
Permanent baseAttacker = game.getPermanent(attackerId);
|
||||
if (baseAttacker == null) {
|
||||
return false;
|
||||
}
|
||||
// controller of attacking permanent must be an opponent
|
||||
if (!game.getPlayer(this.getControllerId()).hasOpponent(attacker.getControllerId(), game)) {
|
||||
return false;
|
||||
}
|
||||
//20101001 - 509.1b
|
||||
// check blocker restrictions
|
||||
for (Map.Entry<RestrictionEffect, Set<Ability>> entry : game.getContinuousEffects().getApplicableRestrictionEffects(this, game).entrySet()) {
|
||||
for (Ability ability : entry.getValue()) {
|
||||
if (!entry.getKey().canBlock(attacker, this, ability, game)) {
|
||||
return false;
|
||||
List<UUID> attackerIdsToCheck = new ArrayList<>(baseAttacker.getBandedCards()); // handles banding
|
||||
attackerIdsToCheck.add(attackerId);
|
||||
blockCheck:
|
||||
for (UUID bandedId : attackerIdsToCheck) {
|
||||
Permanent attacker = game.getPermanent(bandedId);
|
||||
if (attacker == null) {
|
||||
continue blockCheck;
|
||||
}
|
||||
// controller of attacking permanent must be an opponent
|
||||
if (!game.getPlayer(this.getControllerId()).hasOpponent(attacker.getControllerId(), game)) {
|
||||
continue blockCheck;
|
||||
}
|
||||
//20101001 - 509.1b
|
||||
// check blocker restrictions
|
||||
for (Map.Entry<RestrictionEffect, Set<Ability>> entry : game.getContinuousEffects().getApplicableRestrictionEffects(this, game).entrySet()) {
|
||||
for (Ability ability : entry.getValue()) {
|
||||
if (!entry.getKey().canBlock(attacker, this, ability, game)) {
|
||||
continue blockCheck;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// check also attacker's restriction effects
|
||||
for (Map.Entry<RestrictionEffect, Set<Ability>> restrictionEntry : game.getContinuousEffects().getApplicableRestrictionEffects(attacker, game).entrySet()) {
|
||||
for (Ability ability : restrictionEntry.getValue()) {
|
||||
if (!restrictionEntry.getKey().canBeBlocked(attacker, this, ability, game)) {
|
||||
return false;
|
||||
// check also attacker's restriction effects
|
||||
for (Map.Entry<RestrictionEffect, Set<Ability>> restrictionEntry : game.getContinuousEffects().getApplicableRestrictionEffects(attacker, game).entrySet()) {
|
||||
for (Ability ability : restrictionEntry.getValue()) {
|
||||
if (!restrictionEntry.getKey().canBeBlocked(attacker, this, ability, game)) {
|
||||
continue blockCheck;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!attacker.hasProtectionFrom(this, game)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return !attacker.hasProtectionFrom(this, game);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1331,6 +1345,28 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
this.pairedPermanent = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBandedCard(UUID bandedCard) {
|
||||
if (!this.bandedCards.contains(bandedCard)) {
|
||||
this.bandedCards.add(bandedCard);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeBandedCard(UUID bandedCard) {
|
||||
this.bandedCards.remove(bandedCard);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UUID> getBandedCards() {
|
||||
return bandedCards;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearBandedCards() {
|
||||
this.bandedCards.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLogName() {
|
||||
if (name.isEmpty()) {
|
||||
|
|
|
@ -832,6 +832,14 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
pairedCard.clearPairedCard();
|
||||
}
|
||||
}
|
||||
if (permanent.getBandedCards() != null && !permanent.getBandedCards().isEmpty()) {
|
||||
for (UUID bandedId : permanent.getBandedCards()) {
|
||||
Permanent banded = game.getPermanent(bandedId);
|
||||
if (banded != null) {
|
||||
banded.removeBandedCard(permanent.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue