Merge remote-tracking branch 'origin/master' into decouple-client

Conflicts:
	Mage/src/mage/cards/repository/CardRepository.java
This commit is contained in:
North 2013-06-30 11:26:40 +03:00
commit 1fccbd6b87
339 changed files with 17278 additions and 1002 deletions

View file

@ -52,6 +52,8 @@ import org.apache.log4j.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.abilities.keyword.EntwineAbility;
import mage.constants.SpellAbilityType;
/**
@ -161,21 +163,35 @@ public abstract class AbilityImpl<T extends AbilityImpl<T>> implements Ability {
@Override
public boolean activate(Game game, boolean noMana) {
// 20110204 - 700.2
/* 20130201 - 601.2b
* If the spell is modal the player announces the mode choice (see rule 700.2).
*/
if (!modes.choose(game, this)) {
return false;
}
//20100716 - 601.2b
/* 20130201 - 601.2b
* If the player wishes to splice any cards onto the spell (see rule 702.45), he
* or she reveals those cards in his or her hand.
*/
if (this.abilityType.equals(AbilityType.SPELL)) {
game.getContinuousEffects().applySpliceEffects(this, game);
}
Card card = game.getCard(sourceId);
if (card != null) {
card.adjustChoices(this, game);
}
if (getChoices().size() > 0 && getChoices().choose(game, this) == false) {
logger.debug("activate failed - choice");
return false;
for (UUID modeId :this.getModes().getSelectedModes()) {
this.getModes().setMode(this.getModes().get(modeId));
if (getChoices().size() > 0 && getChoices().choose(game, this) == false) {
logger.debug("activate failed - choice");
return false;
}
}
// 20121001 - 601.2b
// 20130201 - 601.2b
// If the spell has alternative or additional costs that will be paid as it's being cast such
// as buyback, kicker, or convoke costs (see rules 117.8 and 117.9), the player announces his
// or her intentions to pay any or all of those costs (see rule 601.2e).
@ -193,38 +209,43 @@ public abstract class AbilityImpl<T extends AbilityImpl<T>> implements Ability {
}
}
}
// 20121001 - 601.2b
// If the spell has a variable cost that will be paid as it's being cast (such as an {X} in
// its mana cost; see rule 107.3), the player announces the value of that variable.
VariableManaCost variableManaCost = handleXCosts(game, noMana);
//20121001 - 601.2c
// 601.2c The player announces his or her choice of an appropriate player, object, or zone for
// each target the spell requires. A spell may require some targets only if an alternative or
// additional cost (such as a buyback or kicker cost), or a particular mode, was chosen for it;
// otherwise, the spell is cast as though it did not require those targets. If the spell has a
// variable number of targets, the player announces how many targets he or she will choose before
// he or she announces those targets. The same target can't be chosen multiple times for any one
// instance of the word "target" on the spell. However, if the spell uses the word "target" in
// multiple places, the same object, player, or zone can be chosen once for each instance of the
// word "target" (as long as it fits the targeting criteria). If any effects say that an object
// or player must be chosen as a target, the player chooses targets so that he or she obeys the
// maximum possible number of such effects without violating any rules or effects that say that
// an object or player can't be chosen as a target. The chosen players, objects, and/or zones
// each become a target of that spell. (Any abilities that trigger when those players, objects,
// and/or zones become the target of a spell trigger at this point; they'll wait to be put on
// the stack until the spell has finished being cast.)
if (card != null) {
card.adjustTargets(this, game);
}
if (getTargets().size() > 0 && getTargets().chooseTargets(getEffects().get(0).getOutcome(), this.controllerId, this, game) == false) {
if (variableManaCost != null) {
game.informPlayers(new StringBuilder(card.getName()).append(": no valid targets with this value of X").toString());
} else {
logger.debug("activate failed - target");
for (UUID modeId :this.getModes().getSelectedModes()) {
this.getModes().setMode(this.getModes().get(modeId));
//20121001 - 601.2c
// 601.2c The player announces his or her choice of an appropriate player, object, or zone for
// each target the spell requires. A spell may require some targets only if an alternative or
// additional cost (such as a buyback or kicker cost), or a particular mode, was chosen for it;
// otherwise, the spell is cast as though it did not require those targets. If the spell has a
// variable number of targets, the player announces how many targets he or she will choose before
// he or she announces those targets. The same target can't be chosen multiple times for any one
// instance of the word "target" on the spell. However, if the spell uses the word "target" in
// multiple places, the same object, player, or zone can be chosen once for each instance of the
// word "target" (as long as it fits the targeting criteria). If any effects say that an object
// or player must be chosen as a target, the player chooses targets so that he or she obeys the
// maximum possible number of such effects without violating any rules or effects that say that
// an object or player can't be chosen as a target. The chosen players, objects, and/or zones
// each become a target of that spell. (Any abilities that trigger when those players, objects,
// and/or zones become the target of a spell trigger at this point; they'll wait to be put on
// the stack until the spell has finished being cast.)
if (card != null) {
card.adjustTargets(this, game);
}
return false;
}
if (getTargets().size() > 0 && getTargets().chooseTargets(getEffects().get(0).getOutcome(), this.controllerId, this, game) == false) {
if (variableManaCost != null) {
game.informPlayers(new StringBuilder(card != null ? card.getName(): "").append(": no valid targets with this value of X").toString());
} else {
logger.debug("activate failed - target");
}
return false;
}
} // end modes
// TODO: Handle optionalCosts at the same time as already OptionalAdditionalSourceCosts are handled.
for (Cost cost : optionalCosts) {

View file

@ -243,21 +243,59 @@ public abstract class ActivatedAbilityImpl<T extends ActivatedAbilityImpl<T>> ex
} else {
sb.append("unknown");
}
if (object instanceof Spell && ((Spell) object).getSpellAbility().getSpellAbilityType().equals(SpellAbilityType.SPLIT_FUSED)) {
Spell<?> spell = (Spell<?>) object;
int i = 0;
for (SpellAbility spellAbility : spell.getSpellAbilities()) {
i++;
String half;
if (i == 1) {
half = " left";
} else {
half = " right";
if (object instanceof Spell && ((Spell) object).getSpellAbilities().size() > 1) {
if (((Spell) object).getSpellAbility().getSpellAbilityType().equals(SpellAbilityType.SPLIT_FUSED)) {
Spell<?> spell = (Spell<?>) object;
int i = 0;
for (SpellAbility spellAbility : spell.getSpellAbilities()) {
i++;
String half;
if (i == 1) {
half = " left";
} else {
half = " right";
}
if (spellAbility.getTargets().size() > 0) {
sb.append(half).append(" half targeting ");
for (Target target: spellAbility.getTargets()) {
sb.append(target.getTargetedName(game));
}
}
}
if (spellAbility.getTargets().size() > 0) {
sb.append(half).append(" half targeting ");
for (Target target: spellAbility.getTargets()) {
sb.append(target.getTargetedName(game));
} else {
Spell<?> spell = (Spell<?>) object;
int i = 0;
for (SpellAbility spellAbility : spell.getSpellAbilities()) {
i++;
if ( i > 1) {
sb.append(" splicing ");
if (spellAbility.name.length() > 5 && spellAbility.name.startsWith("Cast ")) {
sb.append(spellAbility.name.substring(5));
} else {
sb.append(spellAbility.name);
}
}
if (spellAbility.getTargets().size() > 0) {
for (Target target: spellAbility.getTargets()) {
sb.append(" targeting ");
sb.append(target.getTargetedName(game));
}
}
}
}
} else if (object instanceof Spell && ((Spell) object).getSpellAbility().getModes().size() > 1) {
Modes spellModes = ((Spell) object).getSpellAbility().getModes();
int item = 0;
for (Mode mode : spellModes.values()) {
item++;
if (spellModes.getSelectedModes().contains(mode.getId())) {
spellModes.setMode(mode);
sb.append(" (mode ").append(item).append(")");
if (getTargets().size() > 0) {
sb.append(" targeting ");
for (Target target: getTargets()) {
sb.append(target.getTargetedName(game));
}
}
}
}

View file

@ -28,8 +28,12 @@
package mage.abilities;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import mage.abilities.costs.OptionalAdditionalModeSourceCosts;
import mage.cards.Card;
import mage.game.Game;
import mage.players.Player;
@ -40,11 +44,18 @@ import mage.players.Player;
public class Modes extends LinkedHashMap<UUID, Mode> {
private UUID modeId;
private Set<UUID> selectedModes = new LinkedHashSet<UUID>();
private int minModes;
private int maxModes;
public Modes() {
Mode mode = new Mode();
this.put(mode.getId(), mode);
this.modeId = mode.getId();
this.minModes = 1;
this.maxModes = 1;
this.selectedModes.add(modeId);
}
public Modes(Modes modes) {
@ -52,6 +63,9 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
for (Map.Entry<UUID, Mode> entry: modes.entrySet()) {
this.put(entry.getKey(), entry.getValue().copy());
}
this.minModes = modes.minModes;
this.maxModes = modes.maxModes;
this.selectedModes.addAll(modes.selectedModes);
}
public Modes copy() {
@ -62,9 +76,30 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
return get(modeId);
}
public Set<UUID> getSelectedModes() {
return selectedModes;
}
public void setMinModes(int minModes) {
this.minModes = minModes;
}
public int getMinModes() {
return this.minModes;
}
public void setMaxModes(int maxModes) {
this.maxModes = maxModes;
}
public int getMaxModes() {
return this.maxModes;
}
public void setMode(Mode mode) {
if (this.containsKey(mode.getId())) {
this.modeId = mode.getId();
this.selectedModes.add(mode.getId());
}
}
@ -74,40 +109,78 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
public boolean choose(Game game, Ability source) {
if (this.size() > 1) {
Player player = game.getPlayer(source.getControllerId());
Mode choice = player.chooseMode(this, source, game);
if (choice == null) {
return false;
this.selectedModes.clear();
// check if mode modifying abilities exist
Card card = game.getCard(source.getSourceId());
if (card != null) {
for (Ability modeModifyingAbility : card.getAbilities()) {
if (modeModifyingAbility instanceof OptionalAdditionalModeSourceCosts) {
((OptionalAdditionalModeSourceCosts)modeModifyingAbility).addOptionalAdditionalModeCosts(source, game);
}
}
}
// check if all modes can be activated automatically
if (this.size() == this.getMinModes()) {
for (Mode mode: this.values()) {
if (mode.getTargets().canChoose(source.getSourceId(), source.getControllerId(), game)) {
this.selectedModes.add(mode.getId());
}
}
return selectedModes.size() > 0;
}
// player chooses modes manually
Player player = game.getPlayer(source.getControllerId());
while (this.selectedModes.size() < this.getMaxModes()) {
Mode choice = player.chooseMode(this, source, game);
if (choice == null) {
return this.selectedModes.size() >= this.getMinModes();
}
setMode(choice);
this.selectedModes.add(choice.getId());
}
setMode(choice);
return true;
}
this.modeId = this.values().iterator().next().getId();
this.selectedModes.add(modeId);
return true;
}
public String getText() {
String andOr = "";
StringBuilder sb = new StringBuilder();
if (this.size() > 1) {
sb.append("Choose one - ");
if (this.getMinModes() == 1 && this.getMaxModes() == 3) {
sb.append("Choose one or more - ");
andOr = "; and/or ";
}else if (this.getMinModes() == 1 && this.getMaxModes() == 2) {
sb.append("Choose one or both - ");
andOr = "; and/or ";
} else if (this.getMinModes() == 2 && this.getMaxModes() == 2) {
sb.append("Choose two - ");
andOr = "; or ";
} else {
sb.append("Choose one - ");
andOr = "; or ";
}
}
for (Mode mode: this.values()) {
sb.append(mode.getEffects().getText(mode)).append("; or ");
sb.append(mode.getEffects().getText(mode));
if (this.size() > 1) {
if (sb.length() > 2 && sb.substring(sb.length()-2, sb.length()).equals(". ")) {
sb.delete(sb.length()-2, sb.length());
}
sb.append(andOr);
}
}
if (this.size() > 1) {
sb.delete(sb.length() - andOr.length(), sb.length());
sb.append(".");
}
sb.delete(sb.length() - 5, sb.length());
return sb.toString();
}
public String getText(String sourceName) {
StringBuilder sb = new StringBuilder();
if (this.size() > 1) {
sb.append("Choose one - ");
}
for (Mode mode: this.values()) {
sb.append(mode.getEffects().getText(mode)).append("; or ");
}
sb.delete(sb.length() - 5, sb.length());
String text = sb.toString();
String text = getText();
text = text.replace("{this}", sourceName);
text = text.replace("{source}", sourceName);
return text;

View file

@ -19,6 +19,7 @@ import mage.target.targetpointer.FixedTarget;
public class BlocksAttachedTriggeredAbility extends TriggeredAbilityImpl<BlocksAttachedTriggeredAbility>{
private boolean setFixedTargetPointer;
private String attachedDescription;
private boolean setFixedTargetPointerToBlocked;
public BlocksAttachedTriggeredAbility(Effect effect, String attachedDescription, boolean optional) {
this(effect, attachedDescription, optional, false);
@ -29,6 +30,13 @@ public class BlocksAttachedTriggeredAbility extends TriggeredAbilityImpl<BlocksA
this.setFixedTargetPointer = setFixedTargetPointer;
this.attachedDescription = attachedDescription;
}
public BlocksAttachedTriggeredAbility(Effect effect, String attachedDescription, boolean optional, boolean setFixedTargetPointer, boolean setFixedTargetPointerToBlocked) {
super(Zone.BATTLEFIELD, effect, optional);
this.setFixedTargetPointer = setFixedTargetPointer;
this.attachedDescription = attachedDescription;
this.setFixedTargetPointerToBlocked = setFixedTargetPointerToBlocked;
}
public BlocksAttachedTriggeredAbility(final BlocksAttachedTriggeredAbility ability) {
super(ability);
@ -51,6 +59,11 @@ public class BlocksAttachedTriggeredAbility extends TriggeredAbilityImpl<BlocksA
effect.setTargetPointer(new FixedTarget(event.getPlayerId()));
}
}
if (setFixedTargetPointerToBlocked) {
for (Effect effect : this.getEffects()) {
effect.setTargetPointer(new FixedTarget(event.getTargetId()));
}
}
return true;
}
}

View file

@ -49,7 +49,7 @@ public class EntersBattlefieldAbility extends StaticAbility<EntersBattlefieldAbi
/**
*
* @param effect effect that happens when the permanent enters the battlefield
* @param showRule show a rule for this ability
* @param showRule show the rule for this ability
*/
public EntersBattlefieldAbility(Effect effect, Boolean showRule) {
this(effect, null, showRule, null, null);

View file

@ -86,11 +86,12 @@ public class EntersBattlefieldAllTriggeredAbility extends TriggeredAbilityImpl {
this.setTargetPointer = setTargetPointer;
}
public EntersBattlefieldAllTriggeredAbility(EntersBattlefieldAllTriggeredAbility ability) {
public EntersBattlefieldAllTriggeredAbility(final EntersBattlefieldAllTriggeredAbility ability) {
super(ability);
this.filter = ability.filter;
this.rule = ability.rule;
this.controlled = ability.controlled;
this.setTargetPointer = ability.setTargetPointer;
}
@Override

View file

@ -49,10 +49,15 @@ public class OpponentCastsSpellTriggeredAbility extends TriggeredAbilityImpl<Opp
}
public OpponentCastsSpellTriggeredAbility(Effect effect, FilterCard filter, boolean optional) {
super(Zone.BATTLEFIELD, effect, optional);
this(Zone.BATTLEFIELD, effect, filter, optional);
}
public OpponentCastsSpellTriggeredAbility(Zone zone, Effect effect, FilterCard filter, boolean optional) {
super(zone, effect, optional);
this.filter = filter;
}
public OpponentCastsSpellTriggeredAbility(final OpponentCastsSpellTriggeredAbility ability) {
super(ability);
filter = ability.filter;

View file

@ -51,8 +51,7 @@ public class SpellCastTriggeredAbility extends TriggeredAbilityImpl<SpellCastTri
protected boolean rememberSource = false;
public SpellCastTriggeredAbility(Effect effect, boolean optional) {
super(Zone.BATTLEFIELD, effect, optional);
this.filter = spellCard;
this(Zone.BATTLEFIELD, effect, spellCard, optional, false);
}
public SpellCastTriggeredAbility(Effect effect, FilterSpell filter, boolean optional) {
@ -60,7 +59,11 @@ public class SpellCastTriggeredAbility extends TriggeredAbilityImpl<SpellCastTri
}
public SpellCastTriggeredAbility(Effect effect, FilterSpell filter, boolean optional, boolean rememberSource) {
super(Zone.BATTLEFIELD, effect, optional);
this(Zone.BATTLEFIELD, effect, filter, optional, rememberSource);
}
public SpellCastTriggeredAbility(Zone zone, Effect effect, FilterSpell filter, boolean optional, boolean rememberSource) {
super(zone, effect, optional);
this.filter = filter;
this.rememberSource = rememberSource;
}

View file

@ -0,0 +1,67 @@
/*
* 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.condition.common;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.ColorPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
/**
*
* @author Plopman
*/
public class EnchantedCreatureColorCondition implements Condition {
private FilterPermanent filter = new FilterCreaturePermanent();
public EnchantedCreatureColorCondition(ObjectColor color){
filter.add(new ColorPredicate(color));
}
@Override
public boolean apply(Game game, Ability source) {
Permanent enchantement = game.getPermanent(source.getSourceId());
if (enchantement != null) {
Permanent creature = game.getPermanent(enchantement.getAttachedTo());
if (creature != null) {
if(filter.match(creature, source.getId(), enchantement.getControllerId(), game)){
return true;
}
}
}
return false;
}
}

View file

@ -0,0 +1,70 @@
/*
* 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.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.abilities.keyword.ProwlAbility;
import mage.cards.Card;
import mage.game.Game;
/**
* Checks if a the spell was cast with the alternate prowl costs
*
* @author LevelX2
*/
public class ProwlCondition implements Condition {
private static ProwlCondition fInstance = null;
private ProwlCondition() {}
public static Condition getInstance() {
if (fInstance == null) {
fInstance = new ProwlCondition();
}
return fInstance;
}
@Override
public boolean apply(Game game, Ability source) {
Card card = game.getCard(source.getSourceId());
if (card != null) {
for (Ability ability: card.getAbilities()) {
if (ability instanceof ProwlAbility) {
if(((ProwlAbility) ability).isActivated()) {
return true;
}
}
}
}
return false;
}
}

View file

@ -0,0 +1,58 @@
/*
* 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.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.game.Game;
import mage.game.permanent.Permanent;
/**
*
* @author LevelX2
*/
public class SourceTappedCondition implements Condition {
private static SourceTappedCondition fInstance = new SourceTappedCondition();
public static Condition getInstance() {
return fInstance;
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getBattlefield().getPermanent(source.getSourceId());
if (permanent != null) {
return permanent.isTapped();
}
return false;
}
}

View file

@ -69,11 +69,11 @@ public class SuspendedCondition implements Condition {
break;
}
}
}
if (found) {
if (game.getState().getZone(card.getId()) == Zone.EXILED &&
card.getCounters().getCount(CounterType.TIME) > 0) {
return true;
if (found) {
if (game.getState().getZone(card.getId()) == Zone.EXILED &&
card.getCounters().getCount(CounterType.TIME) > 0) {
return true;
}
}
}
return false;

View file

@ -0,0 +1,42 @@
/*
* 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;
import mage.abilities.Ability;
import mage.game.Game;
/**
*
* @author LevelX2
*/
public interface OptionalAdditionalModeSourceCosts {
void addOptionalAdditionalModeCosts(Ability ability, Game game);
String getCastMessageSuffix();
}

View file

@ -28,16 +28,16 @@
package mage.abilities.costs.common;
import mage.constants.Outcome;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.costs.CostImpl;
import mage.cards.Card;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetCardInYourGraveyard;
import java.util.UUID;
import mage.util.CardUtil;
/**
*
@ -48,7 +48,7 @@ public class ExileFromGraveCost extends CostImpl<ExileFromGraveCost> {
public ExileFromGraveCost(TargetCardInYourGraveyard target) {
this.addTarget(target);
if (target.getMaxNumberOfTargets() > 1) {
this.text = "Exile " + target.getMaxNumberOfTargets() + " " + target.getTargetName();
this.text = "Exile " + CardUtil.numberToText(target.getMaxNumberOfTargets()) + " " + target.getTargetName();
}
else {
this.text = "Exile " + target.getTargetName();

View file

@ -1,12 +1,12 @@
package mage.abilities.decorator;
import mage.constants.Duration;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.condition.Condition;
import mage.abilities.condition.common.FixedCondition;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.SubLayer;
import mage.game.Game;
@ -84,6 +84,9 @@ public class ConditionalContinousEffect extends ContinuousEffectImpl<Conditional
if (!condition.apply(game, source) && effect.getDuration() == Duration.OneUse) {
used = true;
}
if (!condition.apply(game, source) && effect.getDuration() == Duration.Custom) {
this.discard();
}
return false;
}

View file

@ -57,7 +57,7 @@ public class MultikickerCount implements DynamicValue {
}
@Override
public DynamicValue copy() {
public MultikickerCount copy() {
return new MultikickerCount();
}

View file

@ -49,24 +49,25 @@ public class SunburstCount implements DynamicValue{
@Override
public int calculate(Game game, Ability source) {
int count = 0;
StackObject spell = game.getStack().getFirst();
if (spell != null && spell instanceof Spell && ((Spell)spell).getSourceId().equals(source.getSourceId())) {
Mana mana = ((Spell)spell).getSpellAbility().getManaCostsToPay().getPayment();
if(mana.getBlack() > 0) {
count++;
}
if(mana.getBlue() > 0) {
count++;
}
if(mana.getGreen() > 0) {
count++;
}
if(mana.getRed() > 0) {
count++;
}
if(mana.getWhite() > 0) {
count++;
if (!game.getStack().isEmpty()) {
StackObject spell = game.getStack().getFirst();
if (spell != null && spell instanceof Spell && ((Spell)spell).getSourceId().equals(source.getSourceId())) {
Mana mana = ((Spell)spell).getSpellAbility().getManaCostsToPay().getPayment();
if(mana.getBlack() > 0) {
count++;
}
if(mana.getBlue() > 0) {
count++;
}
if(mana.getGreen() > 0) {
count++;
}
if(mana.getRed() > 0) {
count++;
}
if(mana.getWhite() > 0) {
count++;
}
}
}
return count;

View file

@ -36,11 +36,22 @@ import mage.constants.Layer;
import mage.constants.SubLayer;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility;
import mage.abilities.keyword.SpliceOntoArcaneAbility;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.constants.Outcome;
import mage.constants.SpellAbilityType;
import mage.filter.FilterCard;
import mage.filter.predicate.Predicate;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.CardIdPredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetCardInHand;
/**
*
@ -58,6 +69,7 @@ public class ContinuousEffects implements Serializable {
private ContinuousEffectsList<RestrictionEffect> restrictionEffects = new ContinuousEffectsList<RestrictionEffect>();
private ContinuousEffectsList<AsThoughEffect> asThoughEffects = new ContinuousEffectsList<AsThoughEffect>();
private ContinuousEffectsList<CostModificationEffect> costModificationEffects = new ContinuousEffectsList<CostModificationEffect>();
private ContinuousEffectsList<SpliceCardEffect> spliceCardEffects = new ContinuousEffectsList<SpliceCardEffect>();
private List<ContinuousEffectsList<?>> allEffectsLists = new ArrayList<ContinuousEffectsList<?>>();
@ -88,6 +100,7 @@ public class ContinuousEffects implements Serializable {
restrictionEffects = effect.restrictionEffects.copy();
asThoughEffects = effect.asThoughEffects.copy();
costModificationEffects = effect.costModificationEffects.copy();
spliceCardEffects = effect.spliceCardEffects.copy();
for (Map.Entry<UUID, UUID> entry : effect.sources.entrySet()) {
sources.put(entry.getKey(), entry.getValue());
}
@ -103,6 +116,7 @@ public class ContinuousEffects implements Serializable {
allEffectsLists.add(restrictionEffects);
allEffectsLists.add(asThoughEffects);
allEffectsLists.add(costModificationEffects);
allEffectsLists.add(spliceCardEffects);
}
public ContinuousEffects copy() {
@ -125,6 +139,7 @@ public class ContinuousEffects implements Serializable {
restrictionEffects.removeEndOfCombatEffects();
asThoughEffects.removeEndOfCombatEffects();
costModificationEffects.removeEndOfCombatEffects();
spliceCardEffects.removeEndOfCombatEffects();
}
public void removeEndOfTurnEffects() {
@ -135,6 +150,7 @@ public class ContinuousEffects implements Serializable {
restrictionEffects.removeEndOfTurnEffects();
asThoughEffects.removeEndOfTurnEffects();
costModificationEffects.removeEndOfTurnEffects();
spliceCardEffects.removeEndOfTurnEffects();
}
public void removeInactiveEffects(Game game) {
@ -145,6 +161,7 @@ public class ContinuousEffects implements Serializable {
restrictionEffects.removeInactiveEffects(game);
asThoughEffects.removeInactiveEffects(game);
costModificationEffects.removeInactiveEffects(game);
spliceCardEffects.removeInactiveEffects(game);
}
public List<ContinuousEffect> getLayeredEffects(Game game) {
@ -156,7 +173,8 @@ public class ContinuousEffects implements Serializable {
case WhileInGraveyard:
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
for (Ability ability: abilities) {
if (ability.isInUseableZone(game, null, false)) {
// If e.g. triggerd abilities (non static) created the effect, the ability must not be in usable zone (e.g. Unearth giving Haste effect)
if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, false)) {
layerEffects.add(effect);
break;
}
@ -327,6 +345,29 @@ public class ContinuousEffects implements Serializable {
return costEffects;
}
/**
* Filters out splice effects that are not active.
*
* @param game
* @return
*/
private List<SpliceCardEffect> getApplicableSpliceCardEffects(Game game, UUID playerId) {
List<SpliceCardEffect> spliceEffects = new ArrayList<SpliceCardEffect>();
for (SpliceCardEffect effect: spliceCardEffects) {
HashSet<Ability> abilities = spliceCardEffects.getAbility(effect.getId());
for (Ability ability : abilities) {
if (ability.getControllerId().equals(playerId) && (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, false))) {
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
spliceEffects.add(effect);
break;
}
}
}
}
return spliceEffects;
}
public boolean asThough(UUID objectId, AsThoughEffectType type, Game game) {
List<AsThoughEffect> asThoughEffectsList = getApplicableAsThoughEffects(game);
@ -389,6 +430,68 @@ public class ContinuousEffects implements Serializable {
}
}
/**
* Checks all available splice effects to be applied.
*
* @param abilityToModify
* @param game
* @return
*/
public void applySpliceEffects ( Ability abilityToModify, Game game ) {
if ( ((SpellAbility) abilityToModify).getSpellAbilityType().equals(SpellAbilityType.SPLICE)) {
// on a spliced ability of a spell can't be spliced again
return;
}
List<SpliceCardEffect> spliceEffects = getApplicableSpliceCardEffects(game, abilityToModify.getControllerId());
// get the applyable splice abilities
List<SpliceOntoArcaneAbility> spliceAbilities = new ArrayList<SpliceOntoArcaneAbility>();
for (SpliceCardEffect effect : spliceEffects) {
HashSet<Ability> abilities = spliceCardEffects.getAbility(effect.getId());
for (Ability ability : abilities) {
if (effect.applies(abilityToModify, ability, game) ) {
spliceAbilities.add((SpliceOntoArcaneAbility) ability);
}
}
}
// check if player wants to use splice
if (spliceAbilities.size() > 0) {
Player controller = game.getPlayer(abilityToModify.getControllerId());
if (controller.chooseUse(Outcome.Benefit, "Splice a card?", game)) {
Cards cardsToReveal = new CardsImpl();
do {
FilterCard filter = new FilterCard("a card to splice");
ArrayList<Predicate<MageObject>> idPredicates = new ArrayList<Predicate<MageObject>>();
for (SpliceOntoArcaneAbility ability : spliceAbilities) {
idPredicates.add(new CardIdPredicate((ability.getSourceId())));
}
filter.add(Predicates.or(idPredicates));
TargetCardInHand target = new TargetCardInHand(filter);
target.setRequired(true);
controller.chooseTarget(Outcome.Benefit, target, abilityToModify, game);
UUID cardId = target.getFirstTarget();
if (cardId != null) {
SpliceOntoArcaneAbility selectedAbility = null;
for(SpliceOntoArcaneAbility ability :spliceAbilities) {
if (ability.getSourceId().equals(cardId)) {
selectedAbility = ability;
break;
}
}
if (selectedAbility != null) {
SpliceCardEffect spliceEffect = (SpliceCardEffect) selectedAbility.getEffects().get(0);
spliceEffect.apply(game, selectedAbility, abilityToModify);
cardsToReveal.add(game.getCard(cardId));
spliceAbilities.remove(selectedAbility);
}
}
} while (!spliceAbilities.isEmpty() && controller.chooseUse(Outcome.Benefit, "Splice another card?", game));
controller.revealCards("Spliced cards", cardsToReveal, game);
}
}
}
public boolean replaceEvent(GameEvent event, Game game) {
boolean caught = false;
HashMap<UUID, HashSet<UUID>> consumed = new HashMap<UUID, HashSet<UUID>>();
@ -616,6 +719,10 @@ public class ContinuousEffects implements Serializable {
CostModificationEffect newCostModificationEffect = (CostModificationEffect)effect;
costModificationEffects.addEffect(newCostModificationEffect, source);
break;
case SPLICE:
SpliceCardEffect newSpliceCardEffect = (SpliceCardEffect)effect;
spliceCardEffects.addEffect(newSpliceCardEffect, source);
break;
default:
ContinuousEffect newEffect = (ContinuousEffect)effect;
layeredEffects.addEffect(newEffect, source);

View file

@ -0,0 +1,66 @@
/*
* 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;
import mage.abilities.Ability;
import mage.game.Game;
/**
* Represents a {@link ContinuousEffect} that will modify the cost and abilities of a spell
* on the stack (splice a card). {@link mage.abilities.Ability Abilities} with this type of effect will be
* called once and only once from the {@link mage.abilities.AbilityImpl#activate(mage.game.Game,
* boolean) Ability.activate} method before alternative or additional costs are paid.
*
* @param <T>
* @author levelX2
*/
public interface SpliceCardEffect<T extends SpliceCardEffect<T>> extends ContinuousEffect<T> {
/**
* Called by the {@link ContinuousEffects#costModification(Ability abilityToModify, Game game) ContinuousEffects.costModification}
* method.
*
* @param game The game for which this effect should be applied.
* @param source The source ability of this effect.
* @param abilityToModify The {@link mage.abilities.SpellAbility} or {@link Ability} which should be modified.
* @return
*/
boolean apply ( Game game, Ability source, Ability abilityToModify );
/**
* Called by the {@link ContinuousEffects#costModification(mage.abilities.Ability, mage.game.Game) ContinuousEffects.costModification}
* method.
*
* @param abilityToModify The ability to possibly modify.
* @param source The source ability of this effect.
* @param game The game for which this effect shoul dbe applied.
* @return
*/
boolean applies(Ability abilityToModify, Ability source, Game game);
}

View file

@ -0,0 +1,67 @@
/*
* 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;
import mage.abilities.Ability;
import mage.constants.Duration;
import mage.constants.EffectType;
import mage.constants.Outcome;
import mage.game.Game;
/**
* Simple implementation of a {@link SpliceCardEffect} offering simplified
* construction to setup the object for use by the mage framework.
* @author LevelX2
*/
public abstract class SpliceCardEffectImpl<T extends SpliceCardEffectImpl<T>> extends ContinuousEffectImpl<T> implements SpliceCardEffect<T> {
public SpliceCardEffectImpl ( Duration duration, Outcome outcome ) {
super(duration, outcome);
this.effectType = EffectType.SPLICE;
}
public SpliceCardEffectImpl(final SpliceCardEffectImpl<T> effect) {
super(effect);
this.effectType = effect.effectType;
}
/**
* Overridden and 'no-op' implementation put in place.
*
* @see #apply(mage.game.Game, mage.abilities.Ability, mage.abilities.Ability)
*
* @param game
* @param source
* @return
*/
@Override
public final boolean apply ( Game game, Ability source ) { return false; }
}

View file

@ -1,33 +1,33 @@
/*
* 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;
import java.util.UUID;
import mage.constants.Outcome;
import mage.abilities.Ability;
import mage.abilities.Mode;
@ -55,12 +55,20 @@ public class CounterTargetEffect extends OneShotEffect<CounterTargetEffect> {
@Override
public boolean apply(Game game, Ability source) {
return game.getStack().counter(source.getFirstTarget(), source.getSourceId(), game);
boolean countered = false;
for (UUID targetId : source.getTargets().get(0).getTargets()) {
if (game.getStack().counter(targetId, source.getSourceId(), game)) {
countered = true;
}
}
return countered;
}
@Override
public String getText(Mode mode) {
if (staticText != null && !staticText.isEmpty()) {
return staticText;
}
return "Counter target " + mode.getTargets().get(0).getTargetName();
}
}

View file

@ -78,7 +78,7 @@ public class DamagePlayersEffect extends OneShotEffect<DamagePlayersEffect> {
for (UUID playerId: game.getPlayer(source.getControllerId()).getInRange()) {
Player player = game.getPlayer(playerId);
if (player != null) {
player.damage(amount.calculate(game, source), source.getId(), game, false, true);
player.damage(amount.calculate(game, source), source.getSourceId(), game, false, true);
}
}
break;
@ -86,7 +86,7 @@ public class DamagePlayersEffect extends OneShotEffect<DamagePlayersEffect> {
for (UUID playerId: game.getOpponents(source.getControllerId())) {
Player player = game.getPlayer(playerId);
if (player != null) {
player.damage(amount.calculate(game, source), source.getId(), game, false, true);
player.damage(amount.calculate(game, source), source.getSourceId(), game, false, true);
}
}
break;

View file

@ -28,15 +28,14 @@
package mage.abilities.effects.common;
import mage.constants.Outcome;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.Target;
import java.util.UUID;
import mage.target.targetpointer.FirstTargetPointer;
/**
@ -105,12 +104,14 @@ public class DestroyTargetEffect extends OneShotEffect<DestroyTargetEffect> {
StringBuilder sb = new StringBuilder();
if (mode.getTargets().size() == 0) {
sb.append("destroy that creature"); //TODO add possibility to specify text with targetPointer usage
} else if (mode.getTargets().get(0).getNumberOfTargets() == 1)
} else if (mode.getTargets().get(0).getNumberOfTargets() == 1) {
sb.append("Destroy target ").append(mode.getTargets().get(0).getTargetName());
else
} else {
sb.append("Destroy ").append(mode.getTargets().get(0).getNumberOfTargets()).append(" target ").append(mode.getTargets().get(0).getTargetName());
if (noRegen)
}
if (noRegen) {
sb.append(". It can't be regenerated");
}
return sb.toString();
}

View file

@ -0,0 +1,70 @@
/*
* 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.effects.OneShotEffect;
import mage.cards.Card;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
/**
*
* @author LevelX2
*/
public class DiscardHandControllerEffect extends OneShotEffect<DiscardHandControllerEffect> {
public DiscardHandControllerEffect() {
super(Outcome.Discard);
this.staticText = "Discard your hand";
}
public DiscardHandControllerEffect(final DiscardHandControllerEffect effect) {
super(effect);
}
@Override
public DiscardHandControllerEffect copy() {
return new DiscardHandControllerEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player != null) {
for (Card card : player.getHand().getCards(game)) {
player.discard(card, source, game);
}
return true;
}
return false;
}
}

View file

@ -28,11 +28,12 @@
package mage.abilities.effects.common;
import mage.constants.Outcome;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
/**
*
@ -40,13 +41,29 @@ import mage.players.Player;
*/
public class DrawDiscardControllerEffect extends OneShotEffect<DrawDiscardControllerEffect> {
private int cardsToDraw;
private int cardsToDiscard;
public DrawDiscardControllerEffect() {
this(1,1);
}
public DrawDiscardControllerEffect(int cardsToDraw, int cardsToDiscard) {
super(Outcome.DrawCard);
staticText = "Draw a card, then discard a card";
this.cardsToDraw = cardsToDraw;
this.cardsToDiscard = cardsToDiscard;
staticText = new StringBuilder("Draw ")
.append(cardsToDraw == 1?"a": CardUtil.numberToText(cardsToDraw))
.append(" card").append(cardsToDraw == 1?" ": "s")
.append(", then discard ")
.append(cardsToDiscard == 1?"a": CardUtil.numberToText(cardsToDiscard))
.append(" card").append(cardsToDiscard == 1?" ": "s").toString();
}
public DrawDiscardControllerEffect(final DrawDiscardControllerEffect effect) {
super(effect);
this.cardsToDraw = effect.cardsToDraw;
this.cardsToDiscard = effect.cardsToDiscard;
}
@Override
@ -58,8 +75,8 @@ public class DrawDiscardControllerEffect extends OneShotEffect<DrawDiscardContro
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player != null) {
player.drawCards(1, game);
player.discard(1, source, game);
player.drawCards(cardsToDraw, game);
player.discard(cardsToDiscard, source, game);
return true;
}
return false;

View file

@ -0,0 +1,85 @@
/*
* Copyright 2011 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.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
/**
*
* @author LevelX2
*/
public class DrawDiscardTargetEffect extends OneShotEffect<DrawDiscardTargetEffect> {
private int cardsToDraw;
private int cardsToDiscard;
public DrawDiscardTargetEffect() {
this(1,1);
}
public DrawDiscardTargetEffect(int cardsToDraw, int cardsToDiscard) {
super(Outcome.DrawCard);
this.cardsToDraw = cardsToDraw;
this.cardsToDiscard = cardsToDiscard;
staticText = new StringBuilder("Target player draws ")
.append(cardsToDraw == 1?"a": CardUtil.numberToText(cardsToDraw))
.append(" card").append(cardsToDraw == 1?" ": "s")
.append(", then discard ")
.append(cardsToDiscard == 1?"a": CardUtil.numberToText(cardsToDiscard))
.append(" card").append(cardsToDiscard == 1?" ": "s").toString();
}
public DrawDiscardTargetEffect(final DrawDiscardTargetEffect effect) {
super(effect);
this.cardsToDraw = effect.cardsToDraw;
this.cardsToDiscard = effect.cardsToDiscard;
}
@Override
public DrawDiscardTargetEffect copy() {
return new DrawDiscardTargetEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(getTargetPointer().getFirst(game, source));
if (player != null) {
player.drawCards(cardsToDraw, game);
player.discard(cardsToDiscard, source, game);
return true;
}
return false;
}
}

View file

@ -86,8 +86,9 @@ public class PutLibraryIntoGraveTargetEffect extends OneShotEffect<PutLibraryInt
break;
}
}
return true;
}
return true;
return false;
}
@Override

View file

@ -75,7 +75,7 @@ public class SacrificeTargetEffect extends OneShotEffect<SacrificeTargetEffect>
@Override
public String getText(Mode mode) {
if ("".equals(staticText) && !mode.getTargets().isEmpty()) {
if (staticText.isEmpty() && !mode.getTargets().isEmpty()) {
if (mode.getTargets().get(0).getNumberOfTargets() == 1) {
return "The controller of target " + mode.getTargets().get(0).getTargetName() + " sacrifices it";
} else {

View file

@ -28,12 +28,13 @@
package mage.abilities.effects.common.continious;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.cards.Card;
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.game.permanent.Permanent;
@ -44,6 +45,8 @@ import mage.game.permanent.Permanent;
public class GainAbilitySourceEffect extends ContinuousEffectImpl<GainAbilitySourceEffect> {
protected Ability ability;
// shall a card gain the ability (otherwise permanent)
private boolean onCard;
/**
* Add ability with Duration.WhileOnBattlefield
@ -54,14 +57,20 @@ public class GainAbilitySourceEffect extends ContinuousEffectImpl<GainAbilitySou
}
public GainAbilitySourceEffect(Ability ability, Duration duration) {
this(ability, duration, false);
}
public GainAbilitySourceEffect(Ability ability, Duration duration, boolean onCard) {
super(duration, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility);
this.ability = ability;
staticText = "{this} gains \"" + ability.getRule() + "\" " + duration.toString();
this.onCard = onCard;
}
public GainAbilitySourceEffect(final GainAbilitySourceEffect effect) {
super(effect);
this.ability = effect.ability.copy();
this.onCard = effect.onCard;
}
@Override
@ -71,10 +80,20 @@ public class GainAbilitySourceEffect extends ContinuousEffectImpl<GainAbilitySou
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null) {
permanent.addAbility(ability, game);
return true;
if (onCard) {
Card card = game.getCard(source.getSourceId());
if (card != null) {
// add ability to card only once
card.addAbility(ability);
discard();
return true;
}
} else {
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null) {
permanent.addAbility(ability, source.getSourceId(), game);
return true;
}
}
return false;
}

View file

@ -40,6 +40,7 @@ import mage.game.permanent.Permanent;
import mage.target.Target;
import java.util.UUID;
import mage.cards.Card;
/**
*
@ -48,23 +49,29 @@ import java.util.UUID;
public class GainAbilityTargetEffect extends ContinuousEffectImpl<GainAbilityTargetEffect> {
protected Ability ability;
// shall a card gain the ability (otherwise permanent)
private boolean onCard;
public GainAbilityTargetEffect(Ability ability, Duration duration) {
super(duration, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA,
ability.getEffects().size() > 0 ? ability.getEffects().get(0).getOutcome() : Outcome.AddAbility);
this.ability = ability;
this(ability, duration, null);
}
public GainAbilityTargetEffect(Ability ability, Duration duration, String rule) {
this(ability, duration, rule, false);
}
public GainAbilityTargetEffect(Ability ability, Duration duration, String rule, boolean onCard) {
super(duration, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA,
ability.getEffects().size() > 0 ? ability.getEffects().get(0).getOutcome() : Outcome.AddAbility);
this.ability = ability;
staticText = rule;
this.onCard = onCard;
}
public GainAbilityTargetEffect(final GainAbilityTargetEffect effect) {
super(effect);
this.ability = effect.ability.copy();
this.onCard = effect.onCard;
}
@Override
@ -81,11 +88,24 @@ public class GainAbilityTargetEffect extends ContinuousEffectImpl<GainAbilityTar
@Override
public boolean apply(Game game, Ability source) {
int affectedTargets = 0;
for (UUID permanentId : targetPointer.getTargets(game, source)) {
Permanent permanent = game.getPermanent(permanentId);
if (permanent != null) {
permanent.addAbility(ability, source.getSourceId(), game);
affectedTargets++;
if (onCard) {
for (UUID cardId : targetPointer.getTargets(game, source)) {
Card card = game.getCard(cardId);
if (card != null) {
card.addAbility(ability);
affectedTargets++;
}
}
if (duration.equals(Duration.OneUse)) {
discard();
}
} else {
for (UUID permanentId : targetPointer.getTargets(game, source)) {
Permanent permanent = game.getPermanent(permanentId);
if (permanent != null) {
permanent.addAbility(ability, source.getSourceId(), game);
affectedTargets++;
}
}
}
return affectedTargets > 0;

View file

@ -33,6 +33,7 @@ import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.counters.Counter;
import mage.game.Game;
import mage.game.permanent.Permanent;
@ -44,8 +45,9 @@ import mage.players.Player;
public class AddCountersSourceEffect extends OneShotEffect<AddCountersSourceEffect> {
private Counter counter;
protected boolean informPlayers;
protected DynamicValue amount;
private boolean informPlayers;
private DynamicValue amount;
private boolean putOnCard;
public AddCountersSourceEffect(Counter counter) {
this(counter, false);
@ -55,17 +57,23 @@ public class AddCountersSourceEffect extends OneShotEffect<AddCountersSourceEffe
this(counter, new StaticValue(0), informPlayers);
}
public AddCountersSourceEffect(Counter counter, DynamicValue amount, boolean informPlayers) {
this(counter, amount, informPlayers, false);
}
/**
*
* @param counter
* @param amount this amount will be added to the counter instances
* @param informPlayers
* @param informPlayers
* @param putOnCard - counters have to be put on a card instead of a permanent
*/
public AddCountersSourceEffect(Counter counter, DynamicValue amount, boolean informPlayers) {
public AddCountersSourceEffect(Counter counter, DynamicValue amount, boolean informPlayers, boolean putOnCard) {
super(Outcome.Benefit);
this.counter = counter.copy();
this.informPlayers = informPlayers;
this.amount = amount;
this.putOnCard = putOnCard;
setText();
}
@ -76,25 +84,45 @@ public class AddCountersSourceEffect extends OneShotEffect<AddCountersSourceEffe
}
this.informPlayers = effect.informPlayers;
this.amount = effect.amount;
this.putOnCard = effect.putOnCard;
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null) {
if (counter != null) {
Counter newCounter = counter.copy();
newCounter.add(amount.calculate(game, source));
permanent.addCounters(newCounter, game);
if (informPlayers) {
Player player = game.getPlayer(source.getControllerId());
if (player != null) {
game.informPlayers(new StringBuilder(player.getName()).append(" puts ").append(newCounter.getCount()).append(" ").append(newCounter.getName().toLowerCase()).append(" counter on ").append(permanent.getName()).toString());
if (putOnCard) {
Card card = game.getCard(source.getSourceId());
if (card != null) {
if (counter != null) {
Counter newCounter = counter.copy();
newCounter.add(amount.calculate(game, source));
card.addCounters(newCounter, game);
if (informPlayers) {
Player player = game.getPlayer(source.getControllerId());
if (player != null) {
game.informPlayers(new StringBuilder(player.getName()).append(" puts ").append(newCounter.getCount()).append(" ").append(newCounter.getName().toLowerCase()).append(" counter on ").append(card.getName()).toString());
}
}
}
return true;
}
} else {
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null) {
if (counter != null) {
Counter newCounter = counter.copy();
newCounter.add(amount.calculate(game, source));
permanent.addCounters(newCounter, game);
if (informPlayers) {
Player player = game.getPlayer(source.getControllerId());
if (player != null) {
game.informPlayers(new StringBuilder(player.getName()).append(" puts ").append(newCounter.getCount()).append(" ").append(newCounter.getName().toLowerCase()).append(" counter on ").append(permanent.getName()).toString());
}
}
}
return true;
}
}
return true;
return false;
}
private void setText() {

View file

@ -0,0 +1,111 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.abilities.keyword;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.AdjustingSourceCosts;
import mage.cards.Card;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.FilterCard;
import mage.game.Game;
import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetCardInYourGraveyard;
import mage.util.CardUtil;
/**
* 702.64. Delve
*
* 702.64a Delve is a static ability that functions while the spell that has delve is on
* the stack. "Delve" means "As an additional cost to cast this spell, you may exile any
* number of cards from your graveyard. Each card exiled this way reduces the cost to cast
* this spell by {1}." Using the delve ability follows the rules for paying additional
* costs in rules 601.2b and 601.2e-g. #
*
* 702.64b Multiple instances of delve on the same spell are redundant.
*
* @author LevelX2
*
*/
public class DelveAbility extends SimpleStaticAbility implements AdjustingSourceCosts {
public DelveAbility() {
super(Zone.STACK, null);
this.setRuleAtTheTop(true);
}
public DelveAbility(final DelveAbility ability) {
super(ability);
}
@Override
public DelveAbility copy() {
return new DelveAbility(this);
}
@Override
public void adjustCosts(Ability ability, Game game) {
Player player = game.getPlayer(controllerId);
if (player == null || !(ability instanceof SpellAbility)) {
return;
}
Target target = new TargetCardInYourGraveyard(1, Integer.MAX_VALUE, new FilterCard());
target.setTargetName("cards to delve from your graveyard");
if (!target.canChoose(sourceId, controllerId, game)) {
return;
}
if (player.chooseUse(Outcome.Detriment, "Delve cards from your graveyard?", game)) {
player.chooseTarget(Outcome.Detriment, target, ability, game);
if (target.getTargets().size() > 0) {
int adjCost = 0;
for (UUID cardId: target.getTargets()) {
Card card = game.getCard(cardId);
if (card == null) {
continue;
}
card.moveToExile(null, null, this.getSourceId(), game);
++adjCost;
}
game.informPlayers(new StringBuilder(player.getName()).append(" delved ")
.append(adjCost).append(" creature").append(adjCost != 1?"s":"").append(" from his or her graveyard").toString());
CardUtil.adjustCost((SpellAbility)ability, adjCost);
}
}
}
@Override
public String getRule() {
return "Delve <i>(You may exile any number of cards from your graveyard as you cast this spell. It costs {1} less to cast for each card exiled this way.)</i>";
}
}

View file

@ -29,14 +29,15 @@
package mage.abilities.keyword;
import java.util.UUID;
import mage.constants.Outcome;
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.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.EntersTheBattlefieldEvent;
@ -52,18 +53,27 @@ public class EchoAbility extends TriggeredAbilityImpl<EchoAbility> {
protected UUID lastController;
protected boolean echoPaid;
protected String manaString;
protected Costs echoCosts = new CostsImpl();
private boolean manaEcho = true;
public EchoAbility(String manaString) {
super(Zone.BATTLEFIELD, new EchoEffect(new ManaCostsImpl(manaString)), false);
this.echoPaid = false;
this.manaString = manaString;
this.echoCosts.add(new ManaCostsImpl(manaString));
}
public EchoAbility(Cost echoCost) {
super(Zone.BATTLEFIELD, new EchoEffect(echoCost), false);
this.echoPaid = false;
this.echoCosts.add(echoCost);
this.manaEcho = false;
}
public EchoAbility(final EchoAbility ability) {
super(ability);
this.echoPaid = ability.echoPaid;
this.manaString = ability.manaString;
this.echoCosts = ability.echoCosts.copy();
this.manaEcho = ability.manaEcho;
}
@Override
@ -98,7 +108,15 @@ public class EchoAbility extends TriggeredAbilityImpl<EchoAbility> {
@Override
public String getRule() {
return "Echo " + manaString + " <i>(At the beginning of your upkeep, if this came under your control since the beginning of your last upkeep," + getEffects().getText(modes.getMode()) + ")</i>";
StringBuilder sb = new StringBuilder("Echo");
if (manaEcho) {
sb.append(" ");
} else {
sb.append("-");
}
sb.append(echoCosts.getText());
sb.append(" <i>(At the beginning of your upkeep, if this came under your control since the beginning of your last upkeep, sacrifice it unless you pay its echo cost.)</i>");
return sb.toString();
}
}
@ -137,7 +155,7 @@ class EchoEffect extends OneShotEffect<EchoEffect> {
return new EchoEffect(this);
}
@Override
@Override
public String getText(Mode mode) {
StringBuilder sb = new StringBuilder("sacrifice {this} unless you ");
String costText = cost.getText();
@ -145,8 +163,9 @@ class EchoEffect extends OneShotEffect<EchoEffect> {
sb.append(costText.substring(0, 1).toLowerCase());
sb.append(costText.substring(1));
}
else
else {
sb.append("pay ").append(costText);
}
return sb.toString();

View file

@ -0,0 +1,160 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.abilities.keyword;
import java.util.Iterator;
import mage.constants.Zone;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
import mage.abilities.costs.OptionalAdditionalCost;
import mage.abilities.costs.OptionalAdditionalCostImpl;
import mage.abilities.costs.OptionalAdditionalModeSourceCosts;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
/**
* 702.40. Entwine
*
* 702.40a Entwine is a static ability of modal spells (see rule 700.2) that functions
* while the spell is on the stack. "Entwine [cost]" means "You may choose all modes
* of this spell instead of just one. If you do, you pay an additional [cost]." Using
* the entwine ability follows the rules for choosing modes and paying additional costs
* in rules 601.2b and 601.2e-g.
*
* 702.40b If the entwine cost was paid, follow the text of each of the modes in the order
* written on the card when the spell resolves.
*
* @author LevelX2
*/
public class EntwineAbility extends StaticAbility<EntwineAbility> implements OptionalAdditionalModeSourceCosts {
private static final String keywordText = "Entwine";
private static final String reminderText = "<i> (Choose both if you pay the entwine cost.)</i>";
protected OptionalAdditionalCost additionalCost;
public EntwineAbility(String manaString) {
super(Zone.STACK, null);
this.additionalCost = new OptionalAdditionalCostImpl(keywordText, reminderText, new ManaCostsImpl(manaString));
}
public EntwineAbility(Cost cost) {
super(Zone.STACK, null);
this.additionalCost = new OptionalAdditionalCostImpl(keywordText, "-", reminderText, cost);
setRuleAtTheTop(true);
}
public EntwineAbility(final EntwineAbility ability) {
super(ability);
additionalCost = ability.additionalCost;
}
@Override
public EntwineAbility copy() {
return new EntwineAbility(this);
}
@Override
public void addCost(Cost cost) {
if (additionalCost != null) {
((Costs) additionalCost).add(cost);
}
}
public boolean isActivated() {
if (additionalCost != null) {
return additionalCost.isActivated();
}
return false;
}
public void resetCosts() {
if (additionalCost != null) {
additionalCost.reset();
}
}
@Override
public void addOptionalAdditionalModeCosts(Ability ability, Game game) {
if (ability instanceof SpellAbility) {
Player player = game.getPlayer(controllerId);
if (player != null) {
this.resetCosts();
if (additionalCost != null) {
if (player.chooseUse(Outcome.Benefit,new StringBuilder("Pay ").append(additionalCost.getText(false)).append(" ?").toString(), game)) {
additionalCost.activate();
for (Iterator it = ((Costs) additionalCost).iterator(); it.hasNext();) {
Cost cost = (Cost) it.next();
if (cost instanceof ManaCostsImpl) {
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
} else {
ability.getCosts().add(cost.copy());
}
}
ability.getModes().setMinModes(2);
ability.getModes().setMaxModes(2);
}
}
}
}
}
@Override
public String getRule() {
StringBuilder sb = new StringBuilder();
if (additionalCost != null) {
sb.append(additionalCost.getText(false));
sb.append(" ").append(additionalCost.getReminderText());
}
return sb.toString();
}
@Override
public String getCastMessageSuffix() {
if (additionalCost != null) {
return additionalCost.getCastSuffixMessage(0);
} else {
return "";
}
}
public String getReminderText() {
if (additionalCost != null) {
return additionalCost.getReminderText();
} else {
return "";
}
}
}

View file

@ -75,7 +75,7 @@ public class InfectAbility extends StaticAbility<InfectAbility> implements MageS
@Override
public String getRule() {
return "Infect";
return "Infect <i>(This creature deals damage to creatures in the form of -1/-1 counters and to players in the form of poison counters.)</i>";
}
@Override

View file

@ -31,8 +31,6 @@ package mage.abilities.keyword;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import mage.constants.Zone;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility;
@ -41,8 +39,11 @@ import mage.abilities.costs.Costs;
import mage.abilities.costs.OptionalAdditionalCost;
import mage.abilities.costs.OptionalAdditionalCostImpl;
import mage.abilities.costs.OptionalAdditionalSourceCosts;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.costs.mana.VariableManaCost;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.players.Player;
@ -88,6 +89,7 @@ public class KickerAbility extends StaticAbility<KickerAbility> implements Optio
protected String keywordText;
protected String reminderText;
protected List<OptionalAdditionalCost> kickerCosts = new LinkedList<OptionalAdditionalCost>();
private int xManaValue = 0;
public KickerAbility(String manaString) {
this(KICKER_KEYWORD, KICKER_REMINDER_MANA);
@ -112,6 +114,8 @@ public class KickerAbility extends StaticAbility<KickerAbility> implements Optio
this.kickerCosts = ability.kickerCosts;
this.keywordText = ability.keywordText;
this.reminderText = ability.reminderText;
this.xManaValue = ability.xManaValue;
}
@Override
@ -137,6 +141,10 @@ public class KickerAbility extends StaticAbility<KickerAbility> implements Optio
}
}
public int getXManaValue() {
return xManaValue;
}
public int getKickedCounter() {
int counter = 0;
for (OptionalAdditionalCost cost: kickerCosts) {
@ -178,7 +186,16 @@ public class KickerAbility extends StaticAbility<KickerAbility> implements Optio
for (Iterator it = ((Costs) kickerCost).iterator(); it.hasNext();) {
Cost cost = (Cost) it.next();
if (cost instanceof ManaCostsImpl) {
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
List<VariableManaCost> varCosts = ((ManaCostsImpl)cost).getVariableCosts();
if (!varCosts.isEmpty()) {
// use only first variable cost
xManaValue = game.getPlayer(this.controllerId).announceXMana(varCosts.get(0).getMinX(), Integer.MAX_VALUE, "Announce kicker value for " + varCosts.get(0).getText(), game, this);
// kicker variable X costs handled internally as multikicker with {1} cost (no multikicker on card)
game.informPlayers(new StringBuilder(game.getPlayer(this.controllerId).getName()).append(" announced a value of ").append(xManaValue).append(" for ").append(" kicker X ").toString());
ability.getManaCostsToPay().add(new GenericManaCost(xManaValue));
} else {
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
}
} else {
ability.getCosts().add(cost.copy());
}

View file

@ -1,8 +1,5 @@
package mage.abilities.keyword;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.StaticAbility;
@ -11,6 +8,9 @@ import mage.abilities.effects.EntersBattlefieldEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.cards.Card;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.filter.common.FilterArtifactPermanent;
import mage.filter.predicate.mageobject.CardTypePredicate;
@ -42,17 +42,30 @@ public class ModularAbility extends DiesTriggeredAbility {
filter.add(new CardTypePredicate(CardType.CREATURE));
}
private int amount;
private boolean sunburst;
public ModularAbility(Card card, int amount) {
this(card, amount, false);
}
public ModularAbility(Card card, int amount, boolean sunburst) {
super(new ModularDistributeCounterEffect(), true);
this.addTarget(new TargetArtifactPermanent(filter));
this.amount = amount;
card.addAbility(new ModularStaticAbility(amount));
this.sunburst = sunburst;
if (sunburst) {
Ability ability = new SunburstAbility(card);
ability.setRuleVisible(false);
card.addAbility(ability);
} else {
card.addAbility(new ModularStaticAbility(amount));
}
}
public ModularAbility(ModularAbility ability) {
super(ability);
this.amount = ability.amount;
this.sunburst = ability.sunburst;
}
@Override
@ -71,19 +84,31 @@ public class ModularAbility extends DiesTriggeredAbility {
@Override
public String getRule() {
return "Modular " + amount + " <i>(This enters the battlefield with " + amount + " +1/+1 counter on it. When it dies, you may put its +1/+1 counters on target artifact creature.)</i>";
StringBuilder sb = new StringBuilder("Modular");
if (sunburst) {
sb.append("-Sunburst <i>(This enters the battlefield with a +1/+1 counter on it for each color of mana spent to cast it. When it dies, you may put its +1/+1 counters on target artifact creature.)</i>");
} else {
sb.append(" ").append(amount).append(" <i>(This enters the battlefield with ")
.append(amount).append(" +1/+1 counter on it. When it dies, you may put its +1/+1 counters on target artifact creature.)</i>");
}
return sb.toString();
}
}
class ModularStaticAbility extends StaticAbility<ModularStaticAbility> {
private String ruleText;
public ModularStaticAbility(int amount) {
super(Zone.BATTLEFIELD, new EntersBattlefieldEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance(amount))));
ruleText = new StringBuilder("This enters the battlefield with ").append(amount).append(" +1/+1 counter on it.").toString();
this.setRuleVisible(false);
}
public ModularStaticAbility(final ModularStaticAbility ability) {
super(ability);
this.ruleText = ability.ruleText;
}
@Override
@ -93,7 +118,7 @@ class ModularStaticAbility extends StaticAbility<ModularStaticAbility> {
@Override
public String getRule() {
return "";
return ruleText;
}
}

View file

@ -0,0 +1,199 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.abilities.keyword;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility;
import mage.abilities.costs.AlternativeCost2;
import mage.abilities.costs.AlternativeCost2Impl;
import mage.abilities.costs.AlternativeSourceCosts;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.cards.Card;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.players.Player;
import mage.watchers.common.ProwlWatcher;
/**
* 702.74. Prowl #
*
* 702.74a Prowl is a static ability that functions on the stack. "Prowl [cost]" means
* "You may pay [cost] rather than pay this spell's mana cost if a player was dealt combat
* damage this turn by a source that, at the time it dealt that damage, was under your
* control and had any of this spell's creature types." Paying a spell's prowl cost follows
* the rules for paying alternative costs in rules 601.2b and 601.2e-g
*
* @author LevelX2
*/
public class ProwlAbility extends StaticAbility<ProwlAbility> implements AlternativeSourceCosts {
private static final String PROWL_KEYWORD = "Prowl";
private List<AlternativeCost2> prowlCosts = new LinkedList<AlternativeCost2>();
private String reminderText;
public ProwlAbility(Card card, String manaString) {
super(Zone.STACK, null);
setRuleAtTheTop(true);
name = PROWL_KEYWORD;
setReminderText(card);
this.addProwlCost(manaString);
card.addWatcher(new ProwlWatcher());
}
public ProwlAbility(final ProwlAbility ability) {
super(ability);
this.prowlCosts.addAll(ability.prowlCosts);
this.reminderText = ability.reminderText;
}
@Override
public ProwlAbility copy() {
return new ProwlAbility(this);
}
public final AlternativeCost2 addProwlCost(String manaString) {
AlternativeCost2 prowlCost = new AlternativeCost2Impl(PROWL_KEYWORD, reminderText, new ManaCostsImpl(manaString));
prowlCosts.add(prowlCost);
return prowlCost;
}
public void resetProwl() {
for (AlternativeCost2 cost: prowlCosts) {
cost.reset();
}
}
@Override
public boolean isActivated() {
for (AlternativeCost2 cost: prowlCosts) {
if(cost.isActivated()) {
return true;
}
}
return false;
}
@Override
public boolean askToActivateAlternativeCosts(Ability ability, Game game) {
if (ability instanceof SpellAbility) {
Player player = game.getPlayer(controllerId);
ProwlWatcher prowlWatcher = (ProwlWatcher) game.getState().getWatchers().get("Prowl");
Card card = game.getCard(ability.getSourceId());
if (player == null || prowlWatcher == null || card == null) {
throw new IllegalArgumentException("Params can't be null");
}
boolean canProwl = false;
if (prowlWatcher.getDamagingSubtypes(ability.getControllerId()) != null) {
for (String subtype : prowlWatcher.getDamagingSubtypes(ability.getControllerId())) {
if (card.getSubtype().contains(subtype)) {
canProwl = true;
break;
}
}
}
if (canProwl) {
this.resetProwl();
for (AlternativeCost2 prowlCost: prowlCosts) {
if (prowlCost.canPay(sourceId, controllerId, game) &&
player.chooseUse(Outcome.Benefit, new StringBuilder("Cast for ").append(PROWL_KEYWORD).append(" cost ").append(prowlCost.getText(true)).append(" ?").toString(), game)) {
prowlCost.activate();
ability.getManaCostsToPay().clear();
ability.getCosts().clear();
for (Iterator it = ((Costs) prowlCost).iterator(); it.hasNext();) {
Cost cost = (Cost) it.next();
if (cost instanceof ManaCostsImpl) {
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
} else {
ability.getCosts().add(cost.copy());
}
}
}
}
}
}
return isActivated();
}
@Override
public String getRule() {
StringBuilder sb = new StringBuilder();
int numberCosts = 0;
String remarkText = "";
for (AlternativeCost2 prowlCost: prowlCosts) {
if (numberCosts == 0) {
sb.append(prowlCost.getText(false));
remarkText = prowlCost.getReminderText();
} else {
sb.append(" and/or ").append(prowlCost.getText(true));
}
++numberCosts;
}
if (numberCosts == 1) {
sb.append(" ").append(remarkText);
}
return sb.toString();
}
@Override
public String getCastMessageSuffix() {
StringBuilder sb = new StringBuilder();
int position = 0;
for (AlternativeCost2 cost : prowlCosts) {
if (cost.isActivated()) {
sb.append(cost.getCastSuffixMessage(position));
++position;
}
}
return sb.toString();
}
private void setReminderText(Card card) {
StringBuilder sb = new StringBuilder("(You may cast this for its prowl cost if you dealt combat damage to a player this turn with a ");
int i = 0;
for (String subtype: card.getSubtype()) {
i++;
sb.append(subtype);
if (card.getSupertype().size() > 1 && i < card.getSupertype().size()) {
sb.append(" or ");
}
}
//private static final String REMINDER_TEXT = "{subtypes}.)";
reminderText = sb.toString();
}
}

View file

@ -67,9 +67,7 @@ public class RetraceAbility extends ActivatedAbilityImpl<RetraceAbility> {
@Override
public String getRule() {
StringBuilder sbRule = new StringBuilder("Retrace");
return sbRule.toString();
return "Retrace <i>(You may cast this card from your graveyard by discarding a land card in addition to paying its other costs.)</i>";
}
}
class RetraceEffect extends OneShotEffect<RetraceEffect> {

View file

@ -0,0 +1,208 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.abilities.keyword;
import java.util.Iterator;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
import mage.abilities.costs.CostsImpl;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.SpliceCardEffectImpl;
import mage.cards.Card;
import mage.cards.CardsImpl;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.SpellAbilityType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.stack.Spell;
import mage.players.Player;
/**
* 702.45. Splice
*
* 702.45a Splice is a static ability that functions while a card is in your hand.
* "Splice onto [subtype] [cost]" means "You may reveal this card from your hand
* as you cast a [subtype] spell. If you do, copy this card's text box onto that
* spell and pay [cost] as an additional cost to cast that spell." Paying a card's
* splice cost follows the rules for paying additional costs in rules 601.2b and
* 601.2e-g.
*
* Example: Since the card with splice remains in the player's hand, it can later
* be cast normally or spliced onto another spell. It can even be discarded to pay
* a "discard a card" cost of the spell it's spliced onto.
*
* 702.45b You can't choose to use a splice ability if you can't make the required
* choices (targets, etc.) for that card's instructions. You can't splice any one
* card onto the same spell more than once. If you're splicing more than one card
* onto a spell, reveal them all at once and choose the order in which their
* instructions will be followed. The instructions on the main spell have to be
* followed first.
*
* 702.45c The spell has the characteristics of the main spell, plus the text boxes
* of each of the spliced cards. The spell doesn't gain any other characteristics
* (name, mana cost, color, supertypes, card types, subtypes, etc.) of the spliced
* cards. Text copied onto the spell that refers to a card by name refers to the spell
* on the stack, not the card from which the text was copied.
*
* Example: Glacial Ray is a red card with splice onto Arcane that reads, "Glacial
* Ray deals 2 damage to target creature or player." Suppose Glacial Ray is spliced
* onto Reach Through Mists, a blue spell. The spell is still blue, and Reach Through
* Mists deals the damage. This means that the ability can target a creature with
* protection from red and deal 2 damage to that creature.
*
* 702.45d Choose targets for the added text normally (see rule 601.2c). Note that a
* spell with one or more targets will be countered if all of its targets are illegal
* on resolution.
*
* 702.45e The spell loses any splice changes once it leaves the stack (for example,
* when it's countered, it's exiled, or it resolves).
*
* Rulings
*
* You must reveal all of the cards you intend to splice at the same time. Each individual card can only be spliced once onto a spell.
* If you have more than one card with the same name in your hand, you may splice both of them onto the spell.
* A card with a splice ability can't be spliced onto itself because the spell is on the stack (and not in your hand) when you reveal the cards you want to splice onto it.
* The target for a card that's spliced onto a spell may be the same as the target chosen for the original spell or for another spliced-on card. (A recent change to the targeting rules allows this, but most other cards are unaffected by the change.)
* If you splice a targeted card onto an untargeted spell, the entire spell will be countered if the target isn't legal when the spell resolves.
* If you splice an untargeted card onto a targeted spell, the entire spell will be countered if the target isn't legal when the spell resolves.
* A spell is countered on resolution only if *all* of its targets are illegal (or the spell is countered by an effect).
*
* @author LevelX2
*/
public class SpliceOntoArcaneAbility extends SimpleStaticAbility {
private static final String KEYWORD_TEXT = "Splice onto Arcane";
private Costs spliceCosts = new CostsImpl();
private boolean nonManaCosts = false;
public SpliceOntoArcaneAbility(String manaString) {
super(Zone.HAND, new SpliceOntoArcaneEffect());
spliceCosts.add(new ManaCostsImpl(manaString));
}
public SpliceOntoArcaneAbility(Cost cost) {
super(Zone.HAND, new SpliceOntoArcaneEffect());
spliceCosts.add(cost);
nonManaCosts = true;
}
public SpliceOntoArcaneAbility(final SpliceOntoArcaneAbility ability) {
super(ability);
this.spliceCosts = ability.spliceCosts.copy();
this.nonManaCosts = ability.nonManaCosts;
}
@Override
public SimpleStaticAbility copy() {
return new SpliceOntoArcaneAbility(this);
}
public Costs getSpliceCosts() {
return spliceCosts;
}
@Override
public String getRule() {
StringBuilder sb = new StringBuilder();
sb.append(KEYWORD_TEXT).append(nonManaCosts?"-":" ");
sb.append(spliceCosts.getText()).append(nonManaCosts?". ":" ");
sb.append("<i>(As you cast an Arcane spell, you may reveal this card from your hand and pay its splice cost. If you do, add this card's effects to that spell.)</i>");
return sb.toString();
}
}
class SpliceOntoArcaneEffect extends SpliceCardEffectImpl<SpliceOntoArcaneEffect> {
public SpliceOntoArcaneEffect() {
super(Duration.WhileOnBattlefield, Outcome.Copy);
staticText = "Splice onto Arcane";
}
public SpliceOntoArcaneEffect(final SpliceOntoArcaneEffect effect) {
super(effect);
}
@Override
public SpliceOntoArcaneEffect copy() {
return new SpliceOntoArcaneEffect(this);
}
@Override
public boolean apply(Game game, Ability source, Ability abilityToModify) {
Player controller = game.getPlayer(source.getControllerId());
Card spliceCard = game.getCard(source.getSourceId());
if (spliceCard != null && controller != null) {
Spell spell = game.getStack().getSpell(abilityToModify.getId());
if (spell != null) {
SpellAbility splicedAbility = spliceCard.getSpellAbility().copy();
splicedAbility.setSpellAbilityType(SpellAbilityType.SPLICE);
splicedAbility.setSourceId(abilityToModify.getSourceId());
spell.addSpellAbility(splicedAbility);
for (Iterator it = ((SpliceOntoArcaneAbility) source).getSpliceCosts().iterator(); it.hasNext();) {
Cost cost = (Cost) it.next();
if (cost instanceof ManaCostsImpl) {
spell.getSpellAbility().getManaCostsToPay().add((ManaCostsImpl) cost.copy());
} else {
spell.getSpellAbility().getCosts().add(cost.copy());
}
}
}
return true;
}
return false;
}
@Override
public boolean applies(Ability abilityToModify, Ability source, Game game) {
MageObject object = game.getObject(abilityToModify.getSourceId());
if (object != null && object.getSubtype().contains("Arcane")) {
return spliceSpellCanBeActivated(source, game);
}
return false;
}
private boolean spliceSpellCanBeActivated(Ability source, Game game) {
// check if spell can be activated (protection problem not solved because effect will be used from the base spell?)
Card card = game.getCard(source.getSourceId());
if (card != null) {
return card.getSpellAbility().canActivate(source.getControllerId(), game);
}
return false;
}
}

View file

@ -33,6 +33,7 @@ import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.SunburstCount;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.counters.Counter;
@ -49,12 +50,18 @@ import mage.players.Player;
public class SunburstAbility extends EntersBattlefieldAbility{
public SunburstAbility(){
private final static String ruleCreature ="Sunburst <i>(This enters the battlefield with a +1/+1 counter on it for each color of mana spent to cast it.)</i>";
private final static String ruleNonCreature ="Sunburst <i>(This enters the battlefield with a charge counter on it for each color of mana spent to cast it.)</i>";
private boolean isCreature;
public SunburstAbility(Card card){
super(new SunburstEffect(),"");
isCreature = card.getCardType().contains(CardType.CREATURE);
}
public SunburstAbility(final SunburstAbility ability){
super(ability);
this.isCreature = ability.isCreature;
}
@ -65,7 +72,7 @@ public class SunburstAbility extends EntersBattlefieldAbility{
@Override
public String getRule() {
return "Sunburst";
return isCreature ? ruleCreature : ruleNonCreature;
}
@ -114,5 +121,4 @@ class SunburstEffect extends OneShotEffect<SunburstEffect> {
return new SunburstEffect(this);
}
}
}

View file

@ -28,6 +28,8 @@
package mage.abilities.keyword;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.constants.AsThoughEffectType;
import mage.constants.CardType;
@ -42,7 +44,6 @@ import mage.abilities.Ability;
import mage.abilities.ActivatedAbilityImpl;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.common.SuspendedCondition;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.decorator.ConditionalTriggeredAbility;
@ -139,44 +140,53 @@ import mage.players.Player;
public class SuspendAbility extends ActivatedAbilityImpl<SuspendAbility> {
private String ruleText;
private boolean gainedTemporary;
private boolean shortRule;
/**
* Gives the card the SuspendAbility
*
* @param suspend - amount of time counters
* @param cost - null is used for temporary gained suspend ability
* @param card - card that has the suspend ability
*/
public SuspendAbility(int suspend, ManaCost cost, Card card) {
this(suspend, cost, card, false);
}
public SuspendAbility(int suspend, ManaCost cost, Card card, boolean shortRule) {
super(Zone.HAND, new SuspendExileEffect(suspend), cost);
this.usesStack = false;
ruleText = new StringBuilder("Suspend ").append(suspend).append(" - ").append(cost.getText())
.append(" <i>(Rather than cast this card from your hand, pay ")
.append(cost.getText())
.append(" and exile it with ")
.append(suspend == 1 ? "a time counter":suspend + " time counters")
.append(" on it.")
.append(" At the beginning of your upkeep, remove a time counter. When the last is removed, cast it without paying its mana cost.")
.append(card.getCardType().contains(CardType.CREATURE)? " If you play it this way and it's a creature, it gains haste until you lose control of it.":"")
.append(")</i>")
.toString();
this.shortRule = shortRule;
StringBuilder sb = new StringBuilder("Suspend ");
if (cost != null) {
sb.append(suspend).append(" - ").append(cost.getText());
if (!shortRule) {
sb.append(" <i>(Rather than cast this card from your hand, pay ")
.append(cost.getText())
.append(" and exile it with ")
.append(suspend == 1 ? "a time counter":suspend + " time counters")
.append(" on it.")
.append(" At the beginning of your upkeep, remove a time counter. When the last is removed, cast it without paying its mana cost.")
.append(card.getCardType().contains(CardType.CREATURE)? " If you play it this way and it's a creature, it gains haste until you lose control of it.":"")
.append(")</i>");
}
} else {
gainedTemporary = true;
}
ruleText = sb.toString();
if (card.getManaCost().isEmpty()) {
setRuleAtTheTop(true);
}
// add triggered ability to remove the counter from the card
Ability ability = new ConditionalTriggeredAbility(
new BeginningOfUpkeepTriggeredAbility(Zone.EXILED, new RemoveCounterSourceEffect(CounterType.TIME.createInstance()), TargetController.YOU, false),
SuspendedCondition.getInstance(),
"At the beginning of your upkeep, if this card is suspended, remove a time counter from it.");
ability.setRuleVisible(false);
card.addAbility(ability);
// add triggered ability that casts the suspended card, if all counters are removed
}
card.addAbility(new SuspendBeginningOfUpkeepTriggeredAbility());
card.addAbility(new SuspendPlayCardAbility(card.getCardType().contains(CardType.CREATURE)));
// if it's a creature card, add Haste ability
if (card.getCardType().contains(CardType.CREATURE)) {
ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new GainHasteEffect(Duration.WhileOnBattlefield));
ability.setRuleVisible(false);
card.addAbility(ability);
}
}
public SuspendAbility(SuspendAbility ability) {
super(ability);
this.ruleText = ability.getRule();
this.gainedTemporary = ability.gainedTemporary;
this.shortRule = ability.shortRule;
}
@Override
@ -196,6 +206,10 @@ public class SuspendAbility extends ActivatedAbilityImpl<SuspendAbility> {
return ruleText;
}
public boolean isGainedTemporary() {
return gainedTemporary;
}
@Override
public SuspendAbility copy() {
return new SuspendAbility(this);
@ -252,6 +266,9 @@ class SuspendPlayCardAbility extends TriggeredAbilityImpl<SuspendPlayCardAbility
public SuspendPlayCardAbility(boolean isCreature) {
super(Zone.EXILED, new SuspendPlayCardEffect(isCreature));
if (isCreature) {
this.addEffect(new GainHasteEffect());
}
setRuleVisible(false);
}
@ -286,8 +303,7 @@ class SuspendPlayCardEffect extends OneShotEffect<SuspendPlayCardEffect> {
public SuspendPlayCardEffect(boolean isCreature) {
super(Outcome.PutCardInPlay);
this.staticText = new StringBuilder("play it without paying its mana cost if able. If you can't, it remains removed from the game")
.append(isCreature ? ". If you play it this way and it's a creature, it gains haste until you lose control of it":"").toString();
this.staticText = "play it without paying its mana cost if able. If you can't, it remains removed from the game";
}
public SuspendPlayCardEffect(final SuspendPlayCardEffect effect) {
@ -301,9 +317,33 @@ class SuspendPlayCardEffect extends OneShotEffect<SuspendPlayCardEffect> {
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
Card card = game.getCard(source.getSourceId());
if (player != null && card != null) {
// remove temporary suspend ability (used e.g. for Epochrasite)
List<Ability> abilitiesToRemove = new ArrayList<Ability>();
for (Ability ability : card.getAbilities()) {
if (ability instanceof SuspendAbility) {
if (((SuspendAbility)ability).isGainedTemporary()) {
abilitiesToRemove.add(ability);
}
}
}
if (!abilitiesToRemove.isEmpty()) {
for (Ability ability : card.getAbilities()) {
if (ability instanceof SuspendBeginningOfUpkeepTriggeredAbility || ability instanceof SuspendPlayCardAbility ) {
abilitiesToRemove.add(ability);
}
}
// remove the triggered abilities from the game
game.getState().resetTriggersForSourceId(card.getId());
// remove the continious effects from the game
game.getState().getContinuousEffects().removeGainedEffectsForSource(card.getId());
// remove the abilities from the card
card.getAbilities().removeAll(abilitiesToRemove);
}
// cast the card for free
player.cast(card.getSpellAbility(), game, true);
}
return false;
@ -314,8 +354,8 @@ class GainHasteEffect extends ContinuousEffectImpl<GainHasteEffect> {
private UUID suspendController;
public GainHasteEffect(Duration duration) {
super(duration, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility);
public GainHasteEffect() {
super(Duration.Custom, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility);
staticText = "If you play it this way and it's a creature, it gains haste until you lose control of it";
}
@ -336,14 +376,34 @@ class GainHasteEffect extends ContinuousEffectImpl<GainHasteEffect> {
}
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null) {
if (suspendController.equals(source.getControllerId()) && !used) { // used stores if the control changed
if (suspendController.equals(source.getControllerId())) {
permanent.addAbility(HasteAbility.getInstance(), source.getSourceId(), game);
return true;
} else {
used = true;
this.discard();
}
}
return false;
}
}
class SuspendBeginningOfUpkeepTriggeredAbility extends ConditionalTriggeredAbility {
public SuspendBeginningOfUpkeepTriggeredAbility() {
super(new BeginningOfUpkeepTriggeredAbility(Zone.EXILED, new RemoveCounterSourceEffect(CounterType.TIME.createInstance()), TargetController.YOU, false),
SuspendedCondition.getInstance(),
"At the beginning of your upkeep, if this card is suspended, remove a time counter from it.");
this.setRuleVisible(false);
}
public SuspendBeginningOfUpkeepTriggeredAbility(final SuspendBeginningOfUpkeepTriggeredAbility effect) {
super(effect);
}
@Override
public SuspendBeginningOfUpkeepTriggeredAbility copy() {
return new SuspendBeginningOfUpkeepTriggeredAbility(this);
}
}

View file

@ -28,10 +28,6 @@
package mage.abilities.keyword;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.TimingRule;
import mage.constants.Zone;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbilityImpl;
import mage.abilities.DelayedTriggeredAbility;
@ -39,8 +35,12 @@ import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect;
import mage.abilities.effects.common.ExileSourceEffect;
import mage.abilities.effects.common.continious.GainAbilitySourceEffect;
import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldEffect;
import mage.abilities.effects.common.continious.GainAbilitySourceEffect;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.TimingRule;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
@ -49,11 +49,19 @@ import mage.game.events.ZoneChangeEvent;
/**
*
* @author BetaSteward_at_googlemail.com
*
*
* 702.82. Unearth
*
* 702.82a Unearth is an activated ability that functions while the card with unearth
* is in a graveyard. "Unearth [cost]" means "[Cost]: Return this card from your graveyard
* to the battlefield. It gains haste. Exile it at the beginning of the next end step.
* If it would leave the battlefield, exile it instead of putting it anywhere else.
* Activate this ability only any time you could cast a sorcery."
*
*/
public class UnearthAbility extends ActivatedAbilityImpl<UnearthAbility> {
protected boolean unearthed;
public UnearthAbility(ManaCosts costs) {
super(Zone.GRAVEYARD, new ReturnSourceFromGraveyardToBattlefieldEffect(), costs);
this.timing = TimingRule.SORCERY;
@ -64,7 +72,6 @@ public class UnearthAbility extends ActivatedAbilityImpl<UnearthAbility> {
public UnearthAbility(final UnearthAbility ability) {
super(ability);
this.unearthed = ability.unearthed;
}
@Override
@ -72,13 +79,12 @@ public class UnearthAbility extends ActivatedAbilityImpl<UnearthAbility> {
return new UnearthAbility(this);
}
public boolean isUnearthed() {
return unearthed;
}
@Override
public String getRule() {
return "Unearth " + super.getRule();
StringBuilder sb = new StringBuilder("Unearth ").append(this.getManaCosts().getText());
sb.append(" <i>(").append(this.getManaCosts().getText());
sb.append(": Return this card from your graveyard to the battlefield. It gains haste. Exile it at the beginning of the next end step or if it would leave the battlefield. Unearth only as a sorcery.)");
return sb.toString();
}
}
@ -133,8 +139,9 @@ class UnearthLeavesBattlefieldEffect extends ReplacementEffectImpl<UnearthLeaves
public boolean applies(GameEvent event, Ability source, Game game) {
if (event.getType() == EventType.ZONE_CHANGE && event.getTargetId().equals(source.getSourceId())) {
ZoneChangeEvent zEvent = (ZoneChangeEvent)event;
if (zEvent.getFromZone() == Zone.BATTLEFIELD && zEvent.getToZone() != Zone.EXILED)
if (zEvent.getFromZone() == Zone.BATTLEFIELD && zEvent.getToZone() != Zone.EXILED) {
return true;
}
}
return false;
}
@ -149,5 +156,4 @@ class UnearthLeavesBattlefieldEffect extends ReplacementEffectImpl<UnearthLeaves
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
return apply(game, source);
}
}
}

View file

@ -0,0 +1,56 @@
/*
* 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.mana;
import mage.Mana;
import mage.abilities.costs.Cost;
import mage.constants.TimingRule;
import mage.constants.Zone;
/**
*
* @author LevelX2
*/
public class ActivateAsSorceryManaAbility extends SimpleManaAbility {
public ActivateAsSorceryManaAbility(Zone zone, Mana mana, Cost cost) {
super(zone, mana, cost);
timing = TimingRule.SORCERY;
}
public ActivateAsSorceryManaAbility(final ActivateAsSorceryManaAbility ability) {
super(ability);
}
@Override
public ActivateAsSorceryManaAbility copy() {
return new ActivateAsSorceryManaAbility(this);
}
}

View file

@ -402,6 +402,9 @@ public abstract class CardImpl<T extends CardImpl<T>> extends MageObjectImpl<T>
case LIBRARY:
game.getPlayer(ownerId).removeFromLibrary(this, game);
break;
case EXILED:
game.getExile().removeCard(this, game);
break;
default:
logger.warn("moveToExile, not fully implemented: from="+fromZone);
}

View file

@ -46,6 +46,10 @@ public abstract class DeckImporter {
public DeckCardLists importDeck(String file) {
File f = new File(file);
DeckCardLists deckList = new DeckCardLists();
if (!f.exists()) {
logger.warn("Deckfile " + file + " not found.");
return deckList;
}
lineCount = 0;
sbMessage.setLength(0);
try {

View file

@ -14,7 +14,8 @@ public enum EffectType {
ASTHOUGH("As Though Effect"),
RESTRICTION("Restriction Effect"),
REQUIREMENT("Requirement Effect"),
COSTMODIFICATION("Cost Modification Effect");
COSTMODIFICATION("Cost Modification Effect"),
SPLICE("Splice Card Effect");
private String text;

View file

@ -0,0 +1,42 @@
package mage.constants;
/**
* The time per player to have activity in a match.
* If time runs out for a player, he looses the currently running game of a match.
*
* @author LevelX2
*/
public enum MatchTimeLimit {
NONE(0,"None"),
MIN__10(600, "10 Minutes"),
MIN__15(900, "15 Minutes"),
MIN__20(1200, "20 Minutes"),
MIN__25(1500, "25 Minutes"),
MIN__30(1800, "30 Minutes"),
MIN__40(2400, "40 Minutes"),
MIN__50(3000, "50 Minutes"),
MIN__60(3600, "60 Minutes"),
MIN__90(5400, "90 Minutes"),
MIN_120(7200, "120 Minutes");
private int matchSeconds;
private String name;
MatchTimeLimit(int matchSeconds, String name) {
this.matchSeconds = matchSeconds;
this.name = name;
}
public int getTimeLimit() {
return matchSeconds;
}
public String getName() {
return name;
}
@Override
public String toString() {
return name;
}
}

View file

@ -30,6 +30,7 @@ package mage.filter.common;
import mage.constants.CardType;
import mage.filter.predicate.mageobject.CardTypePredicate;
import mage.filter.predicate.mageobject.SubtypePredicate;
/**
*
@ -44,6 +45,13 @@ public class FilterControlledCreaturePermanent extends FilterControlledPermanent
public FilterControlledCreaturePermanent(String name) {
super(name);
this.add(new CardTypePredicate(CardType.CREATURE));
}
public FilterControlledCreaturePermanent(String subtype, String name) {
super(name);
this.add(new CardTypePredicate(CardType.CREATURE));
this.add(new SubtypePredicate(subtype));
}
public FilterControlledCreaturePermanent(final FilterControlledCreaturePermanent filter) {

View file

@ -31,6 +31,7 @@ package mage.filter.common;
import mage.constants.CardType;
import mage.filter.FilterPermanent;
import mage.filter.predicate.mageobject.CardTypePredicate;
import mage.filter.predicate.mageobject.SubtypePredicate;
/**
*
@ -45,6 +46,12 @@ public class FilterCreaturePermanent extends FilterPermanent {
public FilterCreaturePermanent(String name) {
super(name);
this.add(new CardTypePredicate(CardType.CREATURE));
}
public FilterCreaturePermanent(String subtype, String name) {
super(name);
this.add(new CardTypePredicate(CardType.CREATURE));
this.add(new SubtypePredicate(subtype));
}
public FilterCreaturePermanent(final FilterCreaturePermanent filter) {

View file

@ -27,16 +27,16 @@
*/
package mage.filter.predicate.permanent;
import mage.cards.Card;
import mage.counters.CounterType;
import mage.filter.predicate.Predicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
/**
*
* @author jeff
*/
public class CounterPredicate implements Predicate<Permanent> {
public class CounterPredicate implements Predicate<Card> {
private final CounterType counter;
@ -45,7 +45,7 @@ public class CounterPredicate implements Predicate<Permanent> {
}
@Override
public boolean apply(Permanent input, Game game) {
public boolean apply(Card input, Game game) {
return input.getCounters().containsKey(counter);
}

View file

@ -28,9 +28,6 @@
package mage.game;
import mage.constants.MultiplayerAttackOption;
import mage.constants.RangeOfInfluence;
import mage.constants.Zone;
import mage.MageItem;
import mage.MageObject;
import mage.abilities.Ability;
@ -44,6 +41,10 @@ import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.decks.Deck;
import mage.choices.Choice;
import mage.constants.Duration;
import mage.constants.MultiplayerAttackOption;
import mage.constants.RangeOfInfluence;
import mage.constants.Zone;
import mage.game.combat.Combat;
import mage.game.command.Emblem;
import mage.game.events.GameEvent;
@ -65,7 +66,6 @@ import mage.util.functions.ApplyToPermanent;
import java.io.Serializable;
import java.util.*;
import mage.constants.Duration;
public interface Game extends MageItem, Serializable {
@ -225,4 +225,11 @@ public interface Game extends MageItem, Serializable {
// controlling the behaviour of replacement effects
void setScopeRelevant(boolean scopeRelevant);
public boolean getScopeRelevant();
// players' timers
void initTimer(UUID playerId);
void resumeTimer(UUID playerId);
void pauseTimer(UUID playerId);
int getPriorityTime();
void setPriorityTime(int priorityTime);
}

View file

@ -29,7 +29,6 @@
package mage.game;
import mage.Constants;
import mage.constants.CardType;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
@ -49,12 +48,14 @@ import mage.actions.impl.MageAction;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.cards.SplitCard;
import mage.cards.decks.Deck;
import mage.choices.Choice;
import mage.constants.*;
import mage.counters.CounterType;
import mage.filter.Filter;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.common.FilterPlaneswalkerPermanent;
import mage.filter.predicate.mageobject.CardTypePredicate;
import mage.filter.predicate.mageobject.NamePredicate;
@ -89,8 +90,6 @@ import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import mage.cards.SplitCard;
import mage.filter.common.FilterControlledCreaturePermanent;
public abstract class GameImpl<T extends GameImpl<T>> implements Game, Serializable {
@ -156,6 +155,8 @@ public abstract class GameImpl<T extends GameImpl<T>> implements Game, Serializa
// used to indicate that currently applied replacement effects have to check for scope relevance (614.12 13/01/18)
private boolean scopeRelevant = false;
private int priorityTime;
@Override
public abstract T copy();
@ -199,6 +200,7 @@ public abstract class GameImpl<T extends GameImpl<T>> implements Game, Serializa
this.stateCheckRequired = game.stateCheckRequired;
this.scorePlayer = game.scorePlayer;
this.scopeRelevant = game.scopeRelevant;
this.priorityTime = game.priorityTime;
}
@Override
@ -590,6 +592,9 @@ public abstract class GameImpl<T extends GameImpl<T>> implements Game, Serializa
protected void init(UUID choosingPlayerId, GameOptions gameOptions) {
for (Player player: state.getPlayers().values()) {
player.beginTurn(this);
if (priorityTime > 0) {
initTimer(player.getId());
}
}
if (startMessage == null || startMessage.isEmpty()) {
startMessage = "Game has started";
@ -1060,7 +1065,9 @@ public abstract class GameImpl<T extends GameImpl<T>> implements Game, Serializa
public void addTriggeredAbility(TriggeredAbility ability) {
if (ability instanceof TriggeredManaAbility || ability instanceof DelayedTriggeredManaAbility) {
// 20110715 - 605.4
ability.resolve(this);
Ability manaAbiltiy = ability.copy();
manaAbiltiy.activate(this, false);
manaAbiltiy.resolve(this);
}
else {
TriggeredAbility newAbility = (TriggeredAbility) ability.copy();
@ -1946,4 +1953,35 @@ public abstract class GameImpl<T extends GameImpl<T>> implements Game, Serializa
public void setStartMessage(String startMessage) {
this.startMessage = startMessage;
}
@Override
public void initTimer(UUID playerId) {
if (priorityTime > 0) {
tableEventSource.fireTableEvent(EventType.INIT_TIMER, playerId, null, this);
}
}
@Override
public void resumeTimer(UUID playerId) {
if (priorityTime > 0) {
tableEventSource.fireTableEvent(EventType.RESUME_TIMER, playerId, null, this);
}
}
@Override
public void pauseTimer(UUID playerId) {
if (priorityTime > 0) {
tableEventSource.fireTableEvent(EventType.PAUSE_TIMER, playerId, null, this);
}
}
@Override
public int getPriorityTime() {
return priorityTime;
}
@Override
public void setPriorityTime(int priorityTime) {
this.priorityTime = priorityTime;
}
}

View file

@ -46,7 +46,8 @@ import java.util.UUID;
public class TableEvent extends EventObject implements ExternalEvent, Serializable {
public enum EventType {
UPDATE, INFO, STATUS, REVEAL, LOOK, START_DRAFT, START_MATCH, SIDEBOARD, CONSTRUCT, SUBMIT_DECK, END, ERROR
UPDATE, INFO, STATUS, REVEAL, LOOK, START_DRAFT, START_MATCH, SIDEBOARD, CONSTRUCT, SUBMIT_DECK, END, ERROR,
INIT_TIMER, RESUME_TIMER, PAUSE_TIMER
}
private Game game;

View file

@ -162,6 +162,7 @@ public abstract class MatchImpl implements Match {
game.loadCards(matchPlayer.getDeck().getSideboard(), matchPlayer.getPlayer().getId());
game.addPlayer(matchPlayer.getPlayer(), matchPlayer.getDeck());
}
game.setPriorityTime(options.getPriorityTime());
}
protected void shufflePlayers() {

View file

@ -31,6 +31,7 @@ package mage.game.match;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import mage.constants.MatchTimeLimit;
import mage.constants.MultiplayerAttackOption;
import mage.constants.RangeOfInfluence;
@ -49,6 +50,10 @@ public class MatchOptions implements Serializable {
protected String deckType;
protected boolean limited;
protected List<String> playerTypes = new ArrayList<String>();
/**
* Time each player has during the game to play using his\her priority.
*/
protected MatchTimeLimit matchTimeLimit; // 0 = no priorityTime handling
public MatchOptions(String name, String gameType) {
this.name = name;
@ -90,6 +95,7 @@ public class MatchOptions implements Serializable {
public void setFreeMulligans(int freeMulligans) {
this.freeMulligans = freeMulligans;
}
public String getGameType() {
return gameType;
}
@ -117,4 +123,16 @@ public class MatchOptions implements Serializable {
public void setLimited(boolean limited) {
this.limited = limited;
}
public int getPriorityTime() {
if (matchTimeLimit == null) {
return MatchTimeLimit.NONE.getTimeLimit();
}
return matchTimeLimit.getTimeLimit();
}
public void setMatchTimeLimit(MatchTimeLimit matchTimeLimit) {
this.matchTimeLimit = matchTimeLimit;
}
}

View file

@ -49,6 +49,8 @@ public class Token extends MageObjectImpl<Token> {
protected String description;
private UUID lastAddedTokenId;
private int tokenType;
private int originalCardNumber;
private String originalExpansionSetCode;
public enum Type {
FIRST(1),
@ -146,4 +148,21 @@ public class Token extends MageObjectImpl<Token> {
public void setTokenType(int tokenType) {
this.tokenType = tokenType;
}
public int getOriginalCardNumber() {
return originalCardNumber;
}
public void setOriginalCardNumber(int originalCardNumber) {
this.originalCardNumber = originalCardNumber;
}
public String getOriginalExpansionSetCode() {
return originalExpansionSetCode;
}
public void setOriginalExpansionSetCode(String originalExpansionSetCode) {
this.originalExpansionSetCode = originalExpansionSetCode;
}
}

View file

@ -118,9 +118,21 @@ public class Spell<T extends Spell<T>> implements StackObject, Card {
public boolean activate(Game game, boolean noMana) {
if (!spellAbilities.get(0).activate(game, noMana)) {
return false;
}
// if there are more abilities (fused split spell) or first ability added new abilities (splice), activate the additional abilities
boolean ignoreAbility = true;
boolean payNoMana = noMana;
for (SpellAbility spellAbility: spellAbilities) {
if (!spellAbility.activate(game, noMana)) {
return false;
// costs for spliced abilities were added to main spellAbility, so pay no man for spliced abilities
payNoMana |= spellAbility.getSpellAbilityType().equals(SpellAbilityType.SPLICE);
if (ignoreAbility) {
ignoreAbility = false;
} else {
if (!spellAbility.activate(game, payNoMana)) {
return false;
}
}
}
return true;
@ -138,10 +150,15 @@ public class Spell<T extends Spell<T>> implements StackObject, Card {
result = false;
boolean legalParts = false;
for(SpellAbility spellAbility: this.spellAbilities) {
if (spellAbility.getTargets().stillLegal(spellAbility, game)) {
legalParts = true;
updateOptionalCosts(index);
result |= spellAbility.resolve(game);
for (UUID modeId :spellAbility.getModes().getSelectedModes()) {
spellAbility.getModes().setMode(spellAbility.getModes().get(modeId));
if (spellAbility.getTargets().stillLegal(spellAbility, game)) {
legalParts = true;
if (!spellAbility.getSpellAbilityType().equals(SpellAbilityType.SPLICE)) {
updateOptionalCosts(index);
}
result |= spellAbility.resolve(game);
}
}
index++;
}
@ -226,13 +243,16 @@ public class Spell<T extends Spell<T>> implements StackObject, Card {
String name = null;
if (object == null) {
Player targetPlayer = game.getPlayer(targetId);
if (targetPlayer != null) name = targetPlayer.getName();
if (targetPlayer != null) {
name = targetPlayer.getName();
}
} else {
name = object.getName();
}
if (name != null && player.chooseUse(spellAbility.getEffects().get(0).getOutcome(), "Change target from " + name + "?", game)) {
if (!player.chooseTarget(spellAbility.getEffects().get(0).getOutcome(), newTarget, spellAbility, game))
if (!player.chooseTarget(spellAbility.getEffects().get(0).getOutcome(), newTarget, spellAbility, game)) {
newTarget.addTarget(targetId, spellAbility, game, false);
}
}
else {
newTarget.addTarget(targetId, spellAbility, game, false);
@ -345,6 +365,10 @@ public class Spell<T extends Spell<T>> implements StackObject, Card {
return card.getOwnerId();
}
public void addSpellAbility(SpellAbility spellAbility) {
spellAbilities.add(spellAbility);
}
@Override
public void addAbility(Ability ability) {}

View file

@ -299,4 +299,18 @@ public interface Player extends MageItem, Copyable<Player> {
*
*/
void revealFaceDownCard(Card card, Game game);
/**
* Set seconds left to play the game.
*
* @return
*/
void setPriorityTimeLeft(int timeLeft);
/**
* Returns seconds left to play the game.
*
* @return
*/
int getPriorityTimeLeft();
}

View file

@ -28,8 +28,6 @@
package mage.players;
import mage.constants.*;
import mage.constants.Zone;
import mage.MageObject;
import mage.Mana;
import mage.abilities.*;
@ -46,10 +44,13 @@ import mage.actions.MageDrawAction;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.cards.SplitCard;
import mage.cards.decks.Deck;
import mage.constants.*;
import mage.counters.Counter;
import mage.counters.CounterType;
import mage.counters.Counters;
import mage.filter.FilterCard;
import mage.filter.common.FilterCreatureForCombat;
import mage.game.ExileZone;
import mage.game.Game;
@ -59,11 +60,13 @@ import mage.game.events.DamagedPlayerEvent;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.permanent.Permanent;
import mage.game.stack.Spell;
import mage.game.stack.StackAbility;
import mage.game.stack.StackObject;
import mage.players.net.UserData;
import mage.target.Target;
import mage.target.TargetAmount;
import mage.target.TargetCard;
import mage.target.common.TargetCardInLibrary;
import mage.target.common.TargetDiscard;
import mage.watchers.common.BloodthirstWatcher;
@ -72,11 +75,6 @@ import org.apache.log4j.Logger;
import java.io.Serializable;
import java.util.*;
import mage.cards.SplitCard;
import mage.filter.FilterCard;
import mage.game.stack.Spell;
import mage.target.TargetCard;
public abstract class PlayerImpl<T extends PlayerImpl<T>> implements Player, Serializable {
@ -108,6 +106,7 @@ public abstract class PlayerImpl<T extends PlayerImpl<T>> implements Player, Ser
protected boolean passedTurn;
protected int turns;
protected int storedBookmark = -1;
protected int priorityTimeLeft = Integer.MAX_VALUE;
/**
* This indicates that player passed all turns until his own turn starts.
@ -1793,4 +1792,14 @@ public abstract class PlayerImpl<T extends PlayerImpl<T>> implements Player, Ser
this.revealCards(name, cards, game);
}
}
@Override
public void setPriorityTimeLeft(int timeLeft) {
priorityTimeLeft = timeLeft;
}
@Override
public int getPriorityTimeLeft() {
return priorityTimeLeft;
}
}

View file

@ -27,15 +27,17 @@
*/
package mage.target.common;
import mage.constants.Zone;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import mage.abilities.Ability;
import mage.cards.Card;
import mage.constants.Zone;
import mage.filter.FilterCard;
import mage.game.ExileZone;
import mage.game.Game;
import mage.target.TargetCard;
import java.util.UUID;
/**
*
@ -44,26 +46,67 @@ import java.util.UUID;
public class TargetCardInExile extends TargetCard<TargetCardInExile> {
private UUID zoneId;
private boolean allExileZones;
public TargetCardInExile(FilterCard filter, UUID zoneId) {
this(1, 1, filter, zoneId);
}
public TargetCardInExile(int minNumTargets, int maxNumTargets, FilterCard filter, UUID zoneId) {
this(minNumTargets, maxNumTargets, filter, zoneId, false);
}
public TargetCardInExile(int minNumTargets, int maxNumTargets, FilterCard filter, UUID zoneId, boolean allExileZones) {
super(minNumTargets, maxNumTargets, Zone.EXILED, filter);
this.zoneId = zoneId;
this.allExileZones = allExileZones;
this.targetName = filter.getMessage();
}
public TargetCardInExile(final TargetCardInExile target) {
super(target);
this.zoneId = target.zoneId;
this.allExileZones = target.allExileZones;
}
@Override
@Override
public Set<UUID> possibleTargets(UUID sourceId, UUID sourceControllerId, Game game) {
if (allExileZones) {
Set<UUID> possibleTargets = new HashSet<UUID>();
for (Card card : game.getExile().getAllCards(game)) {
if (filter.match(card, sourceControllerId, game)) {
possibleTargets.add(card.getId());
}
}
return possibleTargets;
} else {
return super.possibleTargets(sourceId, sourceControllerId, game);
}
}
@Override
public boolean canChoose(UUID sourceId, UUID sourceControllerId, Game game) {
if (allExileZones) {
int numberTargets = 0;
for(ExileZone exileZone : game.getExile().getExileZones()) {
numberTargets += exileZone.count(filter, sourceId, sourceControllerId, game);
if (numberTargets >= this.minNumberOfTargets) {
return true;
}
}
return false;
} else {
return super.canChoose(sourceControllerId, game);
}
}
@Override
public boolean canTarget(UUID id, Ability source, Game game) {
Card card = game.getCard(id);
if (card != null && game.getState().getZone(card.getId()) == Zone.EXILED) {
if (allExileZones) {
return filter.match(card, source.getControllerId(), game);
}
ExileZone exile;
if (zoneId != null) {
exile = game.getExile().getExileZone(zoneId);

View file

@ -29,6 +29,7 @@
package mage.util;
import java.util.Iterator;
import java.util.UUID;
import mage.Mana;
import mage.abilities.Ability;
@ -43,6 +44,7 @@ import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.costs.mana.VariableManaCost;
import mage.cards.Card;
import mage.constants.CardType;
import mage.game.Game;
import mage.game.permanent.token.Token;
import mage.util.functions.CopyFunction;
import mage.util.functions.CopyTokenFunction;
@ -365,4 +367,25 @@ public class CardUtil {
}
return true;
}
/**
* Creates and saves a (card + zoneChangeCounter) specific exileId.
*
*
* @param game
* @param source - source ability
* @return - the specific UUID
*/
public static UUID getCardExileZoneId(Game game, Ability source) {
UUID exileId = null;
Card card = game.getCard(source.getSourceId());
if (card != null) {
exileId = (UUID) game.getState().getValue(new StringBuilder("SourceExileZone").append(source.getSourceId()).append(card.getZoneChangeCounter()).toString());
if (exileId == null) {
exileId = UUID.randomUUID();
game.getState().setValue(new StringBuilder("SourceExileZone").append(source.getSourceId()).append(card.getZoneChangeCounter()).toString(), exileId);
}
}
return exileId;
}
}

View file

@ -79,7 +79,8 @@ public class CopyTokenFunction implements Function<Token, Card> {
for (String type : sourceObj.getSupertype()) {
target.getSupertype().add(type);
}
//target.setExpansionSetCode(source.getExpansionSetCode());
target.setOriginalExpansionSetCode(source.getExpansionSetCode());
target.setOriginalCardNumber(source.getCardNumber());
target.getAbilities().clear();
for (Ability ability0 : sourceObj.getAbilities()) {

View file

@ -0,0 +1,97 @@
/*
* Copyright 2011 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.watchers.common;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import mage.constants.WatcherScope;
import mage.game.Game;
import mage.game.events.DamagedPlayerEvent;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.permanent.Permanent;
import mage.watchers.WatcherImpl;
/**
* Watcher stores with which creature subtypes a player made combat damage to
* other players during a turn.
*
* @author LevelX
*/
public class ProwlWatcher extends WatcherImpl<ProwlWatcher> {
private Map<UUID, Set<String>> damagingSubtypes = new HashMap<UUID, Set<String>>();
public ProwlWatcher() {
super("Prowl", WatcherScope.GAME);
}
public ProwlWatcher(final ProwlWatcher watcher) {
super(watcher);
for (Entry<UUID, Set<String>> entry : watcher.damagingSubtypes.entrySet()) {
damagingSubtypes.put(entry.getKey(), entry.getValue());
}
}
@Override
public ProwlWatcher copy() {
return new ProwlWatcher(this);
}
@Override
public void watch(GameEvent event, Game game) {
if (event.getType() == EventType.DAMAGED_PLAYER) {
DamagedPlayerEvent dEvent = (DamagedPlayerEvent) event;
if (dEvent.isCombatDamage()) {
Permanent creature = game.getPermanent(dEvent.getSourceId());
if (creature != null) {
Set<String> subtypes = damagingSubtypes.get(creature.getControllerId());
if (subtypes == null) {
subtypes = new LinkedHashSet<String>();
}
subtypes.addAll(creature.getSubtype());
damagingSubtypes.put(creature.getControllerId(), subtypes);
}
}
}
}
@Override
public void reset() {
super.reset();
damagingSubtypes.clear();
}
public Set<String> getDamagingSubtypes(UUID playerId) {
return damagingSubtypes.get(playerId);
}
}