Merge remote-tracking branch 'upstream/master'

# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
This commit is contained in:
ninthworld 2018-07-16 21:16:20 -07:00
parent f31bfa829e
commit 86107de54e
620 changed files with 5627 additions and 2239 deletions

View file

@ -8,7 +8,7 @@ import mage.abilities.condition.Condition;
import mage.abilities.condition.InvertCondition;
import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition;
import mage.abilities.decorator.ConditionalOneShotEffect;
import mage.abilities.decorator.ConditionalTriggeredAbility;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.constants.TargetController;
import mage.filter.FilterPermanent;
@ -19,7 +19,7 @@ import mage.filter.predicate.mageobject.ColorPredicate;
*
* @author TheElk801
*/
public class SanctuaryTriggeredAbility extends ConditionalTriggeredAbility {
public class SanctuaryInterveningIfTriggeredAbility extends ConditionalInterveningIfTriggeredAbility {
private static Condition makeOrCondition(ObjectColor color1, ObjectColor color2) {
FilterPermanent filter = new FilterPermanent();
@ -48,7 +48,7 @@ public class SanctuaryTriggeredAbility extends ConditionalTriggeredAbility {
return ability;
}
public SanctuaryTriggeredAbility(OneShotEffect effect1, OneShotEffect effect2, ObjectColor color1, ObjectColor color2, String text) {
public SanctuaryInterveningIfTriggeredAbility(OneShotEffect effect1, OneShotEffect effect2, ObjectColor color1, ObjectColor color2, String text) {
super(makeTrigger(effect1, effect2, color1, color2), makeOrCondition(color1, color2), text);
}
}

View file

@ -230,7 +230,7 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
if (condition == null || alternateCosts.size() == 1) {
sb.append(" rather than pay this spell's mana cost");
} else if (alternateCosts.isEmpty()) {
sb.append("cast {this} without paying its mana cost");
sb.append("cast this spell without paying its mana cost");
}
sb.append('.');
if (numberCosts == 1 && remarkText != null) {

View file

@ -207,7 +207,9 @@ public abstract class ManaCostImpl extends CostImpl implements ManaCost {
return true;
}
Player player = game.getPlayer(controllerId);
assignPayment(game, ability, player.getManaPool(), costToPay);
if (!player.getManaPool().isForcedToPay()) {
assignPayment(game, ability, player.getManaPool(), costToPay);
}
game.getState().getSpecialActions().removeManaActions();
while (!isPaid()) {
ManaCost unpaid = this.getUnpaid();

View file

@ -117,7 +117,9 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
}
Player player = game.getPlayer(controllerId);
assignPayment(game, ability, player.getManaPool(), this);
if (!player.getManaPool().isForcedToPay()) {
assignPayment(game, ability, player.getManaPool(), this);
}
game.getState().getSpecialActions().removeManaActions();
while (!isPaid()) {
ManaCost unpaid = this.getUnpaid();
@ -235,10 +237,15 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
@Override
public void assignPayment(Game game, Ability ability, ManaPool pool, Cost costToPay) {
if (!pool.isAutoPayment() && pool.getUnlockedManaType() == null) {
boolean wasUnlockedManaType = (pool.getUnlockedManaType() != null);
if (!pool.isAutoPayment() && !wasUnlockedManaType) {
// if auto payment is inactive and no mana type was clicked manually - do nothing
return;
}
ManaCosts referenceCosts = null;
if (pool.isForcedToPay()) {
referenceCosts = this.copy();
}
// attempt to pay colorless costs (not generic) mana costs first
for (ManaCost cost : this) {
if (!cost.isPaid() && cost instanceof ColorlessManaCost) {
@ -318,6 +325,31 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
}
// stop using mana of the clicked mana type
pool.lockManaType();
if (!wasUnlockedManaType) {
handleForcedToPayOnlyForCurrentPayment(game, pool, referenceCosts);
}
}
private void handleForcedToPayOnlyForCurrentPayment(Game game, ManaPool pool, ManaCosts referenceCosts) {
// for Word of Command
if (pool.isForcedToPay()) {
if (referenceCosts != null && this.getText().equals(referenceCosts.getText())) {
UUID playerId = pool.getPlayerId();
Player player = game.getPlayer(playerId);
if (player != null) {
game.undo(playerId);
this.clearPaid();
this.setX(referenceCosts.getX());
player.getManaPool().restoreMana(pool.getPoolBookmark());
game.bookmarkState();
}
}
}
}
public void forceManaRollback(Game game, ManaPool pool) {
// for Word of Command
handleForcedToPayOnlyForCurrentPayment(game, pool, this);
}
@Override

View file

@ -0,0 +1,104 @@
package mage.abilities.decorator;
import mage.abilities.Modes;
import mage.abilities.TriggeredAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.condition.Condition;
import mage.abilities.effects.Effect;
import mage.abilities.effects.Effects;
import mage.constants.EffectType;
import mage.game.Game;
import mage.game.events.GameEvent;
/**
* Adds condition to {@link mage.abilities.effects.ContinuousEffect}. Acts as
* decorator.
*
* @author nantuko
*/
public class ConditionalInterveningIfTriggeredAbility extends TriggeredAbilityImpl {
protected TriggeredAbility ability;
protected Condition condition;
protected String abilityText;
/**
* Triggered ability with a condition. Set the optionality for the trigger
* ability itself.
*
* @param ability
* @param condition
* @param text explicit rule text for the ability, if null or empty, the
* rule text generated by the triggered ability itself is used.
*/
public ConditionalInterveningIfTriggeredAbility(TriggeredAbility ability, Condition condition, String text) {
super(ability.getZone(), null);
this.ability = ability;
this.modes = ability.getModes();
this.condition = condition;
this.abilityText = text;
}
public ConditionalInterveningIfTriggeredAbility(final ConditionalInterveningIfTriggeredAbility triggered) {
super(triggered);
this.ability = triggered.ability.copy();
this.condition = triggered.condition;
this.abilityText = triggered.abilityText;
}
@Override
public boolean checkInterveningIfClause(Game game) {
return condition.apply(game, this);
}
@Override
public ConditionalInterveningIfTriggeredAbility copy() {
return new ConditionalInterveningIfTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return ability.checkEventType(event, game);
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
ability.setSourceId(this.getSourceId());
ability.setControllerId(this.getControllerId());
return ability.checkTrigger(event, game);
}
@Override
public String getRule() {
if (abilityText == null || abilityText.isEmpty()) {
return ability.getRule();
}
return abilityText;
}
@Override
public Effects getEffects() {
return ability.getEffects();
}
@Override
public void addEffect(Effect effect) {
ability.addEffect(effect);
}
@Override
public Modes getModes() {
return ability.getModes();
}
@Override
public Effects getEffects(Game game, EffectType effectType) {
return ability.getEffects(game, effectType);
}
@Override
public boolean isOptional() {
return ability.isOptional();
}
}

View file

@ -14,7 +14,7 @@ import mage.game.events.GameEvent;
* Adds condition to {@link mage.abilities.effects.ContinuousEffect}. Acts as
* decorator.
*
* @author nantuko
* @author noahg
*/
public class ConditionalTriggeredAbility extends TriggeredAbilityImpl {
@ -46,11 +46,6 @@ public class ConditionalTriggeredAbility extends TriggeredAbilityImpl {
this.abilityText = triggered.abilityText;
}
@Override
public boolean checkInterveningIfClause(Game game) {
return condition.apply(game, this);
}
@Override
public ConditionalTriggeredAbility copy() {
return new ConditionalTriggeredAbility(this);
@ -65,7 +60,7 @@ public class ConditionalTriggeredAbility extends TriggeredAbilityImpl {
public boolean checkTrigger(GameEvent event, Game game) {
ability.setSourceId(this.getSourceId());
ability.setControllerId(this.getControllerId());
return ability.checkTrigger(event, game);
return ability.checkTrigger(event, game) && condition.apply(game, this);
}
@Override

View file

@ -543,7 +543,7 @@ public class ContinuousEffects implements Serializable {
* @param game
* @return
*/
private List<AsThoughEffect> getApplicableAsThoughEffects(AsThoughEffectType type, Game game) {
public List<AsThoughEffect> getApplicableAsThoughEffects(AsThoughEffectType type, Game game) {
List<AsThoughEffect> asThoughEffectsList = new ArrayList<>();
if (asThoughEffectsMap.containsKey(type)) {
for (AsThoughEffect effect : asThoughEffectsMap.get(type)) {

View file

@ -33,7 +33,7 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect {
private final UUID playerId;
private final CardType additionalCardType;
private boolean gainsHaste;
private boolean hasHaste;
private final int number;
private List<Permanent> addedTokenPermanents;
private SubType additionalSubType;
@ -62,12 +62,12 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect {
this(playerId, null, false);
}
public CreateTokenCopyTargetEffect(UUID playerId, CardType additionalCardType, boolean gainsHaste) {
this(playerId, additionalCardType, gainsHaste, 1);
public CreateTokenCopyTargetEffect(UUID playerId, CardType additionalCardType, boolean hasHaste) {
this(playerId, additionalCardType, hasHaste, 1);
}
public CreateTokenCopyTargetEffect(UUID playerId, CardType additionalCardType, boolean gainsHaste, int number) {
this(playerId, additionalCardType, gainsHaste, number, false, false);
public CreateTokenCopyTargetEffect(UUID playerId, CardType additionalCardType, boolean hasHaste, int number) {
this(playerId, additionalCardType, hasHaste, number, false, false);
}
/**
@ -75,24 +75,24 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect {
* @param playerId null the token is controlled/owned by the controller of
* the source ability
* @param additionalCardType the token gains this card type in addition
* @param gainsHaste the token gains haste
* @param hasHaste the token gains haste
* @param number number of tokens to put into play
* @param tapped
* @param attacking
*/
public CreateTokenCopyTargetEffect(UUID playerId, CardType additionalCardType, boolean gainsHaste, int number, boolean tapped, boolean attacking) {
this(playerId, additionalCardType, gainsHaste, number, tapped, attacking, null);
public CreateTokenCopyTargetEffect(UUID playerId, CardType additionalCardType, boolean hasHaste, int number, boolean tapped, boolean attacking) {
this(playerId, additionalCardType, hasHaste, number, tapped, attacking, null);
}
public CreateTokenCopyTargetEffect(UUID playerId, CardType additionalCardType, boolean gainsHaste, int number, boolean tapped, boolean attacking, UUID attackedPlayer) {
this(playerId, additionalCardType, gainsHaste, number, tapped, attacking, attackedPlayer, Integer.MIN_VALUE, Integer.MIN_VALUE, false);
public CreateTokenCopyTargetEffect(UUID playerId, CardType additionalCardType, boolean hasHaste, int number, boolean tapped, boolean attacking, UUID attackedPlayer) {
this(playerId, additionalCardType, hasHaste, number, tapped, attacking, attackedPlayer, Integer.MIN_VALUE, Integer.MIN_VALUE, false);
}
public CreateTokenCopyTargetEffect(UUID playerId, CardType additionalCardType, boolean gainsHaste, int number, boolean tapped, boolean attacking, UUID attackedPlayer, int power, int toughness, boolean gainsFlying) {
public CreateTokenCopyTargetEffect(UUID playerId, CardType additionalCardType, boolean hasHaste, int number, boolean tapped, boolean attacking, UUID attackedPlayer, int power, int toughness, boolean gainsFlying) {
super(Outcome.PutCreatureInPlay);
this.playerId = playerId;
this.additionalCardType = additionalCardType;
this.gainsHaste = gainsHaste;
this.hasHaste = hasHaste;
this.addedTokenPermanents = new ArrayList<>();
this.number = number;
this.tapped = tapped;
@ -107,7 +107,7 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect {
super(effect);
this.playerId = effect.playerId;
this.additionalCardType = effect.additionalCardType;
this.gainsHaste = effect.gainsHaste;
this.hasHaste = effect.hasHaste;
this.addedTokenPermanents = new ArrayList<>(effect.addedTokenPermanents);
this.number = effect.number;
this.additionalSubType = effect.additionalSubType;
@ -187,7 +187,7 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect {
if (additionalCardType != null && !token.getCardType().contains(additionalCardType)) {
token.addCardType(additionalCardType);
}
if (gainsHaste) {
if (hasHaste) {
token.addAbility(HasteAbility.getInstance());
}
if (gainsFlying) {

View file

@ -141,6 +141,7 @@ public class DoIfCostPaid extends OneShotEffect {
String costText = cost.getText();
if (costText != null
&& !costText.toLowerCase(Locale.ENGLISH).startsWith("put")
&& !costText.toLowerCase(Locale.ENGLISH).startsWith("return")
&& !costText.toLowerCase(Locale.ENGLISH).startsWith("exile")
&& !costText.toLowerCase(Locale.ENGLISH).startsWith("discard")
&& !costText.toLowerCase(Locale.ENGLISH).startsWith("sacrifice")

View file

@ -61,6 +61,7 @@ public class LookLibraryAndPickControllerEffect extends LookLibraryControllerEff
private boolean putOnTopSelected;
private boolean anyOrder;
//TODO: These constructors are a mess
public LookLibraryAndPickControllerEffect(DynamicValue numberOfCards,
boolean mayShuffleAfter, DynamicValue numberToPick,
FilterCard pickFilter, boolean putOnTop) {

View file

@ -0,0 +1,42 @@
package mage.abilities.effects.common;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.constants.TargetController;
import mage.game.Game;
import mage.players.Player;
/**
*
* @author noahg
*/
public class RevealHandSourceControllerEffect extends OneShotEffect {
public RevealHandSourceControllerEffect() {
super(Outcome.Discard);
this.staticText = "reveal your hand";
}
public RevealHandSourceControllerEffect(final RevealHandSourceControllerEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
MageObject sourceObject = game.getObject(source.getSourceId());
if (player != null && sourceObject != null) {
player.revealCards(sourceObject.getIdName(), player.getHand(), game);
return true;
}
return false;
}
@Override
public RevealHandSourceControllerEffect copy() {
return new RevealHandSourceControllerEffect(this);
}
}

View file

@ -0,0 +1,74 @@
/*
*
* 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.effects.common;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.constants.PhaseStep;
import mage.game.Game;
import mage.game.turn.TurnMod;
import mage.players.Player;
/**
*
* @author noahg
*/
public class SkipNextDrawStepControllerEffect extends OneShotEffect {
public SkipNextDrawStepControllerEffect() {
this("you skip your next draw step");
}
public SkipNextDrawStepControllerEffect(String text) {
super(Outcome.Detriment);
this.staticText = text;
}
public SkipNextDrawStepControllerEffect(SkipNextDrawStepControllerEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player != null) {
game.getState().getTurnMods().add(new TurnMod(player.getId(), PhaseStep.DRAW));
return true;
}
return false;
}
@Override
public SkipNextDrawStepControllerEffect copy() {
return new SkipNextDrawStepControllerEffect(this);
}
}

View file

@ -0,0 +1,154 @@
/*
*
* 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.effects.common.continuous;
import mage.MageObject;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.choices.ChoiceColor;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.stack.Spell;
import mage.players.Player;
import java.util.UUID;
/**
* @author LevelX
*/
public class BecomesColorAllEffect extends ContinuousEffectImpl {
private ObjectColor setColor;
protected boolean loseOther;
protected FilterPermanent filter;
/**
* Set the color of a spell or permanent
*
* @param duration
*/
public BecomesColorAllEffect(Duration duration) {
this(null, duration);
}
public BecomesColorAllEffect(ObjectColor setColor, Duration duration) {
this(setColor, duration, new FilterPermanent("All permanents"), true, null);
}
public BecomesColorAllEffect(ObjectColor setColor, Duration duration, FilterPermanent filter, boolean loseOther, String text) {
super(duration, Layer.ColorChangingEffects_5, SubLayer.NA, Outcome.Neutral);
this.setColor = setColor;
this.filter = filter;
this.loseOther = loseOther;
staticText = text;
}
public BecomesColorAllEffect(final BecomesColorAllEffect effect) {
super(effect);
this.setColor = effect.setColor;
this.filter = effect.filter;
this.loseOther = effect.loseOther;
}
@Override
public void init(Ability source, Game game) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return;
}
if (setColor == null) {
ChoiceColor choice = new ChoiceColor();
if (!controller.choose(Outcome.PutManaInPool, choice, game)) {
discard();
return;
}
setColor = choice.getColor();
if (!game.isSimulation()) {
game.informPlayers(controller.getLogName() + " has chosen the color: " + setColor.toString());
}
}
super.init(source, game); //To change body of generated methods, choose Tools | Templates.
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return false;
}
if (setColor != null) {
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) {
if (permanent != null) {
switch (layer) {
case ColorChangingEffects_5:
if (loseOther) {
permanent.getColor(game).setColor(new ObjectColor());
}
permanent.getColor(game).addColor(setColor);
break;
}
} else if (duration == Duration.Custom) {
discard();
}
}
return true;
}
return false;
}
@Override
public BecomesColorAllEffect copy() {
return new BecomesColorAllEffect(this);
}
@Override
public String getText(Mode mode) {
if (staticText != null && !staticText.isEmpty()) {
return staticText;
}
StringBuilder sb = new StringBuilder();
sb.append(filter.getMessage());
sb.append(" become ");
if (setColor == null) {
sb.append("the color of your choice");
} else {
sb.append(setColor.getDescription());
}
if (!duration.toString().equals("")) {
sb.append(' ').append(duration.toString());
}
return sb.toString();
}
}

View file

@ -0,0 +1,91 @@
package mage.abilities.effects.common.counter;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.counters.Counter;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.util.CardUtil;
import java.util.Locale;
/**
*
* @author noahg
*/
public class RemoveCountersAttachedEffect extends OneShotEffect {
private Counter counter;
private DynamicValue amount;
private String textEnchanted;
public RemoveCountersAttachedEffect(Counter counter, String textEnchanted) {
this(counter, new StaticValue(0), textEnchanted);
}
/**
*
* @param counter
* @param amount this amount will be added to the counter instances
* @param textEnchanted text used for the enchanted permanent in rule text
*/
public RemoveCountersAttachedEffect(Counter counter, DynamicValue amount, String textEnchanted) {
super(Outcome.UnboostCreature);
this.counter = counter.copy();
this.amount = amount;
this.textEnchanted = textEnchanted;
setText();
}
public RemoveCountersAttachedEffect(final RemoveCountersAttachedEffect effect) {
super(effect);
if (effect.counter != null) {
this.counter = effect.counter.copy();
}
this.amount = effect.amount;
this.textEnchanted = effect.textEnchanted;
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null && permanent.getAttachedTo() != null) {
Permanent attachedTo = game.getPermanent(permanent.getAttachedTo());
if (attachedTo != null && counter != null) {
Counter newCounter = counter.copy();
newCounter.add(amount.calculate(game, source, this));
attachedTo.removeCounters(newCounter, game);
}
return true;
}
return false;
}
private void setText() {
StringBuilder sb = new StringBuilder();
// put a +1/+1 counter on it
sb.append("remove ");
if (counter.getCount() > 1) {
sb.append(CardUtil.numberToText(counter.getCount())).append(' ');
sb.append(counter.getName().toLowerCase(Locale.ENGLISH)).append(" counters from ");
} else {
sb.append("a ");
sb.append(counter.getName().toLowerCase(Locale.ENGLISH)).append(" counter from ");
}
sb.append(textEnchanted);
if (!amount.getMessage().isEmpty()) {
sb.append(" for each ").append(amount.getMessage());
}
staticText = sb.toString();
}
@Override
public RemoveCountersAttachedEffect copy() {
return new RemoveCountersAttachedEffect(this);
}
}

View file

@ -0,0 +1,94 @@
package mage.abilities.effects.keyword;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.costs.Cost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.OneShotEffect;
import mage.constants.AsThoughEffectType;
import mage.constants.Outcome;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import java.util.Locale;
public class EchoEffect extends OneShotEffect {
protected Cost cost;
protected DynamicValue amount;
public EchoEffect(Cost costs) {
super(Outcome.Sacrifice);
this.cost = costs;
this.amount = null;
}
public EchoEffect(DynamicValue amount) {
super(Outcome.Sacrifice);
this.amount = amount;
this.cost = null;
}
public EchoEffect(final EchoEffect effect) {
super(effect);
this.cost = effect.cost;
this.amount = effect.amount;
}
@Override
public boolean apply(Game game, Ability source) {
if (cost == null) {
cost = new ManaCostsImpl(Integer.toString(amount.calculate(game, source, this)));
}
Player controller = game.getPlayer(source.getControllerId());
if (controller != null
&& source.getSourceObjectIfItStillExists(game) != null) {
if (game.getContinuousEffects().asThough(source.getSourceId(), AsThoughEffectType.PAY_0_ECHO, source, source.getControllerId(), game) != null) {
Cost altCost = new ManaCostsImpl("{0}");
if (controller.chooseUse(Outcome.Benefit, "Pay {0} instead of the echo cost?", source, game)) {
altCost.clearPaid();
if (altCost.pay(source, game, source.getSourceId(), source.getControllerId(), false, null)) {
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.ECHO_PAID, source.getSourceId(), source.getSourceId(), source.getControllerId()));
return true;
}
}
}
if (controller.chooseUse(Outcome.Benefit, "Pay " + cost.getText() + '?', source, game)) {
cost.clearPaid();
if (cost.pay(source, game, source.getSourceId(), source.getControllerId(), false, null)) {
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.ECHO_PAID, source.getSourceId(), source.getSourceId(), source.getControllerId()));
return true;
}
}
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null) {
permanent.sacrifice(source.getSourceId(), game);
}
return true;
}
return false;
}
@Override
public EchoEffect copy() {
return new EchoEffect(this);
}
@Override
public String getText(Mode mode) {
StringBuilder sb = new StringBuilder("sacrifice {this} unless you ");
String costText = cost.getText();
if (costText.toLowerCase(Locale.ENGLISH).startsWith("discard")) {
sb.append(costText.substring(0, 1).toLowerCase(Locale.ENGLISH));
sb.append(costText.substring(1));
} else {
sb.append("pay ").append(costText);
}
return sb.toString();
}
}

View file

@ -16,6 +16,7 @@ import mage.counters.CounterType;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.events.ManaEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
@ -84,7 +85,7 @@ class CumulativeUpkeepEffect extends OneShotEffect {
if (player.chooseUse(Outcome.Benefit, "Pay " + totalCost.getText() + '?', source, game)) {
totalCost.clearPaid();
if (totalCost.payOrRollback(source, game, source.getSourceId(), source.getControllerId())) {
game.fireEvent(new GameEvent(EventType.PAID_CUMULATIVE_UPKEEP, permanent.getId(), permanent.getId(), player.getId(), ageCounter, false));
game.fireEvent(new ManaEvent(EventType.PAID_CUMULATIVE_UPKEEP, permanent.getId(), permanent.getId(), player.getId(), totalCost.getUsedManaToPay()));
return true;
}
}

View file

@ -1,23 +1,18 @@
package mage.abilities.keyword;
import java.util.Locale;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
import mage.abilities.costs.CostsImpl;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.abilities.effects.keyword.EchoEffect;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import java.util.UUID;
/**
*
@ -82,7 +77,7 @@ public class EchoAbility extends TriggeredAbilityImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
// reset the echo paid state back, if creature enteres the battlefield
// reset the echo paid state back, if creature enters the battlefield
if (event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD
&& event.getTargetId().equals(this.getSourceId())) {
@ -128,70 +123,3 @@ public class EchoAbility extends TriggeredAbilityImpl {
}
}
class EchoEffect extends OneShotEffect {
protected Cost cost;
protected DynamicValue amount;
public EchoEffect(Cost costs) {
super(Outcome.Sacrifice);
this.cost = costs;
this.amount = null;
}
public EchoEffect(DynamicValue amount) {
super(Outcome.Sacrifice);
this.amount = amount;
this.cost = null;
}
public EchoEffect(final EchoEffect effect) {
super(effect);
this.cost = effect.cost;
this.amount = effect.amount;
}
@Override
public boolean apply(Game game, Ability source) {
if (amount != null) {
cost = new ManaCostsImpl(Integer.toString(amount.calculate(game, source, this)));
}
Player controller = game.getPlayer(source.getControllerId());
if (controller != null
&& source.getSourceObjectIfItStillExists(game) != null) {
if (controller.chooseUse(Outcome.Benefit, "Pay " + cost.getText() + '?', source, game)) {
cost.clearPaid();
if (cost.pay(source, game, source.getSourceId(), source.getControllerId(), false, null)) {
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.ECHO_PAID, source.getSourceId(), source.getSourceId(), source.getControllerId()));
return true;
}
}
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null) {
permanent.sacrifice(source.getSourceId(), game);
}
return true;
}
return false;
}
@Override
public EchoEffect copy() {
return new EchoEffect(this);
}
@Override
public String getText(Mode mode) {
StringBuilder sb = new StringBuilder("sacrifice {this} unless you ");
String costText = cost.getText();
if (costText.toLowerCase(Locale.ENGLISH).startsWith("discard")) {
sb.append(costText.substring(0, 1).toLowerCase(Locale.ENGLISH));
sb.append(costText.substring(1));
} else {
sb.append("pay ").append(costText);
}
return sb.toString();
}
}

View file

@ -16,7 +16,7 @@ import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
import mage.abilities.costs.CostsImpl;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.decorator.ConditionalTriggeredAbility;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.common.SacrificeSourceEffect;
import mage.cards.Card;
import mage.constants.Outcome;
@ -42,7 +42,7 @@ public class EvokeAbility extends StaticAbility implements AlternativeSourceCost
super(Zone.ALL, null);
name = EVOKE_KEYWORD;
this.addEvokeCost(manaString);
Ability ability = new ConditionalTriggeredAbility(new EntersBattlefieldTriggeredAbility(new SacrificeSourceEffect()), EvokedCondition.instance, "Sacrifice {this} when it enters the battlefield and was evoked.");
Ability ability = new ConditionalInterveningIfTriggeredAbility(new EntersBattlefieldTriggeredAbility(new SacrificeSourceEffect()), EvokedCondition.instance, "Sacrifice {this} when it enters the battlefield and was evoked.");
ability.setRuleVisible(false);
addSubAbility(ability);

View file

@ -11,7 +11,7 @@ import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.common.DiesTriggeredAbility;
import mage.abilities.condition.common.SourceHasCounterCondition;
import mage.abilities.decorator.ConditionalTriggeredAbility;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
@ -36,7 +36,7 @@ public class RepairAbility extends DiesTriggeredAbility {
public RepairAbility(int count) {
super(new AddCountersSourceEffect(CounterType.REPAIR.createInstance(), new StaticValue(count), false, true));
addSubAbility(new RepairBeginningOfUpkeepTriggeredAbility());
addSubAbility(new RepairBeginningOfUpkeepInterveningIfTriggeredAbility());
addSubAbility(new RepairCastFromGraveyardTriggeredAbility());
ruleText = "Repair " + count + " <i>(When this creature dies, put " + count
@ -127,9 +127,9 @@ class RepairCastFromGraveyardTriggeredAbility extends TriggeredAbilityImpl {
}
}
class RepairBeginningOfUpkeepTriggeredAbility extends ConditionalTriggeredAbility {
class RepairBeginningOfUpkeepInterveningIfTriggeredAbility extends ConditionalInterveningIfTriggeredAbility {
public RepairBeginningOfUpkeepTriggeredAbility() {
public RepairBeginningOfUpkeepInterveningIfTriggeredAbility() {
super(new BeginningOfUpkeepTriggeredAbility(Zone.GRAVEYARD, new RemoveCounterSourceEffect(CounterType.REPAIR.createInstance()), TargetController.YOU, false),
new SourceHasCounterCondition(CounterType.REPAIR),
"At the beginning of your upkeep, remove a repair counter from {this}");
@ -137,12 +137,12 @@ class RepairBeginningOfUpkeepTriggeredAbility extends ConditionalTriggeredAbilit
}
public RepairBeginningOfUpkeepTriggeredAbility(final RepairBeginningOfUpkeepTriggeredAbility effect) {
public RepairBeginningOfUpkeepInterveningIfTriggeredAbility(final RepairBeginningOfUpkeepInterveningIfTriggeredAbility effect) {
super(effect);
}
@Override
public RepairBeginningOfUpkeepTriggeredAbility copy() {
return new RepairBeginningOfUpkeepTriggeredAbility(this);
public RepairBeginningOfUpkeepInterveningIfTriggeredAbility copy() {
return new RepairBeginningOfUpkeepInterveningIfTriggeredAbility(this);
}
}

View file

@ -14,7 +14,7 @@ import mage.abilities.condition.common.SuspendedCondition;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.costs.mana.VariableManaCost;
import mage.abilities.decorator.ConditionalTriggeredAbility;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.OneShotEffect;
@ -154,7 +154,7 @@ public class SuspendAbility extends SpecialAction {
if (card.getManaCost().isEmpty()) {
setRuleAtTheTop(true);
}
addSubAbility(new SuspendBeginningOfUpkeepTriggeredAbility());
addSubAbility(new SuspendBeginningOfUpkeepInterveningIfTriggeredAbility());
addSubAbility(new SuspendPlayCardAbility());
}
ruleText = sb.toString();
@ -174,7 +174,7 @@ public class SuspendAbility extends SpecialAction {
ability.setControllerId(card.getOwnerId());
game.getState().addOtherAbility(card, ability);
SuspendBeginningOfUpkeepTriggeredAbility ability1 = new SuspendBeginningOfUpkeepTriggeredAbility();
SuspendBeginningOfUpkeepInterveningIfTriggeredAbility ability1 = new SuspendBeginningOfUpkeepInterveningIfTriggeredAbility();
ability1.setSourceId(card.getId());
ability1.setControllerId(card.getOwnerId());
game.getState().addOtherAbility(card, ability1);
@ -343,7 +343,7 @@ class SuspendPlayCardEffect extends OneShotEffect {
}
if (!abilitiesToRemove.isEmpty()) {
for (Ability ability : card.getAbilities()) {
if (ability instanceof SuspendBeginningOfUpkeepTriggeredAbility || ability instanceof SuspendPlayCardAbility) {
if (ability instanceof SuspendBeginningOfUpkeepInterveningIfTriggeredAbility || ability instanceof SuspendPlayCardAbility) {
abilitiesToRemove.add(ability);
}
}
@ -405,9 +405,9 @@ class GainHasteEffect extends ContinuousEffectImpl {
}
class SuspendBeginningOfUpkeepTriggeredAbility extends ConditionalTriggeredAbility {
class SuspendBeginningOfUpkeepInterveningIfTriggeredAbility extends ConditionalInterveningIfTriggeredAbility {
public SuspendBeginningOfUpkeepTriggeredAbility() {
public SuspendBeginningOfUpkeepInterveningIfTriggeredAbility() {
super(new BeginningOfUpkeepTriggeredAbility(Zone.EXILED, new RemoveCounterSourceEffect(CounterType.TIME.createInstance()), TargetController.YOU, false),
SuspendedCondition.instance,
"At the beginning of your upkeep, if this card ({this}) is suspended, remove a time counter from it.");
@ -415,12 +415,12 @@ class SuspendBeginningOfUpkeepTriggeredAbility extends ConditionalTriggeredAbili
}
public SuspendBeginningOfUpkeepTriggeredAbility(final SuspendBeginningOfUpkeepTriggeredAbility effect) {
public SuspendBeginningOfUpkeepInterveningIfTriggeredAbility(final SuspendBeginningOfUpkeepInterveningIfTriggeredAbility effect) {
super(effect);
}
@Override
public SuspendBeginningOfUpkeepTriggeredAbility copy() {
return new SuspendBeginningOfUpkeepTriggeredAbility(this);
public SuspendBeginningOfUpkeepInterveningIfTriggeredAbility copy() {
return new SuspendBeginningOfUpkeepInterveningIfTriggeredAbility(this);
}
}

View file

@ -0,0 +1,136 @@
package mage.abilities.meta;
import mage.MageObject;
import mage.abilities.TriggeredAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.watchers.Watcher;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* A triggered ability that combines several others and triggers whenever one or more of them would. The abilities
* passed in should have null as their effect, and should have their own targets set if necessary. All other information
* will be passed in from changes to this Ability. Note: this does NOT work with abilities that have intervening if clauses.
* @author noahg
*/
public class OrTriggeredAbility extends TriggeredAbilityImpl {
private final String ruleTrigger;
private TriggeredAbility[] triggeredAbilities;
private List<Integer> triggeringAbilities;
public OrTriggeredAbility(Zone zone, Effect effect, TriggeredAbility... abilities) {
this(zone, effect, false, null, abilities);
}
public OrTriggeredAbility(Zone zone, Effect effect, boolean optional, String ruleTrigger, TriggeredAbility... abilities) {
super(zone, effect, optional);
this.triggeredAbilities = abilities;
this.ruleTrigger = ruleTrigger;
this.triggeringAbilities = new ArrayList<>();
for (TriggeredAbility ability : triggeredAbilities) {
//Remove useless data
ability.getEffects().clear();
}
}
public OrTriggeredAbility(OrTriggeredAbility ability) {
super(ability);
this.triggeredAbilities = new TriggeredAbility[ability.triggeredAbilities.length];
for (int i = 0; i < this.triggeredAbilities.length; i++){
this.triggeredAbilities[i] = ability.triggeredAbilities[i].copy();
}
this.triggeringAbilities = new ArrayList<>(ability.triggeringAbilities);
this.ruleTrigger = ability.ruleTrigger;
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
for (TriggeredAbility ability : triggeredAbilities) {
if (ability.checkEventType(event, game)){
System.out.println("Correct event type (" + event.getType() + ")");
return true;
}
}
return false;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
boolean toRet = false;
for (int i = 0; i < triggeredAbilities.length; i++) {
TriggeredAbility ability = triggeredAbilities[i];
if (ability.checkEventType(event, game) && ability.checkTrigger(event, game)) {
System.out.println("Triggered from " + ability.getRule());
triggeringAbilities.add(i);
toRet = true;
}
System.out.println("Checked " + ability.getRule());
}
return toRet;
}
@Override
public OrTriggeredAbility copy() {
return new OrTriggeredAbility(this);
}
@Override
public String getRule() {
if (ruleTrigger != null && !ruleTrigger.isEmpty()) {
return ruleTrigger + super.getRule();
}
StringBuilder sb = new StringBuilder();
if (triggeredAbilities[0].getRule().length() > 0) {
sb.append(triggeredAbilities[0].getRule().substring(0, 1).toUpperCase())
.append(triggeredAbilities[0].getRule().substring(1).toLowerCase());
}
for (int i = 1; i < (triggeredAbilities.length - 1); i++) {
sb.append(triggeredAbilities[i].getRule().toLowerCase());
}
sb.append(" or ").append(triggeredAbilities[triggeredAbilities.length - 1].getRule().toLowerCase());
return sb.toString() + super.getRule();
}
@Override
public void setControllerId(UUID controllerId) {
super.setControllerId(controllerId);
for (TriggeredAbility ability : triggeredAbilities) {
ability.setControllerId(controllerId);
}
}
@Override
public void setSourceId(UUID sourceId) {
super.setSourceId(sourceId);
for (TriggeredAbility ability : triggeredAbilities) {
ability.setSourceId(sourceId);
}
}
@Override
public void addWatcher(Watcher watcher) {
super.addWatcher(watcher);
for (TriggeredAbility ability : triggeredAbilities) {
ability.addWatcher(watcher);
}
}
@Override
public void setSourceObject(MageObject sourceObject, Game game) {
super.setSourceObject(sourceObject, game);
for (TriggeredAbility ability : triggeredAbilities) {
ability.setSourceObject(sourceObject, game);
}
}
}

View file

@ -59,7 +59,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
protected UUID ownerId;
protected String cardNumber;
public String expansionSetCode;
protected String expansionSetCode;
protected String tokenSetCode;
protected String tokenDescriptor;
protected Rarity rarity;

View file

@ -18,6 +18,7 @@ public enum AsThoughEffectType {
BLOCK_SWAMPWALK,
BLOCK_MOUNTAINWALK,
BLOCK_FORESTWALK,
DAMAGE_NOT_BLOCKED,
BE_BLOCKED,
PLAY_FROM_NOT_OWN_HAND_ZONE,
CAST_AS_INSTANT,
@ -25,7 +26,7 @@ public enum AsThoughEffectType {
DAMAGE,
SHROUD,
HEXPROOF,
PAY,
PAY_0_ECHO,
LOOK_AT_FACE_DOWN,
SPEND_OTHER_MANA,
SPEND_ONLY_MANA,

View file

@ -80,6 +80,7 @@ public enum CounterType {
MUSTER("muster"),
NET("net"),
OMEN("omen"),
ORE("ore"),
P0P1(new BoostCounter(0, 1).name),
P1P0(new BoostCounter(1, 0).name),
P1P1(new BoostCounter(1, 1).name),

View file

@ -28,13 +28,8 @@ public class AbilityPredicate implements Predicate<MageObject> {
} else {
abilities = input.getAbilities();
}
for (Ability ability : abilities) {
if (abilityClass.equals(ability.getClass())) {
return true;
}
}
return false;
return abilities.stream().anyMatch(ability -> ability.getClass().equals(abilityClass));
}
@Override

View file

@ -16,12 +16,8 @@ public class VariableManaCostPredicate implements Predicate<MageObject> {
@Override
public boolean apply(MageObject input, Game game) {
for (ManaCost manaCost : input.getManaCost()) {
if (manaCost instanceof VariableManaCost) {
return true;
}
}
return false;
return input.getManaCost().stream().anyMatch(manaCost -> manaCost instanceof VariableManaCost);
}
@Override

View file

@ -19,8 +19,8 @@ public class OwnerIdPredicate implements Predicate<Card> {
}
@Override
public boolean apply(Card input, Game game) {
return ownerId.equals(input.getOwnerId());
public boolean apply(Card card, Game game) {
return card.isOwnedBy(ownerId);
}
@Override

View file

@ -18,11 +18,14 @@ public class PlayerCanGainLifePredicate implements ObjectSourcePlayerPredicate<O
@Override
public boolean apply(ObjectSourcePlayer<Player> input, Game game) {
Player player = input.getObject();
return player.isCanGainLife();
if(player != null) {
return player.isCanGainLife();
}
return false;
}
@Override
public String toString() {
return "Player can gain live";
return "Player can gain life";
}
}

View file

@ -17,12 +17,8 @@ public class CounterAnyPredicate implements Predicate<Permanent> {
@Override
public boolean apply(Permanent input, Game game) {
for (Counter counter: input.getCounters(game).values()) {
if (counter.getCount()> 0) {
return true;
}
}
return false;
return input.getCounters(game).values().stream().anyMatch(counter -> counter.getCount() > 0);
}
@Override

View file

@ -1,7 +1,10 @@
package mage.filter.predicate.permanent;
import java.util.Objects;
import java.util.UUID;
import mage.MageObject;
import mage.filter.predicate.Predicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
@ -14,13 +17,11 @@ public class EnchantedPredicate implements Predicate<Permanent> {
@Override
public boolean apply(Permanent input, Game game) {
for (UUID attachmentId : input.getAttachments()) {
Permanent attachment = game.getPermanent(attachmentId);
if (attachment != null && attachment.isEnchantment()) {
return true;
}
}
return false;
return input.getAttachments()
.stream()
.map(game::getPermanent)
.filter(Objects::nonNull)
.anyMatch(MageObject::isEnchantment);
}
@Override

View file

@ -5,27 +5,26 @@
*/
package mage.filter.predicate.permanent;
import java.util.UUID;
import mage.constants.SubType;
import mage.filter.predicate.Predicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.util.Objects;
/**
*
* @author LevelX2
*/
public class EquippedPredicate implements Predicate<Permanent> {
@Override
public boolean apply(Permanent input, Game game) {
for (UUID attachmentId : input.getAttachments()) {
Permanent attachment = game.getPermanent(attachmentId);
if (attachment != null && attachment.hasSubtype(SubType.EQUIPMENT, game)) {
return true;
}
}
return false;
return input.getAttachments()
.stream()
.map(game::getPermanent)
.filter(Objects::nonNull)
.anyMatch(attachment -> attachment.hasSubtype(SubType.EQUIPMENT, game));
}
@Override

View file

@ -398,6 +398,8 @@ public interface Game extends MageItem, Serializable {
void playPriority(UUID activePlayerId, boolean resuming);
void resetControlAfterSpellResolve(UUID topId);
boolean endTurn(Ability source);
int doAction(MageAction action);

View file

@ -1396,6 +1396,7 @@ public abstract class GameImpl implements Game, Serializable {
try {
top = state.getStack().peek();
top.resolve(this);
resetControlAfterSpellResolve(top.getId());
} finally {
if (top != null) {
state.getStack().remove(top, this); // seems partly redundant because move card from stack to grave is already done and the stack removed
@ -1409,6 +1410,37 @@ public abstract class GameImpl implements Game, Serializable {
}
}
}
@Override
public void resetControlAfterSpellResolve(UUID topId) {
// for Word of Command
Spell spell = getSpellOrLKIStack(topId);
if (spell != null) {
if (spell.getCommandedBy() != null) {
UUID commandedBy = spell.getCommandedBy();
UUID spellControllerId = null;
if (commandedBy.equals(spell.getControllerId())) {
spellControllerId = spell.getSpellAbility().getFirstTarget(); // i.e. resolved spell is Word of Command
} else {
spellControllerId = spell.getControllerId(); // i.e. resolved spell is the target opponent's spell
}
if (commandedBy != null && spellControllerId != null) {
Player turnController = getPlayer(commandedBy);
if (turnController != null) {
Player targetPlayer = getPlayer(spellControllerId);
if (targetPlayer != null) {
targetPlayer.setGameUnderYourControl(true, false);
informPlayers(turnController.getLogName() + " lost control over " + targetPlayer.getLogName());
if (targetPlayer.getTurnControlledBy().equals(turnController.getId())) {
turnController.getPlayersUnderYourControl().remove(targetPlayer.getId());
}
}
}
}
spell.setCommandedBy(null);
}
}
}
/**
* This checks if the stack gets filled iterated, without ever getting empty

View file

@ -1,8 +1,6 @@
package mage.game.combat;
import java.io.Serializable;
import java.util.*;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.RequirementEffect;
@ -33,6 +31,9 @@ import mage.util.Copyable;
import mage.util.trace.TraceUtil;
import org.apache.log4j.Logger;
import java.io.Serializable;
import java.util.*;
/**
* @author BetaSteward_at_googlemail.com
*/
@ -47,7 +48,7 @@ public class Combat implements Serializable, Copyable<Combat> {
protected List<CombatGroup> groups = new ArrayList<>();
protected Map<UUID, CombatGroup> blockingGroups = new HashMap<>();
// player and plainswalker ids
// player and planeswalker ids
protected Set<UUID> defenders = new HashSet<>();
// how many creatures attack defending player
protected Map<UUID, Set<UUID>> numberCreaturesDefenderAttackedBy = new HashMap<>();
@ -310,31 +311,28 @@ public class Combat implements Serializable, Copyable<Combat> {
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));
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) {
@ -415,7 +413,6 @@ public class Combat implements Serializable, Copyable<Combat> {
}
/**
*
* @param player
* @param game
* @return true if the attack with that set of creatures and attacked
@ -486,7 +483,7 @@ public class Combat implements Serializable, Copyable<Combat> {
* 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) {
@ -532,7 +529,6 @@ public class Combat implements Serializable, Copyable<Combat> {
/**
* Add info about attacker blocked by blocker to the game log
*
*/
private void logBlockerInfo(Player defender, Game game) {
boolean shownDefendingPlayer = game.getPlayers().size() < 3; // only two players no ned to sow the attacked player
@ -1191,9 +1187,7 @@ public class Combat implements Serializable, Copyable<Combat> {
}
break;
case MULTIPLE:
for (UUID opponentId : game.getOpponents(attackingPlayerId)) {
attackablePlayers.add(opponentId);
}
attackablePlayers.addAll(game.getOpponents(attackingPlayerId));
break;
}
}
@ -1281,10 +1275,10 @@ public class Combat implements Serializable, Copyable<Combat> {
if (defenderAttackedBy.size() >= defendingPlayer.getMaxAttackedBy()) {
Player attackingPlayer = game.getPlayer(game.getControllerId(attackerId));
if (attackingPlayer != null && !game.isSimulation()) {
game.informPlayer(attackingPlayer, new StringBuilder("No more than ")
.append(CardUtil.numberToText(defendingPlayer.getMaxAttackedBy()))
.append(" creatures can attack ")
.append(defendingPlayer.getLogName()).toString());
game.informPlayer(attackingPlayer, "No more than " +
CardUtil.numberToText(defendingPlayer.getMaxAttackedBy()) +
" creatures can attack " +
defendingPlayer.getLogName());
}
return false;
}
@ -1314,7 +1308,7 @@ public class Combat implements Serializable, Copyable<Combat> {
* @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);
@ -1325,9 +1319,7 @@ public class Combat implements Serializable, Copyable<Combat> {
// add all blocked attackers
for (CombatGroup group : groups) {
if (group.getBlockers().contains(blockerId)) {
for (UUID attacker : group.attackers) {
newGroup.attackers.add(attacker);
}
newGroup.attackers.addAll(group.attackers);
}
}
blockingGroups.put(blockerId, newGroup);
@ -1423,12 +1415,8 @@ public class Combat implements Serializable, Copyable<Combat> {
}
public boolean hasFirstOrDoubleStrike(Game game) {
for (CombatGroup group : groups) {
if (group.hasFirstOrDoubleStrike(game)) {
return true;
}
}
return false;
return groups.stream()
.anyMatch(group -> group.hasFirstOrDoubleStrike(game));
}
public CombatGroup findGroup(UUID attackerId) {
@ -1449,7 +1437,7 @@ public class Combat implements Serializable, Copyable<Combat> {
return null;
}
// public int totalUnblockedDamage(Game game) {
// public int totalUnblockedDamage(Game game) {
// int total = 0;
// for (CombatGroup group : groups) {
// if (group.getBlockers().isEmpty()) {
@ -1482,7 +1470,6 @@ public class Combat implements Serializable, Copyable<Combat> {
}
/**
*
* @param attackerId
* @return uuid of defending player or planeswalker
*/

View file

@ -3,6 +3,8 @@ package mage.game.combat;
import java.io.Serializable;
import java.util.*;
import java.util.stream.Stream;
import mage.abilities.common.ControllerAssignCombatDamageToBlockersAbility;
import mage.abilities.common.ControllerDivideCombatDamageAbility;
import mage.abilities.common.DamageAsThoughNotBlockedAbility;
@ -12,6 +14,7 @@ import mage.abilities.keyword.DeathtouchAbility;
import mage.abilities.keyword.DoubleStrikeAbility;
import mage.abilities.keyword.FirstStrikeAbility;
import mage.abilities.keyword.TrampleAbility;
import mage.constants.AsThoughEffectType;
import mage.constants.Outcome;
import mage.filter.StaticFilters;
import mage.game.Game;
@ -60,19 +63,11 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
}
public boolean hasFirstOrDoubleStrike(Game game) {
for (UUID permId : attackers) {
Permanent attacker = game.getPermanent(permId);
if (attacker != null && hasFirstOrDoubleStrike(attacker)) {
return true;
}
}
for (UUID permId : blockers) {
Permanent blocker = game.getPermanent(permId);
if (blocker != null && hasFirstOrDoubleStrike(blocker)) {
return true;
}
}
return false;
return Stream.concat(attackers.stream(), blockers.stream())
.map(id -> game.getPermanent(id))
.filter(Objects::nonNull)
.anyMatch(this::hasFirstOrDoubleStrike);
}
public UUID getDefenderId() {
@ -124,11 +119,14 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
return;
} else {
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;
unblockedDamage(first, game);
}
if ((attacker.getAbilities().containsKey(DamageAsThoughNotBlockedAbility.getInstance().getId()) &&
player.chooseUse(Outcome.Damage, "Do you wish to assign damage for "
+ attacker.getLogName() + " as though it weren't blocked?", null, game)) ||
game.getContinuousEffects().asThough(attacker.getId(), AsThoughEffectType.DAMAGE_NOT_BLOCKED
, null, attacker.getControllerId(), game) != null) {
// for handling creatures like Thorn Elemental
blocked = false;
unblockedDamage(first, game);
}
if (blockers.size() == 1) {
singleBlockerDamage(player, first, game);

View file

@ -6,8 +6,9 @@ import mage.constants.CardType;
import mage.constants.SubType;
public final class AvatarToken2 extends TokenImpl {
public AvatarToken2() {
super("Angel", "4/4 white Avatar creature token with flying");
super("Avatar", "4/4 white Avatar creature token with flying");
cardType.add(CardType.CREATURE);
color.setWhite(true);
subtype.add(SubType.AVATAR);

View file

@ -0,0 +1,45 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.constants.CardType;
import mage.constants.SubType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public final class BeastToken4 extends TokenImpl {
public BeastToken4() {
this(null, 0);
}
public BeastToken4(String setCode) {
this(setCode, 0);
}
public BeastToken4(String setCode, int tokenType) {
super("Beast", "2/2 green Beast creature token");
setOriginalExpansionSetCode(setCode != null ? setCode : "EXO");
cardType.add(CardType.CREATURE);
color.setGreen(true);
subtype.add(SubType.BEAST);
power = new MageInt(2);
toughness = new MageInt(2);
}
public BeastToken4(final BeastToken4 token) {
super(token);
}
@Override
public BeastToken4 copy() {
return new BeastToken4(this);
}
}

View file

@ -67,6 +67,7 @@ public class Spell extends StackObjImpl implements Card {
private boolean faceDown;
private boolean countered;
private boolean resolving = false;
private UUID commandedBy = null; // for Word of Command
private boolean doneActivatingManaAbilities; // if this is true, the player is no longer allowed to pay the spell costs with activating of mana abilies
@ -121,6 +122,7 @@ public class Spell extends StackObjImpl implements Card {
this.faceDown = spell.faceDown;
this.countered = spell.countered;
this.resolving = spell.resolving;
this.commandedBy = spell.commandedBy;
this.doneActivatingManaAbilities = spell.doneActivatingManaAbilities;
this.targetChanged = spell.targetChanged;
@ -179,6 +181,12 @@ public class Spell extends StackObjImpl implements Card {
return false;
}
this.resolving = true;
if (commandedBy != null && !commandedBy.equals(getControllerId())) {
Player turnController = game.getPlayer(commandedBy);
if (turnController != null) {
turnController.controlPlayersTurn(game, controller.getId());
}
}
if (this.isInstant() || this.isSorcery()) {
int index = 0;
result = false;
@ -1024,4 +1032,12 @@ public class Spell extends StackObjImpl implements Card {
throw new UnsupportedOperationException("Not supported."); //To change body of generated methods, choose Tools | Templates.
}
public void setCommandedBy(UUID playerId) {
this.commandedBy = playerId;
}
public UUID getCommandedBy() {
return commandedBy;
}
}

View file

@ -37,10 +37,11 @@ public class SpellStack extends ArrayDeque<StackObject> {
try {
top = this.peek();
top.resolve(game);
game.resetControlAfterSpellResolve(top.getId());
} finally {
if (top != null) {
if (contains(top)) {
logger.warn("StackObject was still on the stack after resoving" + top.getName());
logger.warn("StackObject was still on the stack after resolving" + top.getName());
this.remove(top, game);
}
}
@ -89,10 +90,10 @@ public class SpellStack extends ArrayDeque<StackObject> {
game.informPlayers(counteredObjectName + " is countered by " + sourceObject.getLogName());
}
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.COUNTERED, objectId, sourceId, stackObject.getControllerId()));
return true;
} else if (!game.isSimulation()) {
game.informPlayers(counteredObjectName + " could not be countered by " + sourceObject.getLogName());
}
return true;
}
return false;
}

View file

@ -35,6 +35,8 @@ public class ManaPool implements Serializable {
private boolean autoPayment; // auto payment from mana pool: true - mode is active
private boolean autoPaymentRestricted; // auto payment from mana pool: true - if auto Payment is on, it will only pay if one kind of mana is in the pool
private ManaType unlockedManaType; // type of mana that was selected to pay manually
private boolean forcedToPay; // for Word of Command
private final List<ManaPoolItem> poolBookmark = new ArrayList<>(); // mana pool bookmark for rollback purposes
private final Set<ManaType> doNotEmptyManaTypes = new HashSet<>();
@ -43,6 +45,7 @@ public class ManaPool implements Serializable {
autoPayment = true;
autoPaymentRestricted = true;
unlockedManaType = null;
forcedToPay = false;
}
public ManaPool(final ManaPool pool) {
@ -53,6 +56,10 @@ public class ManaPool implements Serializable {
this.autoPayment = pool.autoPayment;
this.autoPaymentRestricted = pool.autoPaymentRestricted;
this.unlockedManaType = pool.unlockedManaType;
this.forcedToPay = pool.forcedToPay;
for (ManaPoolItem item : pool.poolBookmark) {
poolBookmark.add(item.copy());
}
this.doNotEmptyManaTypes.addAll(pool.doNotEmptyManaTypes);
}
@ -87,12 +94,12 @@ public class ManaPool implements Serializable {
* @return
*/
public boolean pay(ManaType manaType, Ability ability, Filter filter, Game game, Cost costToPay, Mana usedManaToPay) {
if (!autoPayment && manaType != unlockedManaType) {
if (!isAutoPayment() && manaType != unlockedManaType) {
// if manual payment and the needed mana type was not unlocked, nothing will be paid
return false;
}
ManaType possibleAsThoughtPoolManaType = null;
if (autoPayment && autoPaymentRestricted && !wasManaAddedBeyondStock() && manaType != unlockedManaType) {
if (isAutoPayment() && isAutoPaymentRestricted() && !wasManaAddedBeyondStock() && manaType != unlockedManaType) {
// if automatic restricted payment and there is already mana in the pool
// and the needed mana type was not unlocked, nothing will be paid
if (unlockedManaType != null) {
@ -111,6 +118,7 @@ public class ManaPool implements Serializable {
lockManaType(); // pay only one mana if mana payment is set to manually
return true;
}
for (ManaPoolItem mana : manaItems) {
if (filter != null) {
if (!filter.match(mana.getSourceObject(), game)) {
@ -120,7 +128,7 @@ public class ManaPool implements Serializable {
}
}
}
if (possibleAsThoughtPoolManaType == null && manaType != unlockedManaType && autoPayment && autoPaymentRestricted && mana.count() == mana.getStock()) {
if (possibleAsThoughtPoolManaType == null && manaType != unlockedManaType && isAutoPayment() && isAutoPaymentRestricted() && mana.count() == mana.getStock()) {
// no mana added beyond the stock so don't auto pay this
continue;
}
@ -436,19 +444,19 @@ public class ManaPool implements Serializable {
}
public boolean isAutoPayment() {
return autoPayment;
return autoPayment || forcedToPay;
}
public void setAutoPayment(boolean autoPayment) {
this.autoPayment = autoPayment;
}
public void setAutoPaymentRestricted(boolean autoPaymentRestricted) {
this.autoPaymentRestricted = autoPaymentRestricted;
public boolean isAutoPaymentRestricted() {
return autoPaymentRestricted || forcedToPay;
}
public boolean isAutoPaymentRestricted() {
return autoPaymentRestricted;
public void setAutoPaymentRestricted(boolean autoPaymentRestricted) {
this.autoPaymentRestricted = autoPaymentRestricted;
}
public ManaType getUnlockedManaType() {
@ -490,4 +498,39 @@ public class ManaPool implements Serializable {
return itemsCopy;
}
public void setForcedToPay(boolean forcedToPay) {
this.forcedToPay = forcedToPay;
}
public boolean isForcedToPay() {
return forcedToPay;
}
public UUID getPlayerId() {
return playerId;
}
public void storeMana() {
poolBookmark.clear();
poolBookmark.addAll(getManaItems());
}
public List<ManaPoolItem> getPoolBookmark() {
List<ManaPoolItem> itemsCopy = new ArrayList<>();
for (ManaPoolItem manaItem : poolBookmark) {
itemsCopy.add(manaItem.copy());
}
return itemsCopy;
}
public void restoreMana(List<ManaPoolItem> manaList) {
manaItems.clear();
if (!manaList.isEmpty()) {
List<ManaPoolItem> itemsCopy = new ArrayList<>();
for (ManaPoolItem manaItem : manaList) {
itemsCopy.add(manaItem.copy());
}
manaItems.addAll(itemsCopy);
}
}
}

View file

@ -278,6 +278,8 @@ public interface Player extends MageItem, Copyable<Player> {
*/
void setTurnControlledBy(UUID playerId);
List<UUID> getTurnControllers();
UUID getTurnControlledBy();
/**
@ -305,6 +307,8 @@ public interface Player extends MageItem, Copyable<Player> {
*/
void setGameUnderYourControl(boolean value);
void setGameUnderYourControl(boolean value, boolean fullRestore);
boolean isTestMode();
void setTestMode(boolean value);
@ -856,6 +860,8 @@ public interface Player extends MageItem, Copyable<Player> {
Set<UUID> getUsersAllowedToSeeHandCards();
void setPayManaMode(boolean payManaMode);
boolean isInPayManaMode();
void setMatchPlayer(MatchPlayer matchPlayer);

View file

@ -155,6 +155,7 @@ public abstract class PlayerImpl implements Player, Serializable {
protected boolean isGameUnderControl = true;
protected UUID turnController;
protected List<UUID> turnControllers = new ArrayList<>();
protected Set<UUID> playersUnderYourControl = new HashSet<>();
protected Set<UUID> usersAllowedToSeeHandCards = new HashSet<>();
@ -262,6 +263,8 @@ public abstract class PlayerImpl implements Player, Serializable {
this.isGameUnderControl = player.isGameUnderControl;
this.turnController = player.turnController;
this.turnControllers.clear();
this.turnControllers.addAll(player.turnControllers);
this.passed = player.passed;
this.passedTurn = player.passedTurn;
@ -342,6 +345,8 @@ public abstract class PlayerImpl implements Player, Serializable {
this.isGameUnderControl = player.isGameUnderControl();
this.turnController = player.getTurnControlledBy();
this.turnControllers.clear();
this.turnControllers.addAll(player.getTurnControllers());
this.reachedNextTurnAfterLeaving = player.hasReachedNextTurnAfterLeaving();
this.castSourceIdWithAlternateMana = player.getCastSourceIdWithAlternateMana();
this.castSourceIdManaCosts = player.getCastSourceIdManaCosts();
@ -397,6 +402,7 @@ public abstract class PlayerImpl implements Player, Serializable {
this.turns = 0;
this.isGameUnderControl = true;
this.turnController = this.getId();
this.turnControllers.clear();
this.playersUnderYourControl.clear();
this.passed = false;
@ -523,13 +529,13 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public void controlPlayersTurn(Game game, UUID playerId) {
Player player = game.getPlayer(playerId);
player.setTurnControlledBy(this.getId());
game.informPlayers(getLogName() + " controls the turn of " + player.getLogName());
if (!playerId.equals(this.getId())) {
this.playersUnderYourControl.add(playerId);
Player player = game.getPlayer(playerId);
if (!player.hasLeft() && !player.hasLost()) {
player.setGameUnderYourControl(false);
player.setTurnControlledBy(this.getId());
game.informPlayers(getLogName() + " controls the turn of " + player.getLogName());
}
DelayedTriggeredAbility ability = new AtTheEndOfTurnStepPostDelayedTriggeredAbility(new LoseControlOnOtherPlayersControllerEffect(this.getLogName(), player.getLogName()));
ability.setSourceId(getId());
@ -541,6 +547,12 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public void setTurnControlledBy(UUID playerId) {
this.turnController = playerId;
this.turnControllers.add(playerId);
}
@Override
public List<UUID> getTurnControllers() {
return this.turnControllers;
}
@Override
@ -566,9 +578,27 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public void setGameUnderYourControl(boolean value) {
setGameUnderYourControl(value, true);
}
@Override
public void setGameUnderYourControl(boolean value, boolean fullRestore) {
this.isGameUnderControl = value;
if (isGameUnderControl) {
this.turnController = getId();
if (fullRestore) {
this.turnControllers.clear();
this.turnController = getId();
} else {
if (turnControllers.size() > 0) {
this.turnControllers.remove(turnControllers.size() - 1);
}
if (turnControllers.isEmpty()) {
this.turnController = getId();
} else {
this.turnController = turnControllers.get(turnControllers.size() - 1);
isGameUnderControl = false;
}
}
}
}
@ -983,6 +1013,11 @@ public abstract class PlayerImpl implements Player, Serializable {
return castSourceIdManaCosts;
}
@Override
public void setPayManaMode(boolean payManaMode) {
this.payManaMode = payManaMode;
}
@Override
public boolean isInPayManaMode() {
return payManaMode;
@ -1853,7 +1888,7 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public int gainLife(int amount, Game game, UUID sourceId) {
if (!canGainLife || amount == 0) {
if (!canGainLife || amount <= 0) {
return 0;
}
GameEvent event = new GameEvent(GameEvent.EventType.GAIN_LIFE, playerId, playerId, playerId, amount, false);

View file

@ -2,6 +2,8 @@
package mage.util;
import java.util.UUID;
import java.util.stream.Stream;
import mage.MageObject;
import mage.Mana;
import mage.abilities.Ability;
@ -354,13 +356,8 @@ public final class CardUtil {
}
public static boolean checkNumeric(String s) {
return s.chars().allMatch(Character::isDigit);
for (int i = 0; i < s.length(); i++) {
if (!Character.isDigit(s.charAt(i))) {
return false;
}
}
return true;
}
/**
@ -442,9 +439,9 @@ public final class CardUtil {
public static String getObjectZoneString(String text, MageObject mageObject, Game game) {
int zoneChangeCounter = 0;
if (mageObject instanceof Permanent) {
zoneChangeCounter = ((Permanent) mageObject).getZoneChangeCounter(game);
zoneChangeCounter = mageObject.getZoneChangeCounter(game);
} else if (mageObject instanceof Card) {
zoneChangeCounter = ((Card) mageObject).getZoneChangeCounter(game);
zoneChangeCounter = mageObject.getZoneChangeCounter(game);
}
return getObjectZoneString(text, mageObject.getId(), game, zoneChangeCounter, false);
}

View file

@ -30,13 +30,11 @@ public class AttackedLastTurnWatcher extends Watcher {
public AttackedLastTurnWatcher(final AttackedLastTurnWatcher watcher) {
super(watcher);
for (Entry<UUID, Set<MageObjectReference>> entry : watcher.attackedLastTurnCreatures.entrySet()) {
Set<MageObjectReference> allAttackersCopy = new HashSet<>();
allAttackersCopy.addAll(entry.getValue());
Set<MageObjectReference> allAttackersCopy = new HashSet<>(entry.getValue());
attackedLastTurnCreatures.put(entry.getKey(), allAttackersCopy);
}
for (Entry<UUID, Set<MageObjectReference>> entry : watcher.attackedThisTurnCreatures.entrySet()) {
Set<MageObjectReference> allAttackersCopy = new HashSet<>();
allAttackersCopy.addAll(entry.getValue());
Set<MageObjectReference> allAttackersCopy = new HashSet<>(entry.getValue());
attackedThisTurnCreatures.put(entry.getKey(), allAttackersCopy);
}
}