Merge origin/master

This commit is contained in:
fireshoes 2015-08-19 22:23:46 -05:00
commit d5e4ce9bf8
241 changed files with 9962 additions and 1334 deletions

View file

@ -1,75 +0,0 @@
/*
* 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.costs.common;
import java.util.UUID;
import mage.constants.CardType;
import mage.abilities.Ability;
import mage.abilities.costs.CostImpl;
import mage.filter.FilterPermanent;
import mage.filter.predicate.mageobject.CardTypePredicate;
import mage.game.Game;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class MetalcraftCost extends CostImpl {
private static final FilterPermanent filter = new FilterPermanent("artifact");
static {
filter.add(new CardTypePredicate(CardType.ARTIFACT));
}
public MetalcraftCost() {
this.text = "Activate this ability only if you control three or more artifacts";
}
public MetalcraftCost(final MetalcraftCost cost) {
super(cost);
}
@Override
public MetalcraftCost copy() {
return new MetalcraftCost(this);
}
@Override
public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) {
return game.getBattlefield().contains(filter, controllerId, 3, game);
}
@Override
public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana) {
this.paid = true;
return paid;
}
}

View file

@ -1,16 +1,16 @@
/*
* 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
@ -20,21 +20,20 @@
* 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 java.util.UUID;
import mage.constants.Outcome;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
@ -71,12 +70,11 @@ public class DamageMultiEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) {
if (source.getTargets().size() > 0) {
Target multiTarget = source.getTargets().get(0);
for (UUID target: multiTarget.getTargets()) {
for (UUID target : multiTarget.getTargets()) {
Permanent permanent = game.getPermanent(target);
if (permanent != null) {
permanent.damage(multiTarget.getTargetAmount(target), source.getSourceId(), game, false, true);
}
else {
} else {
Player player = game.getPlayer(target);
if (player != null) {
player.damage(multiTarget.getTargetAmount(target), source.getSourceId(), game, false, true);
@ -92,9 +90,6 @@ public class DamageMultiEffect extends OneShotEffect {
if (staticText != null && !staticText.isEmpty()) {
return staticText;
}
StringBuilder sb = new StringBuilder();
sb.append("{source} deals ").append(amount.toString());
sb.append(" damage divided as you choose among any number of target ").append(mode.getTargets().get(0).getTargetName());
return sb.toString();
return "{source} deals " + amount.toString() + " damage divided as you choose among any number of target " + mode.getTargets().get(0).getTargetName();
}
}

View file

@ -53,6 +53,7 @@ public class DoIfCostPaid extends OneShotEffect {
effectText = effectText.substring(0, effectText.length() - 1);
}
message = getCostText() + " and " + effectText + "?";
message = Character.toUpperCase(message.charAt(0)) + message.substring(1);
} else {
message = chooseUseText;
}

View file

@ -0,0 +1,99 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package mage.abilities.effects.common;
import java.util.UUID;
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.constants.TargetController;
import static mage.constants.TargetController.ANY;
import static mage.constants.TargetController.OPPONENT;
import mage.game.Game;
import mage.players.Player;
/**
*
* @author LevelX2
*/
public class SetPlayerLifeAllEffect extends OneShotEffect {
private TargetController targetController;
protected DynamicValue amount;
public SetPlayerLifeAllEffect(int amount) {
this(amount, TargetController.ANY);
}
public SetPlayerLifeAllEffect(DynamicValue amount) {
this(amount, TargetController.ANY);
}
public SetPlayerLifeAllEffect(int amount, TargetController targetController) {
this(new StaticValue(amount), targetController);
}
public SetPlayerLifeAllEffect(DynamicValue amount, TargetController targetController) {
super(Outcome.DrawCard);
this.amount = amount;
this.targetController = targetController;
staticText = setText();
}
public SetPlayerLifeAllEffect(final SetPlayerLifeAllEffect effect) {
super(effect);
this.amount = effect.amount;
this.targetController = effect.targetController;
}
@Override
public SetPlayerLifeAllEffect copy() {
return new SetPlayerLifeAllEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player sourcePlayer = game.getPlayer(source.getControllerId());
switch (targetController) {
case ANY:
for (UUID playerId : sourcePlayer.getInRange()) {
Player player = game.getPlayer(playerId);
if (player != null) {
player.setLife(amount.calculate(game, source, this), game);
}
}
break;
case OPPONENT:
for (UUID playerId : game.getOpponents(sourcePlayer.getId())) {
Player player = game.getPlayer(playerId);
if (player != null) {
player.setLife(amount.calculate(game, source, this), game);
}
}
break;
}
return true;
}
private String setText() {
StringBuilder sb = new StringBuilder("Each ");
switch (targetController) {
case ANY:
sb.append("player");
break;
case OPPONENT:
sb.append("opponent");
break;
default:
throw new UnsupportedOperationException("Not supported value for targetController");
}
sb.append(" 's life total becomes ");
sb.append(amount.toString());
return sb.toString();
}
}

View file

@ -1,39 +1,38 @@
/*
* 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.
*/
* 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.abilities.Ability;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.game.Game;
import mage.players.Player;
@ -44,7 +43,7 @@ public class LifeTotalCantChangeControllerEffect extends ContinuousEffectImpl {
public LifeTotalCantChangeControllerEffect(Duration duration) {
super(duration, Layer.PlayerEffects, SubLayer.NA, Outcome.Benefit);
setText();
staticText = "Your life total can't change. <i>(You can't gain or lose life. You can't pay any amount of life except 0.)</i>";
}
public LifeTotalCantChangeControllerEffect(final LifeTotalCantChangeControllerEffect effect) {
@ -66,8 +65,4 @@ public class LifeTotalCantChangeControllerEffect extends ContinuousEffectImpl {
return true;
}
private void setText() {
staticText = "Your life total can't change. (You can't gain or lose life. You can't pay any amount of life except 0.)";
}
}
}

View file

@ -139,7 +139,7 @@ class HideawayExileEffect extends OneShotEffect {
class HideawayLookAtFaceDownCardEffect extends AsThoughEffectImpl {
public HideawayLookAtFaceDownCardEffect() {
super(AsThoughEffectType.REVEAL_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit);
super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit);
staticText = "You may look at cards exiled with {this}";
}

View file

@ -237,7 +237,7 @@ public class MorphAbility extends StaticAbility implements AlternativeSourceCost
if (player != null) {
this.resetMorph();
if (alternateCosts.canPay(ability, sourceId, controllerId, game)) {
if (player.chooseUse(Outcome.Benefit, new StringBuilder("Cast this card as a 2/2 face-down creature for ").append(getCosts().getText()).append(" ?").toString(), ability, game)) {
if (player.chooseUse(Outcome.Benefit, "Cast this card as a 2/2 face-down creature for " + getCosts().getText() + " ?", ability, game)) {
activateMorph(game);
// change mana costs
ability.getManaCostsToPay().clear();

View file

@ -1,50 +1,45 @@
/*
* 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.
*/
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.abilities.keyword;
import mage.abilities.Ability;
import mage.abilities.common.DiesTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldEffect;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.counters.CounterType;
import mage.counters.Counters;
import mage.game.Game;
import mage.game.events.EntersTheBattlefieldEvent;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
public class PersistAbility extends DiesTriggeredAbility {
public PersistAbility() {
@ -73,7 +68,6 @@ public class PersistAbility extends DiesTriggeredAbility {
if (permanent.getCounters().getCount(CounterType.M1M1) == 0) {
FixedTarget fixedTarget = new FixedTarget(permanent.getId());
fixedTarget.init(game, this);
game.getState().setValue("persist" + getSourceId().toString(), fixedTarget);
return true;
}
}
@ -104,58 +98,9 @@ class PersistEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
game.addEffect(new PersistReplacementEffect(), source);
Counters countersToAdd = new Counters();
countersToAdd.addCounter(CounterType.M1M1.createInstance());
game.setEnterWithCounters(source.getSourceId(), countersToAdd);
return true;
}
}
class PersistReplacementEffect extends ReplacementEffectImpl {
PersistReplacementEffect() {
super(Duration.Custom, Outcome.UnboostCreature, false);
selfScope = true;
staticText = "return it to the battlefield under its owner's control with a -1/-1 counter on it";
}
PersistReplacementEffect(final PersistReplacementEffect effect) {
super(effect);
}
@Override
public PersistReplacementEffect copy() {
return new PersistReplacementEffect(this);
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Permanent permanent = game.getPermanent(event.getTargetId());
if (permanent != null) {
permanent.addCounters(CounterType.M1M1.createInstance(), game);
}
discard();
return false;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (event.getTargetId().equals(source.getSourceId())) {
Object fixedTarget = game.getState().getValue("persist" + source.getSourceId().toString());
if (fixedTarget instanceof FixedTarget && ((FixedTarget) fixedTarget).getTarget().equals(source.getSourceId()) &&
((FixedTarget) fixedTarget).getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(source.getSourceId())) {
return true;
}
}
return false;
}
@Override
public boolean apply(Game game, Ability source) {
return false;
}
}

View file

@ -57,8 +57,8 @@ public class TransmuteAbility extends SimpleActivatedAbility {
@Override
public String getRule() {
return new StringBuilder("Transmute ").append(this.getManaCosts().getText())
.append(" (").append(this.getManaCosts().getText())
.append(", Discard this card: Search your library for a card with the same converted mana cost as this card, reveal it, and put it into your hand. Then shuffle your library. Transmute only as a sorcery.)").toString();
.append(" <i>(").append(this.getManaCosts().getText())
.append(", Discard this card: Search your library for a card with the same converted mana cost as this card, reveal it, and put it into your hand. Then shuffle your library. Transmute only as a sorcery.)</i>").toString();
}
}

View file

@ -1,16 +1,13 @@
package mage.abilities.keyword;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.common.DiesTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldEffect;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.counters.Counters;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
@ -19,7 +16,7 @@ import mage.game.permanent.Permanent;
* @author Loki
*/
public class UndyingAbility extends DiesTriggeredAbility {
public UndyingAbility() {
super(new UndyingEffect());
this.addEffect(new ReturnSourceFromGraveyardToBattlefieldEffect(false, true));
@ -39,7 +36,6 @@ public class UndyingAbility extends DiesTriggeredAbility {
if (super.checkTrigger(event, game)) {
Permanent permanent = (Permanent) game.getLastKnownInformation(event.getTargetId(), Zone.BATTLEFIELD);
if (!permanent.getCounters().containsKey(CounterType.P1P1) || permanent.getCounters().getCount(CounterType.P1P1) == 0) {
game.getState().setValue("undying" + getSourceId(),permanent.getId());
return true;
}
}
@ -70,58 +66,9 @@ class UndyingEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
game.addEffect(new UndyingReplacementEffect(), source);
return false;
}
}
class UndyingReplacementEffect extends ReplacementEffectImpl {
UndyingReplacementEffect() {
super(Duration.OneUse, Outcome.BoostCreature, false);
selfScope = true;
staticText = "return it to the battlefield under its owner's control with a +1/+1 counter on it";
}
UndyingReplacementEffect(final UndyingReplacementEffect effect) {
super(effect);
}
@Override
public UndyingReplacementEffect copy() {
return new UndyingReplacementEffect(this);
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Permanent permanent = game.getPermanent(event.getTargetId());
if (permanent != null) {
game.getState().setValue("undying" + source.getSourceId(), null);
permanent.addCounters(CounterType.P1P1.createInstance(), game);
}
used = true;
return false;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (event.getTargetId().equals(source.getSourceId())) {
// Check if undying condition is true
UUID targetId = (UUID) game.getState().getValue("undying" + source.getSourceId());
if (targetId != null && targetId.equals(source.getSourceId())) {
return true;
}
}
return false;
}
@Override
public boolean apply(Game game, Ability source) {
return false;
Counters countersToAdd = new Counters();
countersToAdd.addCounter(CounterType.P1P1.createInstance());
game.setEnterWithCounters(source.getSourceId(), countersToAdd);
return true;
}
}

View file

@ -31,9 +31,14 @@ public class VanishingUpkeepAbility extends BeginningOfUpkeepTriggeredAbility {
@Override
public String getRule() {
return "Vanishing " + vanishingAmount
if(vanishingAmount > 0) {
return "Vanishing " + vanishingAmount
+ " <i>(This permanent enters the battlefield with " + CardUtil.numberToText(vanishingAmount)
+ " time counters on it. At the beginning of your upkeep, remove a time counter from it. When the last is removed, sacrifice it.)<i>";
+ " time counters on it. At the beginning of your upkeep, remove a time counter from it. When the last is removed, sacrifice it.)</i>";
}
else {
return "Vanishing <i>(At the beginning of your upkeep, remove a time counter from this permanent. When the last is removed, sacrifice it.)</i>";
}
}
}

View file

@ -1,7 +1,8 @@
package mage.actions;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.actions.impl.MageAction;
import mage.actions.score.ArtificialScoringSystem;
import mage.cards.Card;
@ -9,9 +10,6 @@ import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.Player;
import java.util.List;
import java.util.UUID;
import mage.util.CardUtil;
/**
@ -39,7 +37,7 @@ public class MageDrawAction extends MageAction {
* Draw and set action score.
*
* @param game Game context.
* @return
* @return
*/
@Override
public int doAction(Game game) {
@ -70,8 +68,8 @@ public class MageDrawAction extends MageAction {
}
/**
* Draw a card if possible (there is no replacement effect that prevent us from drawing).
* Fire event about card drawn.
* Draw a card if possible (there is no replacement effect that prevent us
* from drawing). Fire event about card drawn.
*
* @param game
* @return

View file

@ -602,6 +602,8 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
PermanentCard permanent = new PermanentCard(this, event.getPlayerId(), game);
// make sure the controller of all continuous effects of this card are switched to the current controller
game.getContinuousEffects().setController(objectId, event.getPlayerId());
// check if there are counters to add to the permanent (e.g. from non replacement effects like Persist)
checkForCountersToAdd(permanent, game);
game.addPermanent(permanent);
setZone(Zone.BATTLEFIELD, game);
game.setScopeRelevant(true);
@ -621,6 +623,16 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
return false;
}
private void checkForCountersToAdd(PermanentCard permanent, Game game) {
Counters countersToAdd = game.getEnterWithCounters(permanent.getId());
if (countersToAdd != null) {
for (Counter counter : countersToAdd.values()) {
permanent.addCounters(counter, game);
}
game.setEnterWithCounters(permanent.getId(), null);
}
}
@Override
public void setFaceDown(boolean value, Game game) {
game.getState().getCardState(objectId).setFaceDown(value);

View file

@ -63,7 +63,7 @@ public enum CardRepository {
// raise this if db structure was changed
private static final long CARD_DB_VERSION = 41;
// raise this if new cards were added to the server
private static final long CARD_CONTENT_VERSION = 32;
private static final long CARD_CONTENT_VERSION = 33;
private final Random random = new Random();
private Dao<CardInfo, Object> cardDao;
@ -250,7 +250,7 @@ public enum CardRepository {
}
return subtypes;
}
public Set<String> getLandTypes() {
TreeSet<String> subtypes = new TreeSet<>();
try {

View file

@ -17,7 +17,7 @@ public enum AsThoughEffectType {
DAMAGE,
HEXPROOF,
PAY,
REVEAL_FACE_DOWN,
LOOK_AT_FACE_DOWN,
SPEND_ANY_MANA,
TARGET
}

View file

@ -33,7 +33,7 @@ package mage.counters;
* @author nantuko
*/
public enum CounterType {
AGE("age"),
AIM("aim"),
ARROWHEAD("arrowhead"),
@ -57,6 +57,7 @@ public enum CounterType {
FUSE("fuse"),
GOLD("gold"),
HATCHLING("hatchling"),
HEALING("healing"),
HOOFPRINT("hoofprint"),
ICE("ice"),
JAVELIN("javelin"),
@ -117,7 +118,7 @@ public enum CounterType {
}
/**
* Create instance of counter type with defined amount of counters of the
* Create instance of counter type with defined amount of counters of the
* given type.
*
* @param amount amount of counters of the given type.

View file

@ -54,6 +54,7 @@ import mage.constants.MultiplayerAttackOption;
import mage.constants.PlayerAction;
import mage.constants.RangeOfInfluence;
import mage.constants.Zone;
import mage.counters.Counters;
import mage.game.combat.Combat;
import mage.game.command.Commander;
import mage.game.command.Emblem;
@ -436,4 +437,8 @@ public interface Game extends MageItem, Serializable {
void rollbackTurns(int turnsToRollback);
boolean executingRollback();
void setEnterWithCounters(UUID sourceId, Counters counters);
Counters getEnterWithCounters(UUID sourceId);
}

View file

@ -78,6 +78,7 @@ import mage.constants.PlayerAction;
import mage.constants.RangeOfInfluence;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.counters.Counters;
import mage.filter.Filter;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledCreaturePermanent;
@ -207,6 +208,9 @@ public abstract class GameImpl implements Game, Serializable {
private final int startLife;
protected PlayerList playerList;
// used to set the counters a permanent adds the battlefield (if no replacement effect is used e.g. Persist)
protected Map<UUID, Counters> enterWithCounters = new HashMap<>();
public GameImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) {
this.id = UUID.randomUUID();
this.range = range;
@ -246,6 +250,7 @@ public abstract class GameImpl implements Game, Serializable {
this.priorityTime = game.priorityTime;
this.saveGame = game.saveGame;
this.startLife = game.startLife;
this.enterWithCounters.putAll(game.enterWithCounters);
}
@Override
@ -2652,6 +2657,7 @@ public abstract class GameImpl implements Game, Serializable {
for (Player playerObject : getPlayers().values()) {
if (playerObject.isHuman() && playerObject.isInGame()) {
playerObject.abort();
playerObject.resetPlayerPassedActions();
}
}
fireUpdatePlayersEvent();
@ -2665,4 +2671,20 @@ public abstract class GameImpl implements Game, Serializable {
return executingRollback;
}
@Override
public void setEnterWithCounters(UUID sourceId, Counters counters) {
if (counters == null) {
if (enterWithCounters.containsKey(sourceId)) {
enterWithCounters.remove(sourceId);
}
return;
}
enterWithCounters.put(sourceId, counters);
}
@Override
public Counters getEnterWithCounters(UUID sourceId) {
return enterWithCounters.get(sourceId);
}
}

View file

@ -179,6 +179,8 @@ public interface Player extends MageItem, Copyable<Player> {
void resetPassed();
void resetPlayerPassedActions();
boolean getPassedTurn();
boolean getPassedUntilEndOfTurn();
@ -564,8 +566,9 @@ public interface Player extends MageItem, Copyable<Player> {
*
* @param card
* @param game
* @return player looked at the card
*/
void revealFaceDownCard(Card card, Game game);
boolean lookAtFaceDownCard(Card card, Game game);
/**
* Set seconds left to play the game.
@ -620,13 +623,12 @@ public interface Player extends MageItem, Copyable<Player> {
*/
boolean moveCards(Cards cards, Zone fromZone, Zone toZone, Ability source, Game game);
// boolean moveCards(Cards cards, Zone fromZone, Zone toZone, Ability source, Game game, boolean withName);
boolean moveCards(Card card, Zone fromZone, Zone toZone, Ability source, Game game);
// boolean moveCards(Card card, Zone fromZone, Zone toZone, Ability source, Game game, boolean withName);
boolean moveCards(Set<Card> cards, Zone fromZone, Zone toZone, Ability source, Game game);
// boolean moveCards(Set<Card> cards, Zone fromZone, Zone toZone, Ability source, Game game, boolean withName);
boolean moveCardsToExile(Card card, Ability source, Game game, boolean withName, UUID exileId, String exileZoneName);
boolean moveCardsToExile(Set<Card> cards, Ability source, Game game, boolean withName, UUID exileId, String exileZoneName);
/**
@ -683,9 +685,9 @@ public interface Player extends MageItem, Copyable<Player> {
* @param source
* @param game
* @param fromZone if null, this info isn't postet
* @return
* @return Set<Cards> that were successful moved to graveyard
*/
boolean moveCardsToGraveyardWithInfo(Set<Card> cards, Ability source, Game game, Zone fromZone);
Set<Card> moveCardsToGraveyardWithInfo(Set<Card> cards, Ability source, Game game, Zone fromZone);
/**
* Uses card.moveToZone and posts a inform message about moving the card to

View file

@ -34,6 +34,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@ -330,9 +331,21 @@ public abstract class PlayerImpl implements Player, Serializable {
this.name = player.getName();
this.human = player.isHuman();
this.life = player.getLife();
this.wins = player.hasWon();
this.loses = player.hasLost();
// Don't restore more global states. If restored they are probably cause for unintended draws (https://github.com/magefree/mage/issues/1205).
// this.wins = player.hasWon();
// this.loses = player.hasLost();
// this.left = player.hasLeft();
// this.quit = player.hasQuit();
// Makes no sense to restore
// this.passed = player.isPassed();
// this.priorityTimeLeft = player.getPriorityTimeLeft();
// this.idleTimeout = player.hasIdleTimeout();
// this.timerTimeout = player.hasTimerTimeout();
// can't change so no need to restore
// this.isTestMode = player.isTestMode();
// This is meta data and should'nt be restored by rollback
// this.userData = player.getUserData();
this.library = player.getLibrary().copy();
this.sideboard = player.getSideboard().copy();
this.hand = player.getHand().copy();
@ -349,10 +362,6 @@ public abstract class PlayerImpl implements Player, Serializable {
this.manaPool = player.getManaPool().copy();
this.turns = player.getTurns();
this.left = player.hasLeft();
this.quit = player.hasQuit();
this.timerTimeout = player.hasTimerTimeout();
this.idleTimeout = player.hasIdleTimeout();
this.range = player.getRange();
this.canGainLife = player.isCanGainLife();
this.canLoseLife = player.isCanLoseLife();
@ -361,7 +370,6 @@ public abstract class PlayerImpl implements Player, Serializable {
this.inRange.clear();
this.inRange.addAll(player.getInRange());
this.userData = player.getUserData();
this.canPayLifeCost = player.canPayLifeCost();
this.canPaySacrificeCost = player.canPaySacrificeCost();
this.loseByZeroOrLessLife = player.canLoseByZeroOrLessLife();
@ -371,12 +379,9 @@ public abstract class PlayerImpl implements Player, Serializable {
this.topCardRevealed = player.isTopCardRevealed();
this.playersUnderYourControl.clear();
this.playersUnderYourControl.addAll(player.getPlayersUnderYourControl());
this.isTestMode = player.isTestMode();
this.isGameUnderControl = player.isGameUnderControl();
this.turnController = player.getTurnControlledBy();
this.passed = player.isPassed();
this.priorityTimeLeft = player.getPriorityTimeLeft();
this.reachedNextTurnAfterLeaving = player.hasReachedNextTurnAfterLeaving();
this.castSourceIdWithAlternateMana = player.getCastSourceIdWithAlternateMana();
this.castSourceIdManaCosts = player.getCastSourceIdManaCosts();
@ -1187,8 +1192,9 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public LinkedHashMap<UUID, ActivatedAbility> getUseableActivatedAbilities(MageObject object, Zone zone, Game game) {
LinkedHashMap<UUID, ActivatedAbility> useable = new LinkedHashMap<>();
if (!(object instanceof Permanent) || ((Permanent) object).canUseActivatedAbilities(game)) {
for (Ability ability : object.getAbilities()) {
boolean canUse = !(object instanceof Permanent) || ((Permanent) object).canUseActivatedAbilities(game);
for (Ability ability : object.getAbilities()) {
if (canUse || ability.getAbilityType().equals(AbilityType.SPECIAL_ACTION)) {
if (ability.getZone().match(zone)) {
if (ability instanceof ActivatedAbility) {
if (((ActivatedAbility) ability).canActivate(playerId, game)) {
@ -1205,16 +1211,20 @@ public abstract class PlayerImpl implements Player, Serializable {
}
}
}
if (zone != Zone.HAND) {
if (Zone.GRAVEYARD.equals(zone) && canPlayCardsFromGraveyard()) {
for (ActivatedAbility ability : object.getAbilities().getPlayableAbilities(Zone.HAND)) {
}
if (zone != Zone.HAND) {
if (Zone.GRAVEYARD.equals(zone) && canPlayCardsFromGraveyard()) {
for (ActivatedAbility ability : object.getAbilities().getPlayableAbilities(Zone.HAND)) {
if (canUse || ability.getAbilityType().equals(AbilityType.SPECIAL_ACTION)) {
if (ability.canActivate(playerId, game)) {
useable.put(ability.getId(), ability);
}
}
}
if (zone != Zone.BATTLEFIELD && game.getContinuousEffects().asThough(object.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, this.getId(), game)) {
for (Ability ability : object.getAbilities()) {
}
if (zone != Zone.BATTLEFIELD && game.getContinuousEffects().asThough(object.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, this.getId(), game)) {
for (Ability ability : object.getAbilities()) {
if (canUse || ability.getAbilityType().equals(AbilityType.SPECIAL_ACTION)) {
ability.setControllerId(this.getId());
if (ability instanceof ActivatedAbility && ability.getZone().match(Zone.HAND)
&& ((ActivatedAbility) ability).canActivate(playerId, game)) {
@ -1223,8 +1233,9 @@ public abstract class PlayerImpl implements Player, Serializable {
}
}
}
getOtherUseableActivatedAbilities(object, zone, game, useable);
}
getOtherUseableActivatedAbilities(object, zone, game, useable);
return useable;
}
@ -1232,49 +1243,52 @@ public abstract class PlayerImpl implements Player, Serializable {
private void getOtherUseableActivatedAbilities(MageObject object, Zone zone, Game game, Map<UUID, ActivatedAbility> useable) {
Abilities<ActivatedAbility> otherAbilities = game.getState().getActivatedOtherAbilities(object.getId(), zone);
if (otherAbilities != null) {
boolean canUse = !(object instanceof Permanent) || ((Permanent) object).canUseActivatedAbilities(game);
for (ActivatedAbility ability : otherAbilities) {
Card card = game.getCard(ability.getSourceId());
if (card.isSplitCard() && ability instanceof FlashbackAbility) {
FlashbackAbility flashbackAbility;
// Left Half
if (card.getCardType().contains(CardType.INSTANT)) {
flashbackAbility = new FlashbackAbility(((SplitCard) card).getLeftHalfCard().getManaCost(), TimingRule.INSTANT);
} else {
flashbackAbility = new FlashbackAbility(((SplitCard) card).getLeftHalfCard().getManaCost(), TimingRule.SORCERY);
}
flashbackAbility.setSourceId(card.getId());
flashbackAbility.setControllerId(card.getOwnerId());
flashbackAbility.setSpellAbilityType(SpellAbilityType.SPLIT_LEFT);
flashbackAbility.setAbilityName(((SplitCard) card).getLeftHalfCard().getName());
if (flashbackAbility.canActivate(playerId, game)) {
useable.put(flashbackAbility.getId(), flashbackAbility);
}
// Right Half
if (card.getCardType().contains(CardType.INSTANT)) {
flashbackAbility = new FlashbackAbility(((SplitCard) card).getRightHalfCard().getManaCost(), TimingRule.INSTANT);
} else {
flashbackAbility = new FlashbackAbility(((SplitCard) card).getRightHalfCard().getManaCost(), TimingRule.SORCERY);
}
flashbackAbility.setSourceId(card.getId());
flashbackAbility.setControllerId(card.getOwnerId());
flashbackAbility.setSpellAbilityType(SpellAbilityType.SPLIT_RIGHT);
flashbackAbility.setAbilityName(((SplitCard) card).getRightHalfCard().getName());
if (flashbackAbility.canActivate(playerId, game)) {
useable.put(flashbackAbility.getId(), flashbackAbility);
}
if (canUse || ability.getAbilityType().equals(AbilityType.SPECIAL_ACTION)) {
Card card = game.getCard(ability.getSourceId());
if (card.isSplitCard() && ability instanceof FlashbackAbility) {
FlashbackAbility flashbackAbility;
// Left Half
if (card.getCardType().contains(CardType.INSTANT)) {
flashbackAbility = new FlashbackAbility(((SplitCard) card).getLeftHalfCard().getManaCost(), TimingRule.INSTANT);
} else {
flashbackAbility = new FlashbackAbility(((SplitCard) card).getLeftHalfCard().getManaCost(), TimingRule.SORCERY);
}
flashbackAbility.setSourceId(card.getId());
flashbackAbility.setControllerId(card.getOwnerId());
flashbackAbility.setSpellAbilityType(SpellAbilityType.SPLIT_LEFT);
flashbackAbility.setAbilityName(((SplitCard) card).getLeftHalfCard().getName());
if (flashbackAbility.canActivate(playerId, game)) {
useable.put(flashbackAbility.getId(), flashbackAbility);
}
// Right Half
if (card.getCardType().contains(CardType.INSTANT)) {
flashbackAbility = new FlashbackAbility(((SplitCard) card).getRightHalfCard().getManaCost(), TimingRule.INSTANT);
} else {
flashbackAbility = new FlashbackAbility(((SplitCard) card).getRightHalfCard().getManaCost(), TimingRule.SORCERY);
}
flashbackAbility.setSourceId(card.getId());
flashbackAbility.setControllerId(card.getOwnerId());
flashbackAbility.setSpellAbilityType(SpellAbilityType.SPLIT_RIGHT);
flashbackAbility.setAbilityName(((SplitCard) card).getRightHalfCard().getName());
if (flashbackAbility.canActivate(playerId, game)) {
useable.put(flashbackAbility.getId(), flashbackAbility);
}
} else {
useable.put(ability.getId(), ability);
} else {
useable.put(ability.getId(), ability);
}
}
}
}
}
protected LinkedHashMap<UUID, ManaAbility> getUseableManaAbilities(MageObject object, Zone zone, Game game) {
LinkedHashMap<UUID, ManaAbility> useable = new LinkedHashMap<>();
if (!(object instanceof Permanent) || ((Permanent) object).canUseActivatedAbilities(game)) {
for (ManaAbility ability : object.getAbilities().getManaAbilities(zone)) {
boolean canUse = !(object instanceof Permanent) || ((Permanent) object).canUseActivatedAbilities(game);
for (ManaAbility ability : object.getAbilities().getManaAbilities(zone)) {
if (canUse || ability.getAbilityType().equals(AbilityType.SPECIAL_ACTION)) {
if (ability.canActivate(playerId, game)) {
useable.put(ability.getId(), ability);
}
@ -1823,6 +1837,15 @@ public abstract class PlayerImpl implements Player, Serializable {
this.passed = this.loses || this.hasLeft();
}
@Override
public void resetPlayerPassedActions() {
this.passedAllTurns = false;
this.passedTurn = false;
this.passedUntilEndOfTurn = false;
this.passedUntilNextMain = false;
this.passedUntilStackResolved = false;
}
@Override
public void quit(Game game) {
quit = true;
@ -2817,11 +2840,15 @@ public abstract class PlayerImpl implements Player, Serializable {
}
@Override
public void revealFaceDownCard(Card card, Game game) {
if (game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.REVEAL_FACE_DOWN, this.getId(), game)) {
Cards cards = new CardsImpl(card);
this.revealCards(getName(), cards, game);
public boolean lookAtFaceDownCard(Card card, Game game) {
if (game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.LOOK_AT_FACE_DOWN, this.getId(), game)) {
if (chooseUse(Outcome.Benefit, "Look at that card?", null, game)) {
Cards cards = new CardsImpl(card);
this.lookAtCards(getName(), cards, game);
return true;
}
}
return false;
}
@Override
@ -2908,45 +2935,59 @@ public abstract class PlayerImpl implements Player, Serializable {
if (cards.isEmpty()) {
return true;
}
game.fireEvent(new ZoneChangeGroupEvent(cards, source == null ? null : source.getSourceId(), this.getId(), fromZone, toZone));
Set<Card> successfulMovedCards = new LinkedHashSet<>();
switch (toZone) {
case EXILED:
boolean result = false;
for (Card card : cards) {
fromZone = game.getState().getZone(card.getId());
boolean withName = (fromZone.equals(Zone.BATTLEFIELD) || fromZone.equals(Zone.STACK)) || !card.isFaceDown(game);
result |= moveCardToExileWithInfo(card, null, "", source == null ? null : source.getSourceId(), game, fromZone, withName);
if (moveCardToExileWithInfo(card, null, "", source == null ? null : source.getSourceId(), game, fromZone, withName)) {
successfulMovedCards.add(card);
}
}
return result;
break;
case GRAVEYARD:
return moveCardsToGraveyardWithInfo(cards, source, game, fromZone);
successfulMovedCards = moveCardsToGraveyardWithInfo(cards, source, game, fromZone);
break;
case HAND:
result = false;
for (Card card : cards) {
fromZone = game.getState().getZone(card.getId());
boolean hideCard = fromZone.equals(Zone.LIBRARY)
|| (card.isFaceDown(game) && !fromZone.equals(Zone.STACK) && !fromZone.equals(Zone.BATTLEFIELD));
result |= moveCardToHandWithInfo(card, source == null ? null : source.getSourceId(), game, !hideCard);
if (moveCardToHandWithInfo(card, source == null ? null : source.getSourceId(), game, !hideCard)) {
successfulMovedCards.add(card);
}
}
return result;
break;
case BATTLEFIELD:
result = false;
for (Card card : cards) {
fromZone = game.getState().getZone(card.getId());
result |= putOntoBattlefieldWithInfo(card, game, fromZone, source == null ? null : source.getSourceId(), false, !card.isFaceDown(game));
if (putOntoBattlefieldWithInfo(card, game, fromZone, source == null ? null : source.getSourceId(), false, !card.isFaceDown(game))) {
successfulMovedCards.add(card);
}
}
return result;
break;
case LIBRARY:
result = false;
for (Card card : cards) {
fromZone = game.getState().getZone(card.getId());
boolean withName = fromZone.equals(Zone.BATTLEFIELD) || !card.isFaceDown(game);
result |= moveCardToLibraryWithInfo(card, source == null ? null : source.getSourceId(), game, fromZone, true, withName);
if (moveCardToLibraryWithInfo(card, source == null ? null : source.getSourceId(), game, fromZone, true, withName)) {
successfulMovedCards.add(card);
}
}
return result;
break;
default:
throw new UnsupportedOperationException("to Zone not supported yet");
}
game.fireEvent(new ZoneChangeGroupEvent(successfulMovedCards, source == null ? null : source.getSourceId(), this.getId(), fromZone, toZone));
return successfulMovedCards.size() > 0;
}
@Override
public boolean moveCardsToExile(Card card, Ability source, Game game, boolean withName, UUID exileId, String exileZoneName) {
Set<Card> cards = new HashSet<>();
cards.add(card);
return moveCardsToExile(cards, source, game, withName, exileId, exileZoneName);
}
@Override
@ -2992,9 +3033,9 @@ public abstract class PlayerImpl implements Player, Serializable {
}
@Override
public boolean moveCardsToGraveyardWithInfo(Set<Card> allCards, Ability source, Game game, Zone fromZone) {
boolean result = true;
public Set<Card> moveCardsToGraveyardWithInfo(Set<Card> allCards, Ability source, Game game, Zone fromZone) {
UUID sourceId = source == null ? null : source.getSourceId();
Set<Card> movedCards = new LinkedHashSet<>();
while (!allCards.isEmpty()) {
// identify cards from one owner
Cards cards = new CardsImpl();
@ -3034,21 +3075,28 @@ public abstract class PlayerImpl implements Player, Serializable {
cards.remove(targetObjectId);
if (card != null) {
fromZone = game.getState().getZone(card.getId());
result &= choosingPlayer.moveCardToGraveyardWithInfo(card, sourceId, game, fromZone);
if (choosingPlayer.moveCardToGraveyardWithInfo(card, sourceId, game, fromZone)) {
movedCards.add(card);
}
}
target.clearChosen();
}
if (cards.size() == 1) {
result &= choosingPlayer.moveCardToGraveyardWithInfo(cards.getCards(game).iterator().next(), sourceId, game, fromZone);
Card card = cards.getCards(game).iterator().next();
if (card != null && choosingPlayer.moveCardToGraveyardWithInfo(card, sourceId, game, fromZone)) {
movedCards.add(card);
}
}
} else {
for (Card card : cards.getCards(game)) {
result &= choosingPlayer.moveCardToGraveyardWithInfo(card, sourceId, game, fromZone);
if (choosingPlayer.moveCardToGraveyardWithInfo(card, sourceId, game, fromZone)) {
movedCards.add(card);
}
}
}
}
}
return result;
return movedCards;
}
@Override

View file

@ -25,7 +25,6 @@
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.util;
import java.util.Arrays;
@ -58,7 +57,6 @@ import mage.game.permanent.token.Token;
import mage.game.stack.Spell;
import mage.util.functions.CopyTokenFunction;
/**
* @author nantuko
*/
@ -71,16 +69,16 @@ public class CardUtil {
private static final String regexWhite = ".*\\x7b.{0,2}W.{0,2}\\x7d.*";
private static final String SOURCE_EXILE_ZONE_TEXT = "SourceExileZone";
static String numberStrings[] = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
"ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "ninteen", "twenty"};
public static final String[] NON_CHANGELING_SUBTYPES_VALUES = new String[] { "Mountain", "Forest", "Plains", "Swamp", "Island",
"Aura", "Curse", "Shrine",
"Equipment", "Fortification", "Contraption",
"Trap", "Arcane"};
public static final Set<String> NON_CREATURE_SUBTYPES = new HashSet<>(Arrays.asList(NON_CHANGELING_SUBTYPES_VALUES));
static String numberStrings[] = {"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
"ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "ninteen", "twenty"};
public static final String[] NON_CHANGELING_SUBTYPES_VALUES = new String[]{"Mountain", "Forest", "Plains", "Swamp", "Island",
"Aura", "Curse", "Shrine",
"Equipment", "Fortification", "Contraption",
"Trap", "Arcane"};
public static final Set<String> NON_CREATURE_SUBTYPES = new HashSet<>(Arrays.asList(NON_CHANGELING_SUBTYPES_VALUES));
/**
* Checks whether two cards share card types.
*
@ -102,6 +100,7 @@ public class CardUtil {
return false;
}
/**
* Checks whether two cards share card subtypes.
*
@ -116,10 +115,10 @@ public class CardUtil {
}
if (card1.getCardType().contains(CardType.CREATURE) && card2.getCardType().contains(CardType.CREATURE)) {
if (card1.getAbilities().contains(ChangelingAbility.getInstance()) ||
card1.getSubtype().contains(ChangelingAbility.ALL_CREATURE_TYPE) ||
card2.getAbilities().contains(ChangelingAbility.getInstance()) ||
card2.getSubtype().contains(ChangelingAbility.ALL_CREATURE_TYPE)) {
if (card1.getAbilities().contains(ChangelingAbility.getInstance())
|| card1.getSubtype().contains(ChangelingAbility.ALL_CREATURE_TYPE)
|| card2.getAbilities().contains(ChangelingAbility.getInstance())
|| card2.getSubtype().contains(ChangelingAbility.ALL_CREATURE_TYPE)) {
return true;
}
}
@ -164,7 +163,7 @@ public class CardUtil {
CardUtil.adjustAbilityCost((Ability) spellAbility, reduceCount);
adjustAlternativeCosts(spellAbility, reduceCount);
}
public static ManaCosts<ManaCost> increaseCost(ManaCosts<ManaCost> manaCosts, int increaseCount) {
return adjustCost(manaCosts, -increaseCount);
}
@ -207,8 +206,6 @@ public class CardUtil {
}
}
/**
* Adjusts ability cost to be paid.
*
@ -220,10 +217,10 @@ public class CardUtil {
ability.getManaCostsToPay().clear();
ability.getManaCostsToPay().addAll(adjustedCost);
}
private static ManaCosts<ManaCost> adjustCost(ManaCosts<ManaCost> manaCosts, int reduceCount) {
int restToReduce = reduceCount;
ManaCosts<ManaCost> adjustedCost = new ManaCostsImpl<>();
ManaCosts<ManaCost> adjustedCost = new ManaCostsImpl<>();
boolean updated = false;
for (ManaCost manaCost : manaCosts) {
Mana mana = manaCost.getOptions().get(0);
@ -251,18 +248,18 @@ public class CardUtil {
public static ManaCosts<ManaCost> removeVariableManaCost(ManaCosts<ManaCost> manaCosts) {
ManaCosts<ManaCost> adjustedCost = new ManaCostsImpl<>();
for (ManaCost manaCost: manaCosts) {
for (ManaCost manaCost : manaCosts) {
if (!(manaCost instanceof VariableManaCost)) {
adjustedCost.add(manaCost);
}
}
return adjustedCost;
}
public static void reduceCost(SpellAbility spellAbility, ManaCosts<ManaCost> manaCostsToReduce) {
adjustCost(spellAbility, manaCostsToReduce, true);
}
public static void increaseCost(SpellAbility spellAbility, ManaCosts<ManaCost> manaCostsToIncrease) {
ManaCosts<ManaCost> increasedCost = spellAbility.getManaCostsToPay().copy();
@ -279,19 +276,19 @@ public class CardUtil {
*
* @param spellAbility
* @param manaCostsToReduce costs to reduce
* @param convertToGeneric colored mana does reduce generic mana if no appropriate colored mana is in the costs included
* @param convertToGeneric colored mana does reduce generic mana if no
* appropriate colored mana is in the costs included
*/
public static void adjustCost(SpellAbility spellAbility, ManaCosts<ManaCost> manaCostsToReduce, boolean convertToGeneric) {
ManaCosts<ManaCost> previousCost = spellAbility.getManaCostsToPay();
ManaCosts<ManaCost> adjustedCost = new ManaCostsImpl<>();
ManaCosts<ManaCost> adjustedCost = new ManaCostsImpl<>();
// save X value (e.g. convoke ability)
for (VariableCost vCost: previousCost.getVariableCosts()) {
for (VariableCost vCost : previousCost.getVariableCosts()) {
if (vCost instanceof VariableManaCost) {
adjustedCost.add((VariableManaCost) vCost);
}
}
Mana reduceMana = new Mana();
for (ManaCost manaCost : manaCostsToReduce) {
reduceMana.add(manaCost.getMana());
@ -304,46 +301,46 @@ public class CardUtil {
}
if (mana.getBlack() > 0 && reduceMana.getBlack() > 0) {
if (reduceMana.getBlack() > mana.getBlack()) {
reduceMana.setBlack(reduceMana.getBlack()-mana.getBlack());
reduceMana.setBlack(reduceMana.getBlack() - mana.getBlack());
mana.setBlack(0);
} else {
mana.setBlack(mana.getBlack()-reduceMana.getBlack());
mana.setBlack(mana.getBlack() - reduceMana.getBlack());
reduceMana.setBlack(0);
}
}
if (mana.getRed() > 0 && reduceMana.getRed() > 0) {
if (reduceMana.getRed() > mana.getRed()) {
reduceMana.setRed(reduceMana.getRed()-mana.getRed());
reduceMana.setRed(reduceMana.getRed() - mana.getRed());
mana.setRed(0);
} else {
mana.setRed(mana.getRed()-reduceMana.getRed());
mana.setRed(mana.getRed() - reduceMana.getRed());
reduceMana.setRed(0);
}
}
if (mana.getBlue() > 0 && reduceMana.getBlue() > 0) {
if (reduceMana.getBlue() > mana.getBlue()) {
reduceMana.setBlue(reduceMana.getBlue()-mana.getBlue());
reduceMana.setBlue(reduceMana.getBlue() - mana.getBlue());
mana.setBlue(0);
} else {
mana.setBlue(mana.getBlue()-reduceMana.getBlue());
mana.setBlue(mana.getBlue() - reduceMana.getBlue());
reduceMana.setBlue(0);
}
}
if (mana.getGreen() > 0 && reduceMana.getGreen() > 0) {
if (reduceMana.getGreen() > mana.getGreen()) {
reduceMana.setGreen(reduceMana.getGreen()-mana.getGreen());
reduceMana.setGreen(reduceMana.getGreen() - mana.getGreen());
mana.setGreen(0);
} else {
mana.setGreen(mana.getGreen()-reduceMana.getGreen());
mana.setGreen(mana.getGreen() - reduceMana.getGreen());
reduceMana.setGreen(0);
}
}
if (mana.getWhite() > 0 && reduceMana.getWhite() > 0) {
if (reduceMana.getWhite() > mana.getWhite()) {
reduceMana.setWhite(reduceMana.getWhite()-mana.getWhite());
reduceMana.setWhite(reduceMana.getWhite() - mana.getWhite());
mana.setWhite(0);
} else {
mana.setWhite(mana.getWhite()-reduceMana.getWhite());
mana.setWhite(mana.getWhite() - reduceMana.getWhite());
reduceMana.setWhite(0);
}
}
@ -384,10 +381,10 @@ public class CardUtil {
spellAbility.getManaCostsToPay().clear();
spellAbility.getManaCostsToPay().addAll(adjustedCost);
}
/**
* Returns function that copies params\abilities from one card to {@link Token}.
* Returns function that copies params\abilities from one card to
* {@link Token}.
*
* @param target
* @return
@ -396,7 +393,7 @@ public class CardUtil {
return new CopyTokenFunction(target);
}
public static boolean isPermanentCard ( Card card ) {
public static boolean isPermanentCard(Card card) {
boolean permanent = false;
permanent |= card.getCardType().contains(CardType.ARTIFACT);
@ -409,23 +406,24 @@ public class CardUtil {
}
/**
* Converts an integer number to string
* Numbers > 20 will be returned as digits
* Converts an integer number to string Numbers > 20 will be returned as
* digits
*
* @param number
* @return
* @return
*/
public static String numberToText(int number) {
return numberToText(number, "one");
}
/**
* Converts an integer number to string like "one", "two", "three", ...
* Numbers > 20 will be returned as digits
*
*
* @param number number to convert to text
* @param forOne if the number is 1, this string will be returnedinstead of "one".
* @return
* @param forOne if the number is 1, this string will be returnedinstead of
* "one".
* @return
*/
public static String numberToText(int number, String forOne) {
if (number == 1 && forOne != null) {
@ -458,8 +456,8 @@ public class CardUtil {
}
public static boolean checkNumeric(String s) {
for(int i = 0; i < s.length(); i++) {
if(!Character.isDigit(s.charAt(i))) {
for (int i = 0; i < s.length(); i++) {
if (!Character.isDigit(s.charAt(i))) {
return false;
}
}
@ -476,7 +474,7 @@ public class CardUtil {
public static UUID getCardExileZoneId(Game game, Ability source) {
return getCardExileZoneId(game, source.getSourceId());
}
public static UUID getCardExileZoneId(Game game, UUID sourceId) {
return getCardExileZoneId(game, sourceId, false);
}
@ -484,7 +482,7 @@ public class CardUtil {
public static UUID getCardExileZoneId(Game game, UUID sourceId, boolean previous) {
return getExileZoneId(getCardZoneString(SOURCE_EXILE_ZONE_TEXT, sourceId, game, previous), game);
}
public static UUID getObjectExileZoneId(Game game, MageObject mageObject) {
return getObjectExileZoneId(game, mageObject, false);
}
@ -499,26 +497,27 @@ public class CardUtil {
if (zoneChangeCounter > 0 && previous) {
zoneChangeCounter--;
}
return getExileZoneId(getObjectZoneString(SOURCE_EXILE_ZONE_TEXT,mageObject.getId(), game, zoneChangeCounter, false), game);
return getExileZoneId(getObjectZoneString(SOURCE_EXILE_ZONE_TEXT, mageObject.getId(), game, zoneChangeCounter, false), game);
}
public static UUID getExileZoneId(Game game, UUID objectId, int zoneChangeCounter) {
return getExileZoneId(getObjectZoneString(SOURCE_EXILE_ZONE_TEXT,objectId, game, zoneChangeCounter, false), game);
return getExileZoneId(getObjectZoneString(SOURCE_EXILE_ZONE_TEXT, objectId, game, zoneChangeCounter, false), game);
}
public static UUID getExileZoneId(String key, Game game) {
UUID exileId = (UUID) game.getState().getValue(key);
if (exileId == null) {
exileId = UUID.randomUUID();
game.getState().setValue(key, exileId);
}
return exileId;
return exileId;
}
/**
* Creates a string from text + cardId and the zoneChangeCounter of the card (from cardId).
* This string can be used to save and get values that must be specific to a permanent instance.
* So they won't match, if a permanent was e.g. exiled and came back immediately.
* Creates a string from text + cardId and the zoneChangeCounter of the card
* (from cardId). This string can be used to save and get values that must
* be specific to a permanent instance. So they won't match, if a permanent
* was e.g. exiled and came back immediately.
*
* @param text short value to describe the value
* @param cardId id of the card
@ -529,15 +528,15 @@ public class CardUtil {
return getCardZoneString(text, cardId, game, false);
}
public static String getCardZoneString(String text, UUID cardId, Game game, boolean previous) {
int zoneChangeCounter= 0;
public static String getCardZoneString(String text, UUID cardId, Game game, boolean previous) {
int zoneChangeCounter = 0;
Card card = game.getCard(cardId); // if called for a token, the id is enough
if (card != null) {
zoneChangeCounter = card.getZoneChangeCounter(game);
}
return getObjectZoneString(text,cardId, game, zoneChangeCounter, previous);
return getObjectZoneString(text, cardId, game, zoneChangeCounter, previous);
}
public static String getObjectZoneString(String text, MageObject mageObject, Game game) {
int zoneChangeCounter = 0;
if (mageObject instanceof Permanent) {
@ -547,22 +546,23 @@ public class CardUtil {
}
return getObjectZoneString(text, mageObject.getId(), game, zoneChangeCounter, false);
}
public static String getObjectZoneString(String text, UUID objectId, Game game, int zoneChangeCounter, boolean previous) {
StringBuilder uniqueString = new StringBuilder();
if (text != null) {
uniqueString.append(text);
}
uniqueString.append(objectId);
uniqueString.append(previous ? zoneChangeCounter - 1: zoneChangeCounter);
return uniqueString.toString();
uniqueString.append(previous ? zoneChangeCounter - 1 : zoneChangeCounter);
return uniqueString.toString();
}
/**
* Returns if the ability is used to check which cards
* are playable on hand. (Issue #457)
* Returns if the ability is used to check which cards are playable on hand.
* (Issue #457)
*
* @param ability - ability to check
* @return
* @return
*/
public static boolean isCheckPlayableMode(Ability ability) {
if (ability instanceof ActivatedAbility) {
@ -572,8 +572,8 @@ public class CardUtil {
}
/**
* Adds tags to mark the additional info of a card
* (e.g. blue font color)
* Adds tags to mark the additional info of a card (e.g. blue font color)
*
* @param text text body
* @return
*/
@ -584,7 +584,7 @@ public class CardUtil {
public static boolean convertedManaCostsIsEqual(MageObject object1, MageObject object2) {
Set<Integer> cmcObject1 = getCMC(object1);
Set<Integer> cmcObject2 = getCMC(object2);
for (Integer integer :cmcObject1) {
for (Integer integer : cmcObject1) {
if (cmcObject2.contains(integer)) {
return true;
}
@ -595,7 +595,7 @@ public class CardUtil {
public static Set<Integer> getCMC(MageObject object) {
Set<Integer> cmcObject = new HashSet<>();
if (object instanceof Spell) {
cmcObject.add(((Spell)object).getConvertedManaCost());
cmcObject.add(((Spell) object).getConvertedManaCost());
} else if (object instanceof Card) {
Card card = (Card) object;
if (card instanceof SplitCard) {
@ -605,16 +605,16 @@ public class CardUtil {
} else {
cmcObject.add(card.getManaCost().convertedManaCost());
}
}
}
return cmcObject;
}
/**
* Gets the colors that are in the casting cost but also in the rules text
* Gets the colors that are in the casting cost but also in the rules text
* as far as not included in reminder text.
*
*
* @param card
* @return
* @return
*/
public static FilterMana getColorIdentity(Card card) {
FilterMana mana = new FilterMana();
@ -644,8 +644,17 @@ public class CardUtil {
}
return mana;
}
public static boolean isNonCreatureSubtype(String subtype) {
return NON_CREATURE_SUBTYPES.contains(subtype);
}
public static boolean cardCanBePlayedNow(Card card, UUID playerId, Game game) {
if (card.getCardType().contains(CardType.LAND)) {
return game.canPlaySorcery(playerId) && game.getPlayer(playerId).canPlayLand();
} else {
return card.getSpellAbility() != null && card.getSpellAbility().spellCanBeActivatedRegularlyNow(playerId, game);
}
}
}