expansionDao;
+ public boolean instanceInitialized = false;
+
ExpansionRepository() {
File file = new File("db");
if (!file.exists()) {
@@ -48,9 +50,11 @@ public enum ExpansionRepository {
TableUtils.createTableIfNotExists(connectionSource, ExpansionInfo.class);
expansionDao = DaoManager.createDao(connectionSource, ExpansionInfo.class);
+ instanceInitialized = true;
} catch (SQLException ex) {
ex.printStackTrace();
}
+
}
public void add(ExpansionInfo expansion) {
diff --git a/Mage/src/main/java/mage/constants/Rarity.java b/Mage/src/main/java/mage/constants/Rarity.java
index b8fbf3259e2..2a79d5f3689 100644
--- a/Mage/src/main/java/mage/constants/Rarity.java
+++ b/Mage/src/main/java/mage/constants/Rarity.java
@@ -1,29 +1,30 @@
package mage.constants;
/**
- *
* @author North
*/
public enum Rarity {
- LAND ("Land", "common", "C", 1),
- COMMON ("Common", "common", "C", 1),
- UNCOMMON ("Uncommon", "uncommon", "U", 2),
- RARE ("Rare", "rare", "R", 3),
- MYTHIC ("Mythic", "mythic", "M", 3),
- SPECIAL ("Special", "special", "Special", 3),
- BONUS ("Bonus", "bonus", "Bonus", 3);
+ LAND("Land", "common", "C", 1, 1),
+ COMMON("Common", "common", "C", 1, 2),
+ UNCOMMON("Uncommon", "uncommon", "U", 2, 3),
+ RARE("Rare", "rare", "R", 3, 4),
+ MYTHIC("Mythic", "mythic", "M", 3, 5),
+ SPECIAL("Special", "special", "Special", 3, 6),
+ BONUS("Bonus", "bonus", "Bonus", 3, 7);
private final String text;
private final String symbolCode;
private final String code;
private final int rating;
+ private final int sorting;
- Rarity(String text, String symbolCode, String code, int rating) {
+ Rarity(String text, String symbolCode, String code, int rating, int sorting) {
this.text = text;
this.symbolCode = symbolCode;
this.code = code;
this.rating = rating;
+ this.sorting = sorting;
}
@Override
@@ -42,4 +43,8 @@ public enum Rarity {
public int getRating() {
return rating;
}
+
+ public int getSorting() {
+ return sorting;
+ }
}
diff --git a/Mage/src/main/java/mage/constants/SubType.java b/Mage/src/main/java/mage/constants/SubType.java
index fef2c75e130..5f61eef8ec2 100644
--- a/Mage/src/main/java/mage/constants/SubType.java
+++ b/Mage/src/main/java/mage/constants/SubType.java
@@ -93,6 +93,7 @@ public enum SubType {
CHIMERA("Chimera", SubTypeSet.CreatureType),
CHISS("Chiss", SubTypeSet.CreatureType, true),
CITIZEN("Citizen", SubTypeSet.CreatureType),
+ CLAMFOLK("Clamfolk", SubTypeSet.CreatureType, true), // Unglued
CLERIC("Cleric", SubTypeSet.CreatureType),
COCKATRICE("Cockatrice", SubTypeSet.CreatureType),
CONSTRUCT("Construct", SubTypeSet.CreatureType),
@@ -131,6 +132,7 @@ public enum SubType {
ELK("Elk", SubTypeSet.CreatureType),
EYE("Eye", SubTypeSet.CreatureType),
EWOK("Ewok", SubTypeSet.CreatureType, true), // Star Wars
+ EXPANSION_SYMBOL("Expansion-Symbol", SubTypeSet.CreatureType, true), // Unhinged
// F
FAERIE("Faerie", SubTypeSet.CreatureType),
FERRET("Ferret", SubTypeSet.CreatureType),
diff --git a/Mage/src/main/java/mage/counters/CounterType.java b/Mage/src/main/java/mage/counters/CounterType.java
index 865cab23a53..a5ce8b6a603 100644
--- a/Mage/src/main/java/mage/counters/CounterType.java
+++ b/Mage/src/main/java/mage/counters/CounterType.java
@@ -9,6 +9,7 @@ public enum CounterType {
AGE("age"),
AIM("aim"),
+ ARROW("arrow"),
ARROWHEAD("arrowhead"),
AWAKENING("awakening"),
BLAZE("blaze"),
@@ -19,6 +20,7 @@ public enum CounterType {
CAGE("cage"),
CARRION("carrion"),
CHARGE("charge"),
+ CHIP("chip"),
CORPSE("corpse"),
CREDIT("credit"),
CRYSTAL("crystal"),
@@ -44,6 +46,7 @@ public enum CounterType {
FEATHER("feather"),
FILIBUSTER("filibuster"),
FLOOD("flood"),
+ FUNK("funk"),
FURY("fury"),
FUNGUS("fungus"),
FUSE("fuse"),
diff --git a/Mage/src/main/java/mage/filter/StaticFilters.java b/Mage/src/main/java/mage/filter/StaticFilters.java
index 0fcf903c4c3..4226afe8767 100644
--- a/Mage/src/main/java/mage/filter/StaticFilters.java
+++ b/Mage/src/main/java/mage/filter/StaticFilters.java
@@ -23,7 +23,7 @@ import mage.filter.predicate.permanent.TokenPredicate;
/**
* A class that holds Filter objects that may not be modified without copying
* before. This prevents the creation of thousands of filter objects.
- *
+ *
* Because the filters are used application wide they may not be modified.
* NEVER!!!!! But it's possible, so be careful!
*
@@ -42,6 +42,7 @@ public final class StaticFilters {
static {
FILTER_ENCHANTMENT_PERMANENT.setLockedFilter(true);
}
+
public static final FilterCard FILTER_CARD = new FilterCard("card");
static {
@@ -119,6 +120,7 @@ public final class StaticFilters {
static {
FILTER_CARD_NON_LAND.setLockedFilter(true);
}
+
public static final FilterNonlandCard FILTER_CARD_A_NON_LAND = new FilterNonlandCard("a nonland card");
static {
@@ -154,11 +156,13 @@ public final class StaticFilters {
static {
FILTER_PERMANENT_ARTIFACT_OR_ENCHANTMENT.setLockedFilter(true);
}
+
public static final FilterCreaturePermanent FILTER_ARTIFACT_CREATURE_PERMANENT = new FilterArtifactCreaturePermanent();
static {
FILTER_ARTIFACT_CREATURE_PERMANENT.setLockedFilter(true);
}
+
public static final FilterPermanent FILTER_PERMANENT_ARTIFACT_OR_CREATURE = new FilterPermanent("artifact or creature");
static {
@@ -168,6 +172,7 @@ public final class StaticFilters {
));
FILTER_PERMANENT_ARTIFACT_OR_CREATURE.setLockedFilter(true);
}
+
public static final FilterPermanent FILTER_PERMANENT_ARTIFACT_CREATURE_OR_ENCHANTMENT = new FilterPermanent("artifact, creature, or enchantment");
static {
@@ -178,6 +183,7 @@ public final class StaticFilters {
));
FILTER_PERMANENT_ARTIFACT_CREATURE_OR_ENCHANTMENT.setLockedFilter(true);
}
+
public static final FilterPermanent FILTER_PERMANENT_ARTIFACT_CREATURE_ENCHANTMENT_OR_LAND = new FilterPermanent("artifact, creature, enchantment, or land");
static {
@@ -195,16 +201,19 @@ public final class StaticFilters {
static {
FILTER_CONTROLLED_PERMANENT.setLockedFilter(true);
}
+
public static final FilterControlledPermanent FILTER_CONTROLLED_PERMANENT_ARTIFACT = new FilterControlledArtifactPermanent();
static {
FILTER_CONTROLLED_PERMANENT_ARTIFACT.setLockedFilter(true);
}
+
public static final FilterControlledPermanent FILTER_CONTROLLED_PERMANENT_ARTIFACT_AN = new FilterControlledArtifactPermanent("an artifact");
static {
FILTER_CONTROLLED_PERMANENT_ARTIFACT_AN.setLockedFilter(true);
}
+
public static final FilterControlledPermanent FILTER_CONTROLLED_PERMANENT_ARTIFACT_OR_CREATURE = new FilterControlledPermanent("artifact or creature you control");
static {
@@ -214,6 +223,7 @@ public final class StaticFilters {
));
FILTER_CONTROLLED_PERMANENT_ARTIFACT_OR_CREATURE.setLockedFilter(true);
}
+
public static final FilterControlledPermanent FILTER_CONTROLLED_PERMANENT_LAND = new FilterControlledLandPermanent();
static {
@@ -232,12 +242,14 @@ public final class StaticFilters {
FILTER_OPPONENTS_PERMANENT.add(new ControllerPredicate(TargetController.OPPONENT));
FILTER_OPPONENTS_PERMANENT.setLockedFilter(true);
}
+
public static final FilterCreaturePermanent FILTER_OPPONENTS_PERMANENT_CREATURE = new FilterCreaturePermanent("creature an opponent controls");
static {
FILTER_OPPONENTS_PERMANENT_CREATURE.add(new ControllerPredicate(TargetController.OPPONENT));
FILTER_OPPONENTS_PERMANENT_CREATURE.setLockedFilter(true);
}
+
public static final FilterPermanent FILTER_OPPONENTS_PERMANENT_ARTIFACT = new FilterPermanent("artifact an opponent controls");
static {
@@ -245,6 +257,7 @@ public final class StaticFilters {
FILTER_OPPONENTS_PERMANENT_ARTIFACT.add(new CardTypePredicate(CardType.ARTIFACT));
FILTER_OPPONENTS_PERMANENT_ARTIFACT.setLockedFilter(true);
}
+
public static final FilterPermanent FILTER_OPPONENTS_PERMANENT_ARTIFACT_OR_CREATURE = new FilterPermanent("artifact or creature an opponent controls");
static {
@@ -280,12 +293,14 @@ public final class StaticFilters {
static {
FILTER_CONTROLLED_A_CREATURE.setLockedFilter(true);
}
+
public static final FilterControlledCreaturePermanent FILTER_CONTROLLED_ANOTHER_CREATURE = new FilterControlledCreaturePermanent("another creature");
static {
FILTER_CONTROLLED_ANOTHER_CREATURE.add(new AnotherPredicate());
FILTER_CONTROLLED_ANOTHER_CREATURE.setLockedFilter(true);
}
+
public static final FilterControlledPermanent FILTER_CONTROLLED_PERMANENT_NON_LAND = new FilterControlledPermanent("nonland permanent");
static {
@@ -294,6 +309,7 @@ public final class StaticFilters {
);
FILTER_CONTROLLED_PERMANENT_NON_LAND.setLockedFilter(true);
}
+
public static final FilterLandPermanent FILTER_LAND = new FilterLandPermanent();
static {
@@ -331,6 +347,12 @@ public final class StaticFilters {
FILTER_PERMANENT_CREATURE.setLockedFilter(true);
}
+ public static final FilterCreaturePermanent FILTER_PERMANENT_CREATURE_A = new FilterCreaturePermanent("a creature");
+
+ static {
+ FILTER_PERMANENT_CREATURE_A.setLockedFilter(true);
+ }
+
public static final FilterPermanent FILTER_PERMANENT_CREATURE_OR_PLANESWALKER_A = new FilterPermanent("a creature or planeswalker");
static {
@@ -344,33 +366,39 @@ public final class StaticFilters {
static {
FILTER_PERMANENT_A_CREATURE.setLockedFilter(true);
}
+
public static final FilterCreaturePermanent FILTER_PERMANENT_CREATURE_CONTROLLED = new FilterCreaturePermanent("creature you control");
static {
FILTER_PERMANENT_CREATURE_CONTROLLED.add(new ControllerPredicate(TargetController.YOU));
FILTER_PERMANENT_CREATURE_CONTROLLED.setLockedFilter(true);
}
+
public static final FilterCreaturePermanent FILTER_PERMANENT_CREATURES = new FilterCreaturePermanent("creatures");
static {
FILTER_PERMANENT_CREATURES.setLockedFilter(true);
}
+
public static final FilterCreaturePermanent FILTER_PERMANENT_CREATURES_CONTROLLED = new FilterCreaturePermanent("creatures you control");
static {
FILTER_PERMANENT_CREATURES_CONTROLLED.add(new ControllerPredicate(TargetController.YOU));
FILTER_PERMANENT_CREATURES_CONTROLLED.setLockedFilter(true);
}
+
public static final FilterCreaturePermanent FILTER_PERMANENT_CREATURE_GOBLINS = new FilterCreaturePermanent(SubType.GOBLIN, "Goblin creatures");
static {
FILTER_PERMANENT_CREATURE_GOBLINS.setLockedFilter(true);
}
+
public static final FilterCreaturePermanent FILTER_PERMANENT_CREATURE_SLIVERS = new FilterCreaturePermanent(SubType.SLIVER, "all Sliver creatures");
static {
FILTER_PERMANENT_CREATURE_SLIVERS.setLockedFilter(true);
}
+
public static final FilterPlaneswalkerPermanent FILTER_PERMANENT_PLANESWALKER = new FilterPlaneswalkerPermanent();
static {
@@ -388,12 +416,14 @@ public final class StaticFilters {
static {
FILTER_PERMANENTS_NON_LAND.setLockedFilter(true);
}
+
public static final FilterStackObject FILTER_SPELL_OR_ABILITY_OPPONENTS = new FilterStackObject("spell or ability and opponent controls");
static {
FILTER_SPELL_OR_ABILITY_OPPONENTS.add(new ControllerPredicate(TargetController.OPPONENT));
FILTER_SPELL_OR_ABILITY_OPPONENTS.setLockedFilter(true);
}
+
public static final FilterStackObject FILTER_SPELL_OR_ABILITY = new FilterStackObject();
static {
@@ -405,11 +435,13 @@ public final class StaticFilters {
static {
FILTER_SPELL_A_CREATURE.setLockedFilter(true);
}
+
public static final FilterCreatureSpell FILTER_SPELL_CREATURE = new FilterCreatureSpell("creature spell");
static {
FILTER_SPELL_CREATURE.setLockedFilter(true);
}
+
public static final FilterSpell FILTER_SPELL_NON_CREATURE = (FilterSpell) new FilterSpell("noncreature spell").add(Predicates.not(new CardTypePredicate(CardType.CREATURE)));
static {
@@ -444,6 +476,7 @@ public final class StaticFilters {
));
FILTER_SPELL_AN_INSTANT_OR_SORCERY.setLockedFilter(true);
}
+
public static final FilterSpell FILTER_SPELL_INSTANT_OR_SORCERY = new FilterSpell("instant or sorcery spell");
static {
@@ -463,6 +496,7 @@ public final class StaticFilters {
));
FILTER_SPELLS_INSTANT_OR_SORCERY.setLockedFilter(true);
}
+
public static final FilterCreaturePermanent FILTER_CREATURE_TOKENS = new FilterCreaturePermanent("creature tokens");
static {
@@ -484,6 +518,7 @@ public final class StaticFilters {
FILTER_PERMANENT_AURA.add(new SubtypePredicate(SubType.AURA));
FILTER_PERMANENT_AURA.setLockedFilter(true);
}
+
public static final FilterPermanent FILTER_PERMANENT_EQUIPMENT = new FilterPermanent();
static {
@@ -491,6 +526,7 @@ public final class StaticFilters {
FILTER_PERMANENT_EQUIPMENT.add(new SubtypePredicate(SubType.EQUIPMENT));
FILTER_PERMANENT_EQUIPMENT.setLockedFilter(true);
}
+
public static final FilterPermanent FILTER_PERMANENT_FORTIFICATION = new FilterPermanent();
static {
@@ -498,6 +534,7 @@ public final class StaticFilters {
FILTER_PERMANENT_FORTIFICATION.add(new SubtypePredicate(SubType.FORTIFICATION));
FILTER_PERMANENT_FORTIFICATION.setLockedFilter(true);
}
+
public static final FilterPermanent FILTER_PERMANENT_LEGENDARY = new FilterPermanent();
static {
diff --git a/Mage/src/main/java/mage/game/Controllable.java b/Mage/src/main/java/mage/game/Controllable.java
index 025b8f3720d..ac7507e5082 100644
--- a/Mage/src/main/java/mage/game/Controllable.java
+++ b/Mage/src/main/java/mage/game/Controllable.java
@@ -12,6 +12,9 @@ public interface Controllable {
UUID getId();
default boolean isControlledBy(UUID controllerID){
+ if(getControllerId() == null){
+ return false;
+ }
return getControllerId().equals(controllerID);
}
}
diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java
index e745c75e129..c0fa488e204 100644
--- a/Mage/src/main/java/mage/game/GameImpl.java
+++ b/Mage/src/main/java/mage/game/GameImpl.java
@@ -1026,6 +1026,7 @@ public abstract class GameImpl implements Game, Serializable {
watchers.add(new BlockedAttackerWatcher());
watchers.add(new DamageDoneWatcher());
watchers.add(new PlanarRollWatcher());
+ watchers.add(new PlayersAttackedThisTurnWatcher());
//20100716 - 103.5
for (UUID playerId : state.getPlayerList(startingPlayerId)) {
@@ -1527,7 +1528,7 @@ public abstract class GameImpl implements Game, Serializable {
@Override
public void addEffect(ContinuousEffect continuousEffect, Ability source) {
Ability newAbility = source.copy();
- newAbility.setSourceObject(null, this); // Update the source object to the currently existing Object
+ newAbility.setSourceObjectZoneChangeCounter(getState().getZoneChangeCounter(source.getSourceId()));
ContinuousEffect newEffect = continuousEffect.copy();
newEffect.newId();
@@ -1698,11 +1699,17 @@ public abstract class GameImpl implements Game, Serializable {
if (ability instanceof TriggeredManaAbility || ability instanceof DelayedTriggeredManaAbility) {
// 20110715 - 605.4
Ability manaAbiltiy = ability.copy();
+ if (manaAbiltiy.getSourceObjectZoneChangeCounter() == 0) {
+ manaAbiltiy.setSourceObjectZoneChangeCounter(getState().getZoneChangeCounter(ability.getSourceId()));
+ }
manaAbiltiy.activate(this, false);
manaAbiltiy.resolve(this);
} else {
TriggeredAbility newAbility = ability.copy();
newAbility.newId();
+ if (newAbility.getSourceObjectZoneChangeCounter() == 0) {
+ newAbility.setSourceObjectZoneChangeCounter(getState().getZoneChangeCounter(ability.getSourceId()));
+ }
state.addTriggeredAbility(newAbility);
}
}
@@ -1711,10 +1718,10 @@ public abstract class GameImpl implements Game, Serializable {
public UUID addDelayedTriggeredAbility(DelayedTriggeredAbility delayedAbility, Ability source) {
delayedAbility.setSourceId(source.getSourceId());
delayedAbility.setControllerId(source.getControllerId());
- delayedAbility.setSourceObject(source.getSourceObject(this), this);
// return addDelayedTriggeredAbility(delayedAbility);
DelayedTriggeredAbility newAbility = delayedAbility.copy();
newAbility.newId();
+ newAbility.setSourceObjectZoneChangeCounter(getState().getZoneChangeCounter(source.getSourceId()));
newAbility.initOnAdding(this);
// ability.init is called as the ability triggeres not now.
// If a FixedTarget pointer is already set from the effect setting up this delayed ability
@@ -2594,7 +2601,7 @@ public abstract class GameImpl implements Game, Serializable {
boolean addPlaneAgain = false;
for (Iterator it = this.getState().getCommand().iterator(); it.hasNext();) {
CommandObject obj = it.next();
- if (obj.getControllerId().equals(playerId)) {
+ if (obj.isControlledBy(playerId)) {
if (obj instanceof Emblem) {
((Emblem) obj).discardEffects();// This may not be the best fix but it works
}
diff --git a/Mage/src/main/java/mage/game/combat/Combat.java b/Mage/src/main/java/mage/game/combat/Combat.java
index 5b56d49347c..08157890e1f 100644
--- a/Mage/src/main/java/mage/game/combat/Combat.java
+++ b/Mage/src/main/java/mage/game/combat/Combat.java
@@ -1,4 +1,3 @@
-
package mage.game.combat;
import mage.MageObject;
@@ -6,6 +5,7 @@ import mage.abilities.Ability;
import mage.abilities.effects.RequirementEffect;
import mage.abilities.effects.RestrictionEffect;
import mage.abilities.keyword.BandingAbility;
+import mage.abilities.keyword.BandsWithOtherAbility;
import mage.abilities.keyword.VigilanceAbility;
import mage.abilities.keyword.special.JohanVigilanceAbility;
import mage.constants.Outcome;
@@ -14,8 +14,12 @@ import mage.filter.StaticFilters;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.common.FilterCreatureForCombatBlock;
import mage.filter.common.FilterCreaturePermanent;
+import mage.filter.predicate.Predicate;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.AbilityPredicate;
+import mage.filter.predicate.mageobject.NamePredicate;
+import mage.filter.predicate.mageobject.SubtypePredicate;
+import mage.filter.predicate.mageobject.SupertypePredicate;
import mage.filter.predicate.permanent.AttackingSameNotBandedPredicate;
import mage.filter.predicate.permanent.PermanentIdPredicate;
import mage.game.Game;
@@ -103,8 +107,8 @@ public class Combat implements Serializable, Copyable {
}
/**
- * Get all possible defender (players and plainwalkers) That does not mean
- * neccessarly mean that they are really attacked
+ * Get all possible defender (players and planeswalkers) That does not mean
+ * necessarily mean that they are really attacked
*
* @return
*/
@@ -246,11 +250,14 @@ public class Combat implements Serializable, Copyable {
game.getCombat().checkAttackRequirements(player, game);
boolean firstTime = true;
do {
- if (!firstTime || !game.getPlayer(game.getActivePlayerId()).getAvailableAttackers(game).isEmpty()) {
+ if (!firstTime
+ || !game.getPlayer(game.getActivePlayerId()).getAvailableAttackers(game).isEmpty()) {
player.selectAttackers(game, attackingPlayerId);
}
firstTime = false;
- if (game.isPaused() || game.checkIfGameIsOver() || game.executingRollback()) {
+ if (game.isPaused()
+ || game.checkIfGameIsOver()
+ || game.executingRollback()) {
return;
}
// because of possible undo during declare attackers it's neccassary to call here the methods with "game.getCombat()." to get the current combat object!!!
@@ -293,53 +300,115 @@ public class Combat implements Serializable, Copyable {
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.Benefit, "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) {
-
- 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 (combatGroup != null && attacker.getBandedCards().isEmpty() && getAttackers().size() > 1) {
+ boolean canBand = attacker.getAbilities().containsKey(BandingAbility.getInstance().getId());
+ List bandsWithOther = new ArrayList<>();
+ for (Ability ability : attacker.getAbilities()) {
+ if (ability.getClass().equals(BandsWithOtherAbility.class)) {
+ bandsWithOther.add(ability);
}
}
- if (isBanded) {
- StringBuilder sb = new StringBuilder(player.getLogName()).append(" formed a band with ").append((attacker.getBandedCards().size() + 1) + " creatures: ");
- sb.append(attacker.getLogName());
- for (UUID id : attacker.getBandedCards()) {
- sb.append(", ");
- Permanent permanent = game.getPermanent(id);
- if (permanent != null) {
- sb.append(permanent.getLogName());
+ boolean canBandWithOther = !bandsWithOther.isEmpty();
+ if (canBand || canBandWithOther) {
+ 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
+ List> predicates = new ArrayList<>();
+ if (!canBand && canBandWithOther) {
+ for (Ability ab : bandsWithOther) {
+ BandsWithOtherAbility ability = (BandsWithOtherAbility) ab;
+ if (ability.getSubtype() != null) {
+ predicates.add(new SubtypePredicate(ability.getSubtype()));
+ }
+ if (ability.getSupertype() != null) {
+ predicates.add(new SupertypePredicate(ability.getSupertype()));
+ }
+ if (ability.getName() != null) {
+ predicates.add(new NamePredicate(ability.getName()));
+ }
+ }
+ filter.add(Predicates.or(predicates));
+ }
+ while (player.canRespond()) {
+ TargetControlledPermanent target = new TargetControlledPermanent(1, 1, filter, true);
+ target.setRequired(false);
+ canBand &= target.canChoose(attackingPlayerId, game);
+ canBandWithOther &= target.canChoose(attackingPlayerId, game);
+ if (game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARING_ATTACKERS, attackingPlayerId, attackingPlayerId))
+ || (!canBand && !canBandWithOther)
+ || !player.chooseUse(Outcome.Benefit, "Do you wish to " + (isBanded ? "band " + attacker.getLogName() + " with another " : "form a band with " + attacker.getLogName() + " and an ") + "attacking creature?", null, game)) {
+ break;
+ }
+
+ if (canBand && canBandWithOther) {
+ if (player.chooseUse(Outcome.Detriment, "Choose type of banding ability to apply:", attacker.getLogName(), "Banding", "Bands with other", null, game)) {
+ canBandWithOther = false;
+ } else {
+ canBand = false;
+ for (Ability ab : bandsWithOther) {
+ BandsWithOtherAbility ability = (BandsWithOtherAbility) ab;
+ if (ability.getSubtype() != null) {
+ predicates.add(new SubtypePredicate(ability.getSubtype()));
+ }
+ if (ability.getSupertype() != null) {
+ predicates.add(new SupertypePredicate(ability.getSupertype()));
+ }
+ if (ability.getName() != null) {
+ predicates.add(new NamePredicate(ability.getName()));
+ }
+ }
+ filter.add(Predicates.or(predicates));
+ }
+ }
+
+ if (target.choose(Outcome.Benefit, attackingPlayerId, null, game)) {
+ isBanded = true;
+ for (UUID targetId : target.getTargets()) {
+ Permanent permanent = game.getPermanent(targetId);
+ 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 (canBand) {
+ if (!permanent.getAbilities().containsKey(BandingAbility.getInstance().getId())) {
+ filter.add(new AbilityPredicate(BandingAbility.class));
+ canBandWithOther = false;
+ }
+ } else if (canBandWithOther) {
+ List> newPredicates = new ArrayList<>();
+ for (Predicate predicate : predicates) {
+ if (predicate.apply(permanent, game)) {
+ newPredicates.add(predicate);
+ }
+ }
+ filter.add(Predicates.or(newPredicates));
+ canBand = false;
+ }
+ }
+
+ }
}
}
- game.informPlayers(sb.toString());
+ if (isBanded) {
+ StringBuilder sb = new StringBuilder(player.getLogName()).append(" formed a band with ").append((attacker.getBandedCards().size() + 1) + " creatures: ");
+ sb.append(attacker.getLogName());
+ for (UUID id : attacker.getBandedCards()) {
+ sb.append(", ");
+ Permanent permanent = game.getPermanent(id);
+ if (permanent != null) {
+ sb.append(permanent.getLogName());
+ }
+ }
+ game.informPlayers(sb.toString());
+ }
}
}
}
@@ -387,11 +456,16 @@ public class Combat implements Serializable, Copyable {
if (defenders.size() == 1) {
player.declareAttacker(creature.getId(), defenders.iterator().next(), game, false);
} else {
- TargetDefender target = new TargetDefender(defenders, creature.getId());
- target.setRequired(true);
- target.setTargetName("planeswalker or player for " + creature.getLogName() + " to attack");
- if (player.chooseTarget(Outcome.Damage, target, null, game)) {
- player.declareAttacker(creature.getId(), target.getFirstTarget(), game, false);
+ if (!player.isHuman()) { // computer only for multiple defenders
+ player.declareAttacker(creature.getId(), defenders.iterator().next(), game, false);
+ } else { // human players only for multiple defenders
+ TargetDefender target = new TargetDefender(defenders, creature.getId());
+ target.setRequired(true);
+ target.setTargetName("planeswalker or player for " + creature.getLogName() + " to attack");
+ if (player.chooseTarget(Outcome.Damage, target, null, game)) {
+ System.out.println("The player " + player.getName() + " declares an attacker here. " + creature.getName());
+ player.declareAttacker(creature.getId(), target.getFirstTarget(), game, false);
+ }
}
}
} else {
@@ -483,7 +557,7 @@ public class Combat implements Serializable, Copyable {
* Handle the blocker selection process
*
* @param blockController player that controlls how to block, if null the
- * defender is the controller
+ * defender is the controller
* @param game
*/
public void selectBlockers(Player blockController, Game game) {
@@ -1275,10 +1349,10 @@ public class Combat implements Serializable, Copyable {
if (defenderAttackedBy.size() >= defendingPlayer.getMaxAttackedBy()) {
Player attackingPlayer = game.getPlayer(game.getControllerId(attackerId));
if (attackingPlayer != null && !game.isSimulation()) {
- game.informPlayer(attackingPlayer, "No more than " +
- CardUtil.numberToText(defendingPlayer.getMaxAttackedBy()) +
- " creatures can attack " +
- defendingPlayer.getLogName());
+ game.informPlayer(attackingPlayer, "No more than "
+ + CardUtil.numberToText(defendingPlayer.getMaxAttackedBy())
+ + " creatures can attack "
+ + defendingPlayer.getLogName());
}
return false;
}
@@ -1308,7 +1382,7 @@ public class Combat implements Serializable, Copyable {
* @param playerId
* @param game
* @param solveBanding check whether also add creatures banded with
- * attackerId
+ * attackerId
*/
public void addBlockingGroup(UUID blockerId, UUID attackerId, UUID playerId, Game game, boolean solveBanding) {
Permanent blocker = game.getPermanent(blockerId);
diff --git a/Mage/src/main/java/mage/game/combat/CombatGroup.java b/Mage/src/main/java/mage/game/combat/CombatGroup.java
index e357b6df977..be2f139af66 100644
--- a/Mage/src/main/java/mage/game/combat/CombatGroup.java
+++ b/Mage/src/main/java/mage/game/combat/CombatGroup.java
@@ -5,10 +5,12 @@ import java.io.Serializable;
import java.util.*;
import java.util.stream.Stream;
+import mage.abilities.Ability;
import mage.abilities.common.ControllerAssignCombatDamageToBlockersAbility;
import mage.abilities.common.ControllerDivideCombatDamageAbility;
import mage.abilities.common.DamageAsThoughNotBlockedAbility;
import mage.abilities.keyword.BandingAbility;
+import mage.abilities.keyword.BandsWithOtherAbility;
import mage.abilities.keyword.CantBlockAloneAbility;
import mage.abilities.keyword.DeathtouchAbility;
import mage.abilities.keyword.DoubleStrikeAbility;
@@ -110,6 +112,56 @@ public class CombatGroup implements Serializable, Copyable