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/counters/CounterType.java b/Mage/src/main/java/mage/counters/CounterType.java
index fd2eb779126..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"),
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 {
return perm.getAbilities().containsKey(BandingAbility.getInstance().getId());
}
+ private boolean appliesBandsWithOther(List creatureIds, Game game) {
+ for (UUID creatureId : creatureIds) {
+ Permanent perm = game.getPermanent(creatureId);
+ if (perm != null && perm.getBandedCards() != null) {
+ for (Ability ab : perm.getAbilities()) {
+ if (ab.getClass().equals(BandsWithOtherAbility.class)) {
+ BandsWithOtherAbility ability = (BandsWithOtherAbility) ab;
+ if (ability.getSubtype() != null) {
+ if (perm.hasSubtype(ability.getSubtype(), game)) {
+ for (UUID bandedId : creatureIds) {
+ if (!bandedId.equals(creatureId)) {
+ Permanent banded = game.getPermanent(bandedId);
+ if (banded != null && banded.hasSubtype(ability.getSubtype(), game)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ if (ability.getSupertype() != null) {
+ if (perm.getSuperType().contains(ability.getSupertype())) {
+ for (UUID bandedId : creatureIds) {
+ if (!bandedId.equals(creatureId)) {
+ Permanent banded = game.getPermanent(bandedId);
+ if (banded != null && banded.getSuperType().contains(ability.getSupertype())) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ if (ability.getName() != null) {
+ if (perm.getName().equals(ability.getName())) {
+ for (UUID bandedId : creatureIds) {
+ if (!bandedId.equals(creatureId)) {
+ Permanent banded = game.getPermanent(bandedId);
+ if (banded != null && banded.getName().equals(ability.getName())) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
public void assignDamageToBlockers(boolean first, Game game) {
if (!attackers.isEmpty() && (!first || hasFirstOrDoubleStrike(game))) {
Permanent attacker = game.getPermanent(attackers.get(0));
@@ -837,6 +889,9 @@ public class CombatGroup implements Serializable, Copyable {
}
}
}
+ if (appliesBandsWithOther(attackers, game)) { // 702.21k - both a [quality] creature with “bands with other [quality]” and another [quality] creature (...)
+ return true;
+ }
return false;
}
@@ -855,6 +910,9 @@ public class CombatGroup implements Serializable, Copyable {
}
}
}
+ if (appliesBandsWithOther(blockers, game)) { // 702.21j - both a [quality] creature with “bands with other [quality]” and another [quality] creature (...)
+ return true;
+ }
for (Permanent defensiveFormation : game.getBattlefield().getAllActivePermanents(defendingPlayerId)) {
if (defensiveFormation.getAbilities().containsKey(ControllerAssignCombatDamageToBlockersAbility.getInstance().getId())) {
return true;
diff --git a/Mage/src/main/java/mage/game/command/CommandObject.java b/Mage/src/main/java/mage/game/command/CommandObject.java
index b537d823ea6..2a376c6b3f0 100644
--- a/Mage/src/main/java/mage/game/command/CommandObject.java
+++ b/Mage/src/main/java/mage/game/command/CommandObject.java
@@ -3,17 +3,16 @@ package mage.game.command;
import java.util.UUID;
import mage.MageObject;
+import mage.game.Controllable;
/**
*
* @author Viserion, nantuko
*/
-public interface CommandObject extends MageObject {
+public interface CommandObject extends MageObject, Controllable {
UUID getSourceId();
- UUID getControllerId();
-
void assignNewId();
MageObject getSourceObject();
diff --git a/Mage/src/main/java/mage/game/command/Emblem.java b/Mage/src/main/java/mage/game/command/Emblem.java
index 898a5aeb562..d95cfb659d6 100644
--- a/Mage/src/main/java/mage/game/command/Emblem.java
+++ b/Mage/src/main/java/mage/game/command/Emblem.java
@@ -1,4 +1,3 @@
-
package mage.game.command;
import java.util.EnumSet;
diff --git a/Mage/src/main/java/mage/game/command/Plane.java b/Mage/src/main/java/mage/game/command/Plane.java
index b6a3768c298..ae34edade03 100644
--- a/Mage/src/main/java/mage/game/command/Plane.java
+++ b/Mage/src/main/java/mage/game/command/Plane.java
@@ -1,7 +1,5 @@
-
package mage.game.command;
-import static java.lang.Math.log;
import java.lang.reflect.Constructor;
import java.util.EnumSet;
import java.util.List;
@@ -289,7 +287,7 @@ public class Plane implements CommandObject {
if (plane instanceof Plane) {
return (Plane) plane;
}
- } catch (Exception ex) {
+ } catch (Exception ex) {
}
return null;
}
diff --git a/Mage/src/main/java/mage/game/command/emblems/VraskaGolgariQueenEmblem.java b/Mage/src/main/java/mage/game/command/emblems/VraskaGolgariQueenEmblem.java
index f7c2fac333c..018943ecaba 100644
--- a/Mage/src/main/java/mage/game/command/emblems/VraskaGolgariQueenEmblem.java
+++ b/Mage/src/main/java/mage/game/command/emblems/VraskaGolgariQueenEmblem.java
@@ -3,11 +3,11 @@ package mage.game.command.emblems;
import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility;
import mage.abilities.effects.common.LoseGameTargetPlayerEffect;
import mage.constants.SetTargetPointer;
+import mage.constants.Zone;
import mage.filter.StaticFilters;
import mage.game.command.Emblem;
/**
- *
* @author TheElk801
*/
public final class VraskaGolgariQueenEmblem extends Emblem {
@@ -17,9 +17,9 @@ public final class VraskaGolgariQueenEmblem extends Emblem {
this.setName("Emblem Vraska");
this.setExpansionSetCodeForImage("GRN");
this.getAbilities().add(new DealsDamageToAPlayerAllTriggeredAbility(
- new LoseGameTargetPlayerEffect(),
+ Zone.COMMAND, new LoseGameTargetPlayerEffect(),
StaticFilters.FILTER_CONTROLLED_A_CREATURE,
- false, SetTargetPointer.PLAYER, true
+ false, SetTargetPointer.NONE, true, true
));
}
}
diff --git a/Mage/src/main/java/mage/game/draft/DraftImpl.java b/Mage/src/main/java/mage/game/draft/DraftImpl.java
index 35efc144316..5ef5a2330ea 100644
--- a/Mage/src/main/java/mage/game/draft/DraftImpl.java
+++ b/Mage/src/main/java/mage/game/draft/DraftImpl.java
@@ -147,7 +147,8 @@ public abstract class DraftImpl implements Draft {
@Override
public void autoPick(UUID playerId) {
- this.addPick(playerId, players.get(playerId).getBooster().get(0).getId(), null);
+ List booster = players.get(playerId).getBooster();
+ this.addPick(playerId, booster.get(booster.size()-1).getId(), null);
}
protected void passLeft() {
diff --git a/Mage/src/main/java/mage/game/match/MatchOptions.java b/Mage/src/main/java/mage/game/match/MatchOptions.java
index 02e6808416c..47ad873e631 100644
--- a/Mage/src/main/java/mage/game/match/MatchOptions.java
+++ b/Mage/src/main/java/mage/game/match/MatchOptions.java
@@ -37,6 +37,7 @@ public class MatchOptions implements Serializable {
protected boolean spectatorsAllowed;
protected boolean planeChase;
protected int quitRatio;
+ protected int minimumRating;
protected int edhPowerLevel;
protected boolean rated;
protected int numSeatsForMatch;
@@ -205,6 +206,10 @@ public class MatchOptions implements Serializable {
this.quitRatio = quitRatio;
}
+ public int getMinimumRating() { return minimumRating; }
+
+ public void setMinimumRating(int minimumRating) { this.minimumRating = minimumRating; }
+
public int getEdhPowerLevel() {
return edhPowerLevel;
}
diff --git a/Mage/src/main/java/mage/game/permanent/token/GoblinToken.java b/Mage/src/main/java/mage/game/permanent/token/GoblinToken.java
index e59b7f70df4..79a3a2236db 100644
--- a/Mage/src/main/java/mage/game/permanent/token/GoblinToken.java
+++ b/Mage/src/main/java/mage/game/permanent/token/GoblinToken.java
@@ -20,7 +20,7 @@ public final class GoblinToken extends TokenImpl {
static {
tokenImageSets.addAll(Arrays.asList("10E", "ALA", "SOM", "M10", "NPH", "M13", "RTR",
"MMA", "M15", "C14", "KTK", "EVG", "DTK", "ORI", "DDG", "DDN", "DD3EVG", "MM2",
- "MM3", "EMA", "C16", "DOM"));
+ "MM3", "EMA", "C16", "DOM", "ANA"));
}
public GoblinToken(boolean withHaste) {
diff --git a/Mage/src/main/java/mage/game/permanent/token/MaskToken.java b/Mage/src/main/java/mage/game/permanent/token/MaskToken.java
index 662ee9e0603..fd1619f9955 100644
--- a/Mage/src/main/java/mage/game/permanent/token/MaskToken.java
+++ b/Mage/src/main/java/mage/game/permanent/token/MaskToken.java
@@ -10,7 +10,6 @@ import mage.constants.SubType;
import mage.target.TargetPermanent;
/**
- *
* @author TheElk801
*/
public final class MaskToken extends TokenImpl {
@@ -18,8 +17,8 @@ public final class MaskToken extends TokenImpl {
public MaskToken() {
super(
"Mask", "white Aura enchantment token named Mask "
- + "attached to another target permanent. "
- + "The token has enchant permanent and totem armor."
+ + "attached to another target permanent. "
+ + "The token has enchant permanent and totem armor."
);
cardType.add(CardType.ENCHANTMENT);
color.setWhite(true);
@@ -31,6 +30,7 @@ public final class MaskToken extends TokenImpl {
ability.addEffect(new AttachEffect(Outcome.BoostCreature));
this.addAbility(ability);
+ // Totem armor
this.addAbility(new TotemArmorAbility());
}
diff --git a/Mage/src/main/java/mage/game/permanent/token/SpiritWhiteToken.java b/Mage/src/main/java/mage/game/permanent/token/SpiritWhiteToken.java
index 5b856ddedad..64668eb42a2 100644
--- a/Mage/src/main/java/mage/game/permanent/token/SpiritWhiteToken.java
+++ b/Mage/src/main/java/mage/game/permanent/token/SpiritWhiteToken.java
@@ -17,7 +17,7 @@ public final class SpiritWhiteToken extends TokenImpl {
final static private List tokenImageSets = new ArrayList<>();
static {
- tokenImageSets.addAll(Arrays.asList("AVR", "C14", "CNS", "DDC", "DDK", "FRF", "ISD", "KTK", "M15", "MM2", "SHM", "SOI", "EMA", "C16", "MM3", "CMA", "E01"));
+ tokenImageSets.addAll(Arrays.asList("AVR", "C14", "CNS", "DDC", "DDK", "FRF", "ISD", "KTK", "M15", "MM2", "SHM", "SOI", "EMA", "C16", "MM3", "CMA", "E01", "ANA"));
}
public SpiritWhiteToken() {
diff --git a/Mage/src/main/java/mage/game/permanent/token/WolvesOfTheHuntToken.java b/Mage/src/main/java/mage/game/permanent/token/WolvesOfTheHuntToken.java
new file mode 100644
index 00000000000..af156cd597b
--- /dev/null
+++ b/Mage/src/main/java/mage/game/permanent/token/WolvesOfTheHuntToken.java
@@ -0,0 +1,32 @@
+
+package mage.game.permanent.token;
+
+import mage.constants.CardType;
+import mage.constants.SubType;
+import mage.MageInt;
+import mage.abilities.keyword.BandsWithOtherAbility;
+
+/**
+ *
+ * @author L_J
+ */
+public final class WolvesOfTheHuntToken extends TokenImpl {
+
+ public WolvesOfTheHuntToken() {
+ super("Wolves of the Hunt", "1/1 green Wolf creature token named Wolves of the Hunt");
+ cardType.add(CardType.CREATURE);
+ subtype.add(SubType.WOLF);
+ color.setGreen(true);
+ power = new MageInt(1);
+ toughness = new MageInt(1);
+ this.addAbility(new BandsWithOtherAbility("Wolves of the Hunt"));
+ }
+
+ public WolvesOfTheHuntToken(final WolvesOfTheHuntToken token) {
+ super(token);
+ }
+
+ public WolvesOfTheHuntToken copy() {
+ return new WolvesOfTheHuntToken(this);
+ }
+}
diff --git a/Mage/src/main/java/mage/game/stack/StackAbility.java b/Mage/src/main/java/mage/game/stack/StackAbility.java
index 4c7259ed7d8..02546c99c23 100644
--- a/Mage/src/main/java/mage/game/stack/StackAbility.java
+++ b/Mage/src/main/java/mage/game/stack/StackAbility.java
@@ -518,14 +518,19 @@ public class StackAbility extends StackObjImpl implements Ability {
return this.ability.getSourcePermanentIfItStillExists(game);
}
+ @Override
+ public void setSourceObjectZoneChangeCounter(int zoneChangeCounter) {
+ ability.setSourceObjectZoneChangeCounter(zoneChangeCounter);
+ }
+
@Override
public int getSourceObjectZoneChangeCounter() {
return ability.getSourceObjectZoneChangeCounter();
}
@Override
- public void setSourceObject(MageObject sourceObject, Game game) {
- throw new UnsupportedOperationException("Not supported.");
+ public Permanent getSourcePermanentOrLKI(Game game) {
+ return ability.getSourcePermanentOrLKI(game);
}
@Override
diff --git a/Mage/src/main/java/mage/game/tournament/TournamentOptions.java b/Mage/src/main/java/mage/game/tournament/TournamentOptions.java
index 2b042d19a1d..c81294ce3ee 100644
--- a/Mage/src/main/java/mage/game/tournament/TournamentOptions.java
+++ b/Mage/src/main/java/mage/game/tournament/TournamentOptions.java
@@ -24,6 +24,7 @@ public class TournamentOptions implements Serializable {
protected int numberRounds;
protected String password;
protected int quitRatio;
+ protected int minimumRating;
public TournamentOptions(String name, String matchType, int numSeats) {
this.name = name;
@@ -98,4 +99,8 @@ public class TournamentOptions implements Serializable {
public void setQuitRatio(int quitRatio) {
this.quitRatio = quitRatio;
}
+
+ public int getMinimumRating() { return minimumRating; }
+
+ public void setMinimumRating(int minimumRating) { this.minimumRating = minimumRating; }
}
diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java
index ec86ad550c1..dab98647ec4 100644
--- a/Mage/src/main/java/mage/players/Player.java
+++ b/Mage/src/main/java/mage/players/Player.java
@@ -1,5 +1,7 @@
package mage.players;
+import java.io.Serializable;
+import java.util.*;
import mage.MageItem;
import mage.MageObject;
import mage.MageObjectReference;
@@ -37,9 +39,6 @@ import mage.target.TargetCard;
import mage.target.common.TargetCardInLibrary;
import mage.util.Copyable;
-import java.io.Serializable;
-import java.util.*;
-
/**
* @author BetaSteward_at_googlemail.com
*/
@@ -74,7 +73,7 @@ public interface Player extends MageItem, Copyable {
void setLife(int life, Game game, UUID sourceId);
/**
- * @param amount amount of life loss
+ * @param amount amount of life loss
* @param game
* @param atCombat was the source combat damage
* @return
@@ -345,7 +344,7 @@ public interface Player extends MageItem, Copyable {
* @param target
* @param game
* @param targetPlayerId player whose library will be searched
- * @param triggerEvents whether searching will trigger any game events
+ * @param triggerEvents whether searching will trigger any game events
* @return true if search was successful
*/
boolean searchLibrary(TargetCardInLibrary target, Game game, UUID targetPlayerId, boolean triggerEvents);
@@ -364,22 +363,23 @@ public interface Player extends MageItem, Copyable {
/**
* Plays a card if possible
*
- * @param card the card that can be cast
+ * @param card the card that can be cast
* @param game
- * @param noMana if it's a spell i can be cast without paying mana
+ * @param noMana if it's a spell i can be cast without paying mana
* @param ignoreTiming if it's cast during the resolution of another spell
- * no sorcery or play land timing restriction are checked. For a land it has
- * to be the turn of the player playing that card.
+ * no sorcery or play land timing restriction are checked. For a land it has
+ * to be the turn of the player playing that card.
+ * @param reference mage object that allows to play the card
* @return
*/
boolean playCard(Card card, Game game, boolean noMana, boolean ignoreTiming, MageObjectReference reference);
/**
- * @param card the land card to play
+ * @param card the land card to play
* @param game
* @param ignoreTiming false - it won't be checked if the stack is empty and
- * you are able to play a Sorcery. It's still checked, if you are able to
- * play a land concerning the numner of lands you already played.
+ * you are able to play a Sorcery. It's still checked, if you are able to
+ * play a land concerning the number of lands you already played.
* @return
*/
boolean playLand(Card card, Game game, boolean ignoreTiming);
@@ -525,11 +525,11 @@ public interface Player extends MageItem, Copyable {
/**
* Moves the cards from cards to the bottom of the players library.
*
- * @param cards - list of cards that have to be moved
- * @param game - game
+ * @param cards - list of cards that have to be moved
+ * @param game - game
* @param anyOrder - true if player can determine the order of the cards
- * else random order
- * @param source - source ability
+ * else random order
+ * @param source - source ability
* @return
*/
boolean putCardsOnBottomOfLibrary(Cards cards, Game game, Ability source, boolean anyOrder);
@@ -550,10 +550,10 @@ public interface Player extends MageItem, Copyable {
/**
* Moves the cards from cards to the top of players library.
*
- * @param cards - list of cards that have to be moved
- * @param game - game
+ * @param cards - list of cards that have to be moved
+ * @param game - game
* @param anyOrder - true if player can determine the order of the cards
- * @param source - source ability
+ * @param source - source ability
* @return
*/
boolean putCardsOnTopOfLibrary(Cards cards, Game game, Ability source, boolean anyOrder);
@@ -579,8 +579,8 @@ public interface Player extends MageItem, Copyable {
/**
* Choose the order in which blockers get damage assigned to
*
- * @param blockers list of blockers where to choose the next one from
- * @param combatGroup the concerning combat group
+ * @param blockers list of blockers where to choose the next one from
+ * @param combatGroup the concerning combat group
* @param blockerOrder the already set order of blockers
* @param game
* @return blocker next to add to the blocker order
@@ -719,11 +719,11 @@ public interface Player extends MageItem, Copyable {
* @param toZone
* @param source
* @param game
- * @param tapped the cards are tapped on the battlefield
- * @param faceDown the cards are face down in the to zone
- * @param byOwner the card is moved (or put onto battlefield) by the owner
- * of the card and if target zone is battlefield controls the permanent
- * (instead of the controller of the source)
+ * @param tapped the cards are tapped on the battlefield
+ * @param faceDown the cards are face down in the to zone
+ * @param byOwner the card is moved (or put onto battlefield) by the owner
+ * of the card and if target zone is battlefield controls the permanent
+ * (instead of the controller of the source)
* @param appliedEffects
* @return
*/
@@ -759,7 +759,7 @@ public interface Player extends MageItem, Copyable {
* list of applied effects is not saved
*
* @param card
- * @param exileId exile zone id (optional)
+ * @param exileId exile zone id (optional)
* @param exileName name of exile zone (optional)
* @param sourceId
* @param game
@@ -801,7 +801,7 @@ public interface Player extends MageItem, Copyable {
* @param sourceId
* @param game
* @param fromZone if null, this info isn't postet
- * @param toTop to the top of the library else to the bottom
+ * @param toTop to the top of the library else to the bottom
* @param withName show the card name in the log
* @return
*/
@@ -826,10 +826,10 @@ public interface Player extends MageItem, Copyable {
* without mana (null) or the mana set to manaCosts instead of its normal
* mana costs.
*
- * @param sourceId the source that can be cast without mana
+ * @param sourceId the source that can be cast without mana
* @param manaCosts alternate ManaCost, null if it can be cast without mana
- * cost
- * @param costs alternate other costs you need to pay
+ * cost
+ * @param costs alternate other costs you need to pay
*/
void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts manaCosts, Costs costs);
diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java
index 53053fb0466..9e93659dbf1 100644
--- a/Mage/src/main/java/mage/players/PlayerImpl.java
+++ b/Mage/src/main/java/mage/players/PlayerImpl.java
@@ -1041,15 +1041,15 @@ public abstract class PlayerImpl implements Player, Serializable {
}
@Override
- public boolean cast(SpellAbility ability, Game game, boolean noMana, MageObjectReference permittingObject) {
- if (game == null || ability == null) {
+ public boolean cast(SpellAbility originalAbility, Game game, boolean noMana, MageObjectReference permittingObject) {
+ if (game == null || originalAbility == null) {
return false;
}
// Use ability copy to avoid problems with targets and costs on recast (issue https://github.com/magefree/mage/issues/5189).
- ability = ability.copy();
-
+ SpellAbility ability = originalAbility.copy();
ability.setControllerId(getId());
+ ability.setSourceObjectZoneChangeCounter(game.getState().getZoneChangeCounter(ability.getSourceId()));
if (ability.getSpellAbilityType() != SpellAbilityType.BASE) {
ability = chooseSpellAbilityForCast(ability, game, noMana);
if (ability == null) {
@@ -1073,6 +1073,8 @@ public abstract class PlayerImpl implements Player, Serializable {
logger.error("Got no spell from stack. ability: " + ability.getRule());
return false;
}
+ // Update the zcc to the stack
+ ability.setSourceObjectZoneChangeCounter(game.getState().getZoneChangeCounter(ability.getSourceId()));
// some effects set sourceId to cast without paying mana costs or other costs
if (ability.getSourceId().equals(getCastSourceIdWithAlternateMana())) {
Ability spellAbility = spell.getSpellAbility();
@@ -1143,8 +1145,14 @@ public abstract class PlayerImpl implements Player, Serializable {
}
//20091005 - 114.2a
ActivationStatus activationStatus = playLandAbility.canActivate(this.playerId, game);
- if (!ignoreTiming && !activationStatus.canActivate()) {
- return false;
+ if (ignoreTiming) {
+ if (!canPlayLand()) {
+ return false; // ignore timing does not mean that more lands than normal can be played
+ }
+ } else {
+ if (!activationStatus.canActivate()) {
+ return false;
+ }
}
//20091005 - 305.1
@@ -2364,7 +2372,9 @@ public abstract class PlayerImpl implements Player, Serializable {
setStoredBookmark(game.bookmarkState()); // makes it possible to UNDO a declared attacker with costs from e.g. Propaganda
}
Permanent attacker = game.getPermanent(attackerId);
- if (attacker != null && attacker.canAttack(defenderId, game) && attacker.isControlledBy(playerId)) {
+ if (attacker != null
+ && attacker.canAttack(defenderId, game)
+ && attacker.isControlledBy(playerId)) {
if (!game.getCombat().declareAttacker(attackerId, defenderId, playerId, game)) {
game.undo(playerId);
}
@@ -2463,13 +2473,14 @@ public abstract class PlayerImpl implements Player, Serializable {
for (UUID targetId : newTarget.getTargets()) {
target.add(targetId, game);
}
- if (triggerEvents) {
- game.fireEvent(GameEvent.getEvent(GameEvent.EventType.LIBRARY_SEARCHED, targetPlayerId, playerId));
- }
+
} else if (targetPlayerId.equals(playerId) && handleLibraryCastableCards(library, game, targetPlayerId)) { // for handling Panglacial Wurm
newTarget.clearChosen();
continue;
}
+ if (triggerEvents) {
+ game.fireEvent(GameEvent.getEvent(GameEvent.EventType.LIBRARY_SEARCHED, targetPlayerId, playerId));
+ }
break;
} while (true);
return true;
diff --git a/Mage/src/main/java/mage/target/common/TargetDefender.java b/Mage/src/main/java/mage/target/common/TargetDefender.java
index 3d48481d59a..863c419b28a 100644
--- a/Mage/src/main/java/mage/target/common/TargetDefender.java
+++ b/Mage/src/main/java/mage/target/common/TargetDefender.java
@@ -68,7 +68,8 @@ public class TargetDefender extends TargetImpl {
}
}
for (Permanent permanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_PLANESWALKER, sourceControllerId, game)) {
- if ((notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, game))
+ if ((notTarget
+ || permanent.canBeTargetedBy(targetSource, sourceControllerId, game))
&& filter.match(permanent, game)) {
count++;
if (count >= this.minNumberOfTargets) {
@@ -84,7 +85,8 @@ public class TargetDefender extends TargetImpl {
int count = 0;
for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) {
Player player = game.getPlayer(playerId);
- if (player != null && filter.match(player, game)) {
+ if (player != null
+ && filter.match(player, game)) {
count++;
if (count >= this.minNumberOfTargets) {
return true;
@@ -109,13 +111,15 @@ public class TargetDefender extends TargetImpl {
for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) {
Player player = game.getPlayer(playerId);
if (player != null
- && (notTarget || player.canBeTargetedBy(targetSource, sourceControllerId, game))
+ && (notTarget
+ || player.canBeTargetedBy(targetSource, sourceControllerId, game))
&& filter.match(player, game)) {
possibleTargets.add(playerId);
}
}
for (Permanent permanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_PLANESWALKER, sourceControllerId, game)) {
- if ((notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, game))
+ if ((notTarget
+ || permanent.canBeTargetedBy(targetSource, sourceControllerId, game))
&& filter.match(permanent, game)) {
possibleTargets.add(permanent.getId());
}
@@ -128,7 +132,8 @@ public class TargetDefender extends TargetImpl {
Set possibleTargets = new HashSet<>();
for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) {
Player player = game.getPlayer(playerId);
- if (player != null && filter.match(player, game)) {
+ if (player != null
+ && filter.match(player, game)) {
possibleTargets.add(playerId);
}
}
@@ -162,7 +167,8 @@ public class TargetDefender extends TargetImpl {
return filter.match(player, game);
}
Permanent permanent = game.getPermanent(id);
- return permanent != null && filter.match(permanent, game);
+ return permanent != null
+ && filter.match(permanent, game);
}
@Override
@@ -170,7 +176,8 @@ public class TargetDefender extends TargetImpl {
Player player = game.getPlayer(id);
MageObject targetSource = game.getObject(attackerId);
if (player != null) {
- return (notTarget || player.canBeTargetedBy(targetSource, (source == null ? null : source.getControllerId()), game))
+ return (notTarget
+ || player.canBeTargetedBy(targetSource, (source == null ? null : source.getControllerId()), game))
&& filter.match(player, game);
}
Permanent permanent = game.getPermanent(id); // planeswalker
@@ -180,7 +187,8 @@ public class TargetDefender extends TargetImpl {
if (source != null) {
controllerId = source.getControllerId();
}
- return (notTarget || permanent.canBeTargetedBy(targetSource, controllerId, game))
+ return (notTarget
+ || permanent.canBeTargetedBy(targetSource, controllerId, game))
&& filter.match(permanent, game);
}
return false;
diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java
index ac9a02f558f..53da40ceef7 100644
--- a/Mage/src/main/java/mage/util/CardUtil.java
+++ b/Mage/src/main/java/mage/util/CardUtil.java
@@ -362,7 +362,7 @@ public final class CardUtil {
/**
* Parse card number as int (support base [123] and alternative numbers
- * [123b]).
+ * [123b], [U123]).
*
* @param cardNumber origin card number
* @return int
@@ -373,10 +373,15 @@ public final class CardUtil {
throw new IllegalArgumentException("Card number is empty.");
}
- if (Character.isDigit(cardNumber.charAt(cardNumber.length() - 1))) {
- return Integer.parseInt(cardNumber);
- } else {
+ if (!Character.isDigit(cardNumber.charAt(0))) {
+ // U123
+ return Integer.parseInt(cardNumber.substring(1, cardNumber.length()));
+ } else if (!Character.isDigit(cardNumber.charAt(cardNumber.length() - 1))) {
+ // 123b
return Integer.parseInt(cardNumber.substring(0, cardNumber.length() - 1));
+ } else {
+ // 123
+ return Integer.parseInt(cardNumber);
}
}
diff --git a/Mage/src/main/java/mage/util/ClassScanner.java b/Mage/src/main/java/mage/util/ClassScanner.java
index 46609ea4baf..47972fe400b 100644
--- a/Mage/src/main/java/mage/util/ClassScanner.java
+++ b/Mage/src/main/java/mage/util/ClassScanner.java
@@ -33,8 +33,8 @@ public final class ClassScanner {
if(classLoader == null) classLoader = Thread.currentThread().getContextClassLoader();
assert classLoader != null;
- HashMap dirs = new HashMap<>();
- TreeSet jars = new TreeSet<>();
+ Map dirs = new HashMap<>();
+ Set jars = new TreeSet<>();
for (String packageName : packages) {
String path = packageName.replace('.', '/');
Enumeration resources = classLoader.getResources(path);
@@ -51,8 +51,8 @@ public final class ClassScanner {
}
}
- for (String filePath : dirs.keySet()) {
- cards.addAll(findClasses(classLoader, new File(filePath), dirs.get(filePath), type));
+ for (Map.Entry dir : dirs.entrySet()) {
+ cards.addAll(findClasses(classLoader, new File(dir.getKey()), dir.getValue(), type));
}
for (String filePath : jars) {
@@ -66,7 +66,7 @@ public final class ClassScanner {
private static List findClasses(ClassLoader classLoader, File directory, String packageName, Class> type) {
List cards = new ArrayList<>();
- if (!directory.exists()) return cards;
+ if (directory == null || !directory.exists()) return cards;
for (File file : directory.listFiles()) {
if (file.getName().endsWith(".class")) {
diff --git a/Mage/src/main/java/mage/watchers/common/PlayersAttackedThisTurnWatcher.java b/Mage/src/main/java/mage/watchers/common/PlayersAttackedThisTurnWatcher.java
new file mode 100644
index 00000000000..0099fb12672
--- /dev/null
+++ b/Mage/src/main/java/mage/watchers/common/PlayersAttackedThisTurnWatcher.java
@@ -0,0 +1,91 @@
+package mage.watchers.common;
+
+import mage.constants.WatcherScope;
+import mage.game.Game;
+import mage.game.events.GameEvent;
+import mage.players.PlayerList;
+import mage.watchers.Watcher;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * @author JayDi85
+ */
+public class PlayersAttackedThisTurnWatcher extends Watcher {
+
+ // how many players or opponents each player attacked this turn
+ private final Map playersAttackedThisTurn = new HashMap<>();
+ private final Map opponentsAttackedThisTurn = new HashMap<>();
+
+ public PlayersAttackedThisTurnWatcher() {
+ super(PlayersAttackedThisTurnWatcher.class.getSimpleName(), WatcherScope.GAME);
+ }
+
+ public PlayersAttackedThisTurnWatcher(final PlayersAttackedThisTurnWatcher watcher) {
+ super(watcher);
+
+ for (Map.Entry entry : watcher.playersAttackedThisTurn.entrySet()) {
+ this.playersAttackedThisTurn.put(entry.getKey(), entry.getValue());
+ }
+
+ for (Map.Entry entry : watcher.opponentsAttackedThisTurn.entrySet()) {
+ this.opponentsAttackedThisTurn.put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ @Override
+ public PlayersAttackedThisTurnWatcher copy() {
+ return new PlayersAttackedThisTurnWatcher(this);
+ }
+
+ @Override
+ public void watch(GameEvent event, Game game) {
+ if (event.getType() == GameEvent.EventType.BEGINNING_PHASE_PRE) {
+ playersAttackedThisTurn.clear();
+ opponentsAttackedThisTurn.clear();
+ }
+
+ if (event.getType() == GameEvent.EventType.ATTACKER_DECLARED) {
+
+ // players
+ PlayerList playersAttacked = playersAttackedThisTurn.get(event.getPlayerId());
+ if (playersAttacked == null) {
+ playersAttacked = new PlayerList();
+ }
+ UUID playerDefender = game.getCombat().getDefendingPlayerId(event.getSourceId(), game);
+ if (playerDefender != null) {
+ playersAttacked.add(playerDefender);
+ }
+ playersAttackedThisTurn.put(event.getPlayerId(), playersAttacked);
+
+ // opponents
+ PlayerList opponentsAttacked = opponentsAttackedThisTurn.get(event.getPlayerId());
+ if (opponentsAttacked == null) {
+ opponentsAttacked = new PlayerList();
+ }
+ UUID opponentDefender = game.getCombat().getDefendingPlayerId(event.getSourceId(), game);
+ if (opponentDefender != null && game.getOpponents(event.getPlayerId()).contains(opponentDefender)) {
+ opponentsAttacked.add(opponentDefender);
+ }
+ opponentsAttackedThisTurn.put(event.getPlayerId(), opponentsAttacked);
+ }
+ }
+
+ public int getAttackedPlayersCount(UUID playerID) {
+ PlayerList defendersList = playersAttackedThisTurn.getOrDefault(playerID, null);
+ if (defendersList != null) {
+ return defendersList.size();
+ }
+ return 0;
+ }
+
+ public int getAttackedOpponentsCount(UUID playerID) {
+ PlayerList defendersList = opponentsAttackedThisTurn.getOrDefault(playerID, null);
+ if (defendersList != null) {
+ return defendersList.size();
+ }
+ return 0;
+ }
+}
diff --git a/pom.xml b/pom.xml
index 39d6abf21b4..62c3c3a0d9a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -103,7 +103,7 @@
org.slf4j
slf4j-log4j12
- 1.7.19
+ 1.7.25