fix conflicts

This commit is contained in:
Ingmar Goudt 2019-12-29 19:28:20 +01:00
commit ce23f6900d
2451 changed files with 84128 additions and 14873 deletions

View file

@ -1,10 +1,5 @@
package mage;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.abilities.costs.Cost;
@ -14,6 +9,11 @@ import mage.filter.Filter;
import mage.filter.FilterMana;
import mage.game.Game;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* @author nantuko
*/
@ -192,6 +192,7 @@ public class ConditionalMana extends Mana implements Serializable {
break;
case COLORLESS:
colorless += amount;
break;
case GENERIC:
generic += amount;
break;

View file

@ -18,6 +18,7 @@ import mage.util.SubTypeList;
import java.io.Serializable;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
public interface MageObject extends MageItem, Serializable {
@ -32,13 +33,13 @@ public interface MageObject extends MageItem, Serializable {
void setName(String name);
EnumSet<CardType> getCardType();
Set<CardType> getCardType();
SubTypeList getSubtype(Game game);
boolean hasSubtype(SubType subtype, Game game);
EnumSet<SuperType> getSuperType();
Set<SuperType> getSuperType();
Abilities<Ability> getAbilities();
@ -199,7 +200,7 @@ public interface MageObject extends MageItem, Serializable {
void setIsAllCreatureTypes(boolean value);
default void addCardTypes(EnumSet<CardType> cardType) {
default void addCardTypes(Set<CardType> cardType) {
getCardType().addAll(cardType);
}

View file

@ -31,10 +31,10 @@ public abstract class MageObjectImpl implements MageObject {
protected ObjectColor color;
protected ObjectColor frameColor;
protected FrameStyle frameStyle;
protected EnumSet<CardType> cardType = EnumSet.noneOf(CardType.class);
protected Set<CardType> cardType = EnumSet.noneOf(CardType.class);
protected SubTypeList subtype = new SubTypeList();
protected boolean isAllCreatureTypes;
protected EnumSet<SuperType> supertype = EnumSet.noneOf(SuperType.class);
protected Set<SuperType> supertype = EnumSet.noneOf(SuperType.class);
protected Abilities<Ability> abilities;
protected String text;
protected MageInt power;
@ -111,7 +111,7 @@ public abstract class MageObjectImpl implements MageObject {
}
@Override
public EnumSet<CardType> getCardType() {
public Set<CardType> getCardType() {
return cardType;
}
@ -121,7 +121,7 @@ public abstract class MageObjectImpl implements MageObject {
}
@Override
public EnumSet<SuperType> getSuperType() {
public Set<SuperType> getSuperType() {
return supertype;
}

View file

@ -1,5 +1,8 @@
package mage.abilities;
import java.io.Serializable;
import java.util.List;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostAdjuster;
@ -23,10 +26,6 @@ import mage.target.Targets;
import mage.target.targetadjustment.TargetAdjuster;
import mage.watchers.Watcher;
import java.io.Serializable;
import java.util.List;
import java.util.UUID;
/**
* Practically everything in the game is started from an Ability. This interface
* describes what an Ability is composed of at the highest level.
@ -47,8 +46,10 @@ public interface Ability extends Controllable, Serializable {
*
* @see mage.players.PlayerImpl#playAbility(mage.abilities.ActivatedAbility,
* mage.game.Game)
* @see mage.game.GameImpl#addTriggeredAbility(mage.abilities.TriggeredAbility)
* @see mage.game.GameImpl#addDelayedTriggeredAbility(mage.abilities.DelayedTriggeredAbility)
* @see
* mage.game.GameImpl#addTriggeredAbility(mage.abilities.TriggeredAbility)
* @see
* mage.game.GameImpl#addDelayedTriggeredAbility(mage.abilities.DelayedTriggeredAbility)
*/
void newId();
@ -57,8 +58,10 @@ public interface Ability extends Controllable, Serializable {
*
* @see mage.players.PlayerImpl#playAbility(mage.abilities.ActivatedAbility,
* mage.game.Game)
* @see mage.game.GameImpl#addTriggeredAbility(mage.abilities.TriggeredAbility)
* @see mage.game.GameImpl#addDelayedTriggeredAbility(mage.abilities.DelayedTriggeredAbility)
* @see
* mage.game.GameImpl#addTriggeredAbility(mage.abilities.TriggeredAbility)
* @see
* mage.game.GameImpl#addDelayedTriggeredAbility(mage.abilities.DelayedTriggeredAbility)
*/
void newOriginalId();
@ -264,15 +267,16 @@ public interface Ability extends Controllable, Serializable {
/**
* Activates this ability prompting the controller to pay any mandatory
*
* @param game A reference the {@link Game} for which this ability should be
* activated within.
* @param game A reference the {@link Game} for which this ability should be
* activated within.
* @param noMana Whether or not {@link ManaCosts} have to be paid.
* @return True if this ability was successfully activated.
* @see mage.players.PlayerImpl#cast(mage.abilities.SpellAbility,
* mage.game.Game, boolean)
* @see mage.players.PlayerImpl#playAbility(mage.abilities.ActivatedAbility,
* mage.game.Game)
* @see mage.players.PlayerImpl#triggerAbility(mage.abilities.TriggeredAbility,
* @see
* mage.players.PlayerImpl#triggerAbility(mage.abilities.TriggeredAbility,
* mage.game.Game)
*/
boolean activate(Game game, boolean noMana);
@ -286,7 +290,8 @@ public interface Ability extends Controllable, Serializable {
*
* @param game The {@link Game} for which this ability resolves within.
* @return Whether or not this ability successfully resolved.
* @see mage.players.PlayerImpl#playManaAbility(mage.abilities.mana.ManaAbility,
* @see
* mage.players.PlayerImpl#playManaAbility(mage.abilities.mana.ManaAbility,
* mage.game.Game)
* @see mage.players.PlayerImpl#specialAction(mage.abilities.SpecialAction,
* mage.game.Game)
@ -461,15 +466,6 @@ public interface Ability extends Controllable, Serializable {
*/
String getGameLogMessage(Game game);
/**
* Used to deactivate cost modification logic of ability activation for some
* special handling (e.g. FlashbackAbility gets cost modifiaction twice
* because of how it's handled now)
*
* @param active execute no cost modification
*/
void setCostModificationActive(boolean active);
boolean activateAlternateOrAdditionalCosts(MageObject sourceObject, boolean noMana, Player controller, Game game);
/**

View file

@ -1,5 +1,9 @@
package mage.abilities;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import mage.MageObject;
import mage.Mana;
import mage.abilities.costs.*;
@ -34,11 +38,6 @@ import mage.util.ThreadLocalStringBuilder;
import mage.watchers.Watcher;
import org.apache.log4j.Logger;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
/**
* @author BetaSteward_at_googlemail.com
*/
@ -57,7 +56,7 @@ public abstract class AbilityImpl implements Ability {
protected ManaCosts<ManaCost> manaCostsToPay;
protected Costs<Cost> costs;
protected Costs<Cost> optionalCosts;
protected Modes modes;
protected Modes modes; // access to it by GetModes only (it's can be override by some abilities)
protected Zone zone;
protected String name;
protected AbilityWord abilityWord;
@ -65,11 +64,10 @@ public abstract class AbilityImpl implements Ability {
protected boolean ruleAtTheTop = false;
protected boolean ruleVisible = true;
protected boolean ruleAdditionalCostsVisible = true;
protected boolean costModificationActive = true;
protected boolean activated = false;
protected boolean worksFaceDown = false;
protected int sourceObjectZoneChangeCounter;
protected List<Watcher> watchers = new ArrayList<>();
protected List<Watcher> watchers = new ArrayList<>(); // access to it by GetWatchers only (it's can be override by some abilities)
protected List<Ability> subAbilities = null;
protected boolean canFizzle = true;
protected TargetAdjuster targetAdjuster = null;
@ -101,7 +99,7 @@ public abstract class AbilityImpl implements Ability {
this.manaCostsToPay = ability.manaCostsToPay.copy();
this.costs = ability.costs.copy();
this.optionalCosts = ability.optionalCosts.copy();
for (Watcher watcher : ability.watchers) {
for (Watcher watcher : ability.getWatchers()) {
watchers.add(watcher.copy());
}
@ -115,7 +113,6 @@ public abstract class AbilityImpl implements Ability {
this.ruleAtTheTop = ability.ruleAtTheTop;
this.ruleVisible = ability.ruleVisible;
this.ruleAdditionalCostsVisible = ability.ruleAdditionalCostsVisible;
this.costModificationActive = ability.costModificationActive;
this.worksFaceDown = ability.worksFaceDown;
this.abilityWord = ability.abilityWord;
this.sourceObjectZoneChangeCounter = ability.sourceObjectZoneChangeCounter;
@ -254,14 +251,17 @@ public abstract class AbilityImpl implements Ability {
int xValue = this.getManaCostsToPay().getX();
this.getManaCostsToPay().clear();
VariableManaCost xCosts = new VariableManaCost();
xCosts.setAmount(xValue);
// no x events - rules from Unbound Flourishing:
// - Spells with additional costs that include X won't be affected by Unbound Flourishing. X must be in the spell's mana cost.
xCosts.setAmount(xValue, xValue, false);
this.getManaCostsToPay().add(xCosts);
} else {
this.getManaCostsToPay().clear();
}
}
if (modes.getAdditionalCost() != null) {
modes.getAdditionalCost().addOptionalAdditionalModeCosts(this, game);
if (getModes().getAdditionalCost() != null) {
getModes().getAdditionalCost().addOptionalAdditionalModeCosts(this, game);
}
// 20130201 - 601.2b
// If the spell has alternative or additional costs that will be paid as it's being cast such
@ -288,11 +288,13 @@ public abstract class AbilityImpl implements Ability {
if (getAbilityType() == AbilityType.SPELL && (getManaCostsToPay().isEmpty() && getCosts().isEmpty()) && !noMana) {
return false;
}
// 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 = handleManaXCosts(game, noMana, controller);
String announceString = handleOtherXCosts(game, controller);
// For effects from cards like Void Winnower x costs have to be set
if (this.getAbilityType() == AbilityType.SPELL
&& game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.CAST_SPELL_LATE, getId(), getSourceId(), getControllerId()), this)) {
@ -308,12 +310,12 @@ public abstract class AbilityImpl implements Ability {
// 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
// variable number of targets, the player announces how many targets they will choose before
// they announce 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
// or player must be chosen as a target, the player chooses targets so that they obey 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,
@ -342,23 +344,6 @@ public abstract class AbilityImpl implements Ability {
}
}
}
//20100716 - 601.2e
if (sourceObject != null) {
sourceObject.adjustCosts(this, game);
if (sourceObject instanceof Card) {
for (Ability ability : ((Card) sourceObject).getAbilities(game)) {
if (ability instanceof AdjustingSourceCosts) {
((AdjustingSourceCosts) ability).adjustCosts(this, game);
}
}
} else {
for (Ability ability : sourceObject.getAbilities()) {
if (ability instanceof AdjustingSourceCosts) {
((AdjustingSourceCosts) ability).adjustCosts(this, game);
}
}
}
}
// this is a hack to prevent mana abilities with mana costs from causing endless loops - pay other costs first
if (this instanceof ActivatedManaAbilityImpl && !costs.pay(this, game, sourceId, controllerId, noMana, null)) {
@ -367,10 +352,9 @@ public abstract class AbilityImpl implements Ability {
}
//20101001 - 601.2e
if (costModificationActive) {
if (sourceObject != null) {
sourceObject.adjustCosts(this, game); // still needed
game.getContinuousEffects().costModification(this, game);
} else {
costModificationActive = true;
}
UUID activatorId = controllerId;
@ -428,13 +412,31 @@ public abstract class AbilityImpl implements Ability {
@Override
public boolean activateAlternateOrAdditionalCosts(MageObject sourceObject, boolean noMana, Player controller, Game game) {
boolean canUseAlternativeCost = true;
boolean canUseAdditionalCost = true;
if (this instanceof SpellAbility) {
if (((SpellAbility) this).getSpellAbilityCastMode() != SpellAbilityCastMode.NORMAL) {
// A player can't apply two alternative methods of casting or two alternative costs to a single spell.
// So can only use alternate costs if the spell is cast in normal mode
return false;
// A player can't apply two alternative methods of casting or two alternative costs to a single spell.
switch (((SpellAbility) this).getSpellAbilityCastMode()) {
case FLASHBACK:
case MADNESS:
// from Snapcaster Mage:
// If you cast a spell from a graveyard using its flashback ability, you cant pay other alternative costs
// (such as that of Foil). (2018-12-07)
canUseAlternativeCost = false;
// You may pay any optional additional costs the spell has, such as kicker costs. You must pay any
// mandatory additional costs the spell has, such as that of Tormenting Voice. (2018-12-07)
canUseAdditionalCost = true;
break;
case NORMAL:
default:
canUseAlternativeCost = true;
canUseAdditionalCost = true;
break;
}
}
boolean alternativeCostisUsed = false;
if (sourceObject != null && !(sourceObject instanceof Permanent)) {
Abilities<Ability> abilities = null;
@ -443,10 +445,11 @@ public abstract class AbilityImpl implements Ability {
} else {
sourceObject.getAbilities();
}
if (abilities != null) {
for (Ability ability : abilities) {
// if cast for noMana no Alternative costs are allowed
if (!noMana && ability instanceof AlternativeSourceCosts) {
if (canUseAlternativeCost && !noMana && ability instanceof AlternativeSourceCosts) {
AlternativeSourceCosts alternativeSpellCosts = (AlternativeSourceCosts) ability;
if (alternativeSpellCosts.isAvailable(this, game)) {
if (alternativeSpellCosts.askToActivateAlternativeCosts(this, game)) {
@ -456,13 +459,14 @@ public abstract class AbilityImpl implements Ability {
}
}
}
if (ability instanceof OptionalAdditionalSourceCosts) {
if (canUseAdditionalCost && ability instanceof OptionalAdditionalSourceCosts) {
((OptionalAdditionalSourceCosts) ability).addOptionalAdditionalCosts(this, game);
}
}
}
// controller specific alternate spell costs
if (!noMana && !alternativeCostisUsed) {
if (canUseAlternativeCost && !noMana && !alternativeCostisUsed) {
if (this.getAbilityType() == AbilityType.SPELL
// 117.9a Only one alternative cost can be applied to any one spell as it's being cast.
// So an alternate spell ability can't be paid with Omniscience
@ -479,6 +483,7 @@ public abstract class AbilityImpl implements Ability {
}
}
}
return alternativeCostisUsed;
}
@ -499,7 +504,9 @@ public abstract class AbilityImpl implements Ability {
costs.add(fixedCost);
}
// set the xcosts to paid
variableCost.setAmount(xValue);
// no x events - rules from Unbound Flourishing:
// - Spells with additional costs that include X won't be affected by Unbound Flourishing. X must be in the spell's mana cost.
variableCost.setAmount(xValue, xValue, false);
((Cost) variableCost).setPaid();
String message = controller.getLogName() + " announces a value of " + xValue + " (" + variableCost.getActionText() + ')';
announceString.append(message);
@ -510,14 +517,14 @@ public abstract class AbilityImpl implements Ability {
/**
* 601.2b If a cost that will be paid as the spell is being cast includes
* Phyrexian mana symbols, the player announces whether he or she intends to
* pay 2 life or the corresponding colored mana cost for each of those
* symbols.
* Phyrexian mana symbols, the player announces whether they intend to pay 2
* life or the corresponding colored mana cost for each of those symbols.
*/
private void handlePhyrexianManaCosts(Game game, UUID sourceId, Player controller) {
Iterator<ManaCost> costIterator = manaCostsToPay.iterator();
while (costIterator.hasNext()) {
ManaCost cost = costIterator.next();
if (cost instanceof PhyrexianManaCost) {
PhyrexianManaCost phyrexianManaCost = (PhyrexianManaCost) cost;
PayLifeCost payLifeCost = new PayLifeCost(2);
@ -530,6 +537,13 @@ public abstract class AbilityImpl implements Ability {
}
}
public int handleManaXMultiplier(Game game, int value) {
// some spells can change X value without new pays (Unbound Flourishing doubles X)
GameEvent xEvent = GameEvent.getEvent(GameEvent.EventType.X_MANA_ANNOUNCE, getId(), getSourceId(), getControllerId(), value);
game.replaceEvent(xEvent, this);
return xEvent.getAmount();
}
/**
* Handles X mana costs and sets manaCostsToPay.
*
@ -546,16 +560,22 @@ public abstract class AbilityImpl implements Ability {
VariableManaCost variableManaCost = null;
for (ManaCost cost : manaCostsToPay) {
if (cost instanceof VariableManaCost) {
variableManaCost = (VariableManaCost) cost;
break; // only one VariableManCost per spell (or is it possible to have more?)
if (variableManaCost == null) {
variableManaCost = (VariableManaCost) cost;
} else {
// only one VariableManCost per spell (or is it possible to have more?)
logger.error("Variable mana cost allowes only in one instance per ability: " + this);
}
}
}
if (variableManaCost != null) {
int xValue;
if (!variableManaCost.isPaid()) { // should only happen for human players
int xValue;
int xValueMultiplier = handleManaXMultiplier(game, 1);
if (!noMana) {
xValue = controller.announceXMana(variableManaCost.getMinX(), variableManaCost.getMaxX(), "Announce the value for " + variableManaCost.getText(), game, this);
int amountMana = xValue * variableManaCost.getMultiplier();
xValue = controller.announceXMana(variableManaCost.getMinX(), variableManaCost.getMaxX(), xValueMultiplier,
"Announce the value for " + variableManaCost.getText(), game, this);
int amountMana = xValue * variableManaCost.getXInstancesCount();
StringBuilder manaString = threadLocalBuilder.get();
if (variableManaCost.getFilter() == null || variableManaCost.getFilter().isGeneric()) {
manaString.append('{').append(amountMana).append('}');
@ -584,7 +604,7 @@ public abstract class AbilityImpl implements Ability {
}
}
manaCostsToPay.add(new ManaCostsImpl(manaString.toString()));
manaCostsToPay.setX(amountMana);
manaCostsToPay.setX(xValue * xValueMultiplier, amountMana);
}
variableManaCost.setPaid();
}
@ -611,7 +631,7 @@ public abstract class AbilityImpl implements Ability {
@Override
public void setControllerId(UUID controllerId) {
this.controllerId = controllerId;
for (Watcher watcher : watchers) {
for (Watcher watcher : getWatchers()) {
watcher.setControllerId(controllerId);
}
@ -639,7 +659,7 @@ public abstract class AbilityImpl implements Ability {
subAbility.setSourceId(sourceId);
}
}
for (Watcher watcher : watchers) {
for (Watcher watcher : getWatchers()) {
watcher.setSourceId(sourceId);
}
@ -712,7 +732,7 @@ public abstract class AbilityImpl implements Ability {
watcher.setSourceId(this.sourceId);
watcher.setControllerId(this.controllerId);
watchers.add(watcher);
getWatchers().add(watcher);
}
@Override
@ -844,7 +864,7 @@ public abstract class AbilityImpl implements Ability {
if (getModes().getMode() != null) {
return getModes().getMode().getTargets();
}
return null;
return new Targets();
}
@Override
@ -1145,11 +1165,6 @@ public abstract class AbilityImpl implements Ability {
return sb.toString();
}
@Override
public void setCostModificationActive(boolean active) {
this.costModificationActive = active;
}
@Override
public boolean getWorksFaceDown() {
return worksFaceDown;

View file

@ -1,4 +1,3 @@
package mage.abilities;
import java.util.UUID;
@ -62,14 +61,6 @@ public interface ActivatedAbility extends Ability {
@Override
ActivatedAbility copy();
/**
* Set a flag to know, that the ability is only created adn used to check
* what's playbable for the player.
*/
void setCheckPlayableMode();
boolean isCheckPlayableMode();
void setMaxActivationsPerTurn(int maxActivationsPerTurn);
int getMaxActivationsPerTurn(Game game);

View file

@ -46,11 +46,9 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
protected TimingRule timing = TimingRule.INSTANT;
protected TargetController mayActivate = TargetController.YOU;
protected UUID activatorId;
protected boolean checkPlayableMode;
protected ActivatedAbilityImpl(AbilityType abilityType, Zone zone) {
super(abilityType, zone);
this.checkPlayableMode = false;
}
public ActivatedAbilityImpl(final ActivatedAbilityImpl ability) {
@ -58,7 +56,6 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
timing = ability.timing;
mayActivate = ability.mayActivate;
activatorId = ability.activatorId;
checkPlayableMode = ability.checkPlayableMode;
maxActivationsPerTurn = ability.maxActivationsPerTurn;
condition = ability.condition;
}
@ -262,16 +259,6 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
this.timing = timing;
}
@Override
public void setCheckPlayableMode() {
checkPlayableMode = true;
}
@Override
public boolean isCheckPlayableMode() {
return checkPlayableMode;
}
protected boolean hasMoreActivationsThisTurn(Game game) {
if (getMaxActivationsPerTurn(game) == Integer.MAX_VALUE) {
return true;

View file

@ -1,4 +1,3 @@
package mage.abilities;
import mage.abilities.costs.OptionalAdditionalModeSourceCosts;
@ -10,6 +9,7 @@ import mage.filter.FilterPlayer;
import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetOpponent;
import mage.util.RandomUtil;
import java.util.*;
@ -19,15 +19,19 @@ import java.util.*;
public class Modes extends LinkedHashMap<UUID, Mode> {
private Mode currentMode; // the current mode of the selected modes
private final List<UUID> selectedModes = new ArrayList<>();
private final List<UUID> selectedModes = new ArrayList<>(); // all selected modes (this + duplicate)
private final Map<UUID, Mode> duplicateModes = new LinkedHashMap<>(); // for 2x selects: copy mode and put it to duplicate list
private final Map<UUID, UUID> duplicateToOriginalModeRefs = new LinkedHashMap<>(); // for 2x selects: stores ref from duplicate to original mode
private int minModes;
private int maxModes;
private TargetController modeChooser;
private boolean eachModeMoreThanOnce; // each mode can be selected multiple times during one choice
private boolean eachModeOnlyOnce; // state if each mode can be chosen only once as long as the source object exists
private final Map<UUID, Mode> duplicateModes = new LinkedHashMap<>();
private OptionalAdditionalModeSourceCosts optionalAdditionalModeSourceCosts = null; // only set if costs have to be paid
private Filter maxModesFilter = null; // calculates the max number of available modes
private boolean isRandom = false;
private String chooseText = null;
public Modes() {
this.currentMode = new Mode();
@ -45,22 +49,27 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
this.put(entry.getKey(), entry.getValue().copy());
}
for (Map.Entry<UUID, Mode> entry : modes.duplicateModes.entrySet()) {
this.put(entry.getKey(), entry.getValue().copy());
duplicateModes.put(entry.getKey(), entry.getValue().copy());
}
duplicateToOriginalModeRefs.putAll(modes.duplicateToOriginalModeRefs);
this.minModes = modes.minModes;
this.maxModes = modes.maxModes;
this.selectedModes.addAll(modes.getSelectedModes());
if (modes.getSelectedModes().isEmpty()) {
this.currentMode = values().iterator().next();
} else {
this.currentMode = get(modes.getMode().getId());
}
this.modeChooser = modes.modeChooser;
this.eachModeOnlyOnce = modes.eachModeOnlyOnce;
this.eachModeMoreThanOnce = modes.eachModeMoreThanOnce;
this.optionalAdditionalModeSourceCosts = modes.optionalAdditionalModeSourceCosts;
this.maxModesFilter = modes.maxModesFilter; // can't change so no copy needed
this.isRandom = modes.isRandom;
this.chooseText = modes.chooseText;
if (modes.getSelectedModes().isEmpty()) {
this.currentMode = values().iterator().next();
} else {
this.currentMode = get(modes.getMode().getId());
}
}
public Modes copy() {
@ -111,6 +120,32 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
return selectedModes;
}
public int getSelectedStats(UUID modeId) {
int count = 0;
if (this.selectedModes.contains(modeId)) {
// single select
count++;
// multiple select (all 2x select generate new duplicate mode)
UUID originalId;
if (this.duplicateModes.containsKey(modeId)) {
// modeId is duplicate
originalId = this.duplicateToOriginalModeRefs.get(modeId);
} else {
// modeId is original
originalId = modeId;
}
for (UUID id : this.duplicateToOriginalModeRefs.values()) {
if (id.equals(originalId)) {
count++;
}
}
}
return count;
}
public void setMinModes(int minModes) {
this.minModes = minModes;
}
@ -163,6 +198,12 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
if (this.size() > 1) {
this.selectedModes.clear();
this.duplicateModes.clear();
this.duplicateToOriginalModeRefs.clear();
if (this.isRandom) {
List<Mode> modes = getAvailableModes(source, game);
this.addSelectedMode(modes.get(RandomUtil.nextInt(modes.size())).getId());
return true;
}
// check if mode modifying abilities exist
Card card = game.getCard(source.getSourceId());
if (card != null) {
@ -276,9 +317,11 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
private void addSelectedMode(UUID modeId) {
if (selectedModes.contains(modeId) && eachModeMoreThanOnce) {
Mode duplicateMode = get(modeId).copy();
UUID originalId = modeId;
duplicateMode.setRandomId();
modeId = duplicateMode.getId();
duplicateModes.put(modeId, duplicateMode);
duplicateToOriginalModeRefs.put(duplicateMode.getId(), originalId);
}
this.selectedModes.add(modeId);
@ -319,7 +362,7 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
nonAvailableModes = getAlreadySelectedModes(source, game);
}
for (Mode mode : this.values()) {
if (isEachModeOnlyOnce() && nonAvailableModes != null && nonAvailableModes.contains(mode.getId())) {
if (isEachModeOnlyOnce() && nonAvailableModes.contains(mode.getId())) {
continue;
}
availableModes.add(mode);
@ -332,10 +375,14 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
return this.getMode().getEffects().getText(this.getMode());
}
StringBuilder sb = new StringBuilder();
if (this.getMaxModesFilter() != null) {
if (this.chooseText != null) {
sb.append(chooseText);
} else if (this.getMaxModesFilter() != null) {
sb.append("choose one or more. Each mode must target ").append(getMaxModesFilter().getMessage());
} else if (this.getMinModes() == 0 && this.getMaxModes() == 1) {
sb.append("choose up to one");
} else if (this.getMinModes() == 0 && this.getMaxModes() == 3) {
sb.append("choose any number");
} else if (this.getMinModes() == 1 && this.getMaxModes() > 2) {
sb.append("choose one or more");
} else if (this.getMinModes() == 1 && this.getMaxModes() == 2) {
@ -355,11 +402,13 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
}
if (isEachModeMoreThanOnce()) {
sb.append(". You may choose the same mode more than once.<br>");
} else {
sb.append(" &mdash;<br>");
sb.append(". You may choose the same mode more than once.");
} else if (chooseText == null) {
sb.append(" &mdash;");
}
sb.append("<br>");
for (Mode mode : this.values()) {
sb.append("&bull ");
sb.append(mode.getEffects().getTextStartingUpperCase(mode));
@ -399,4 +448,11 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
this.optionalAdditionalModeSourceCosts = optionalAdditionalModeSourceCosts;
}
public void setRandom(boolean isRandom) {
this.isRandom = isRandom;
}
public void setChooseText(String chooseText) {
this.chooseText = chooseText;
}
}

View file

@ -3,6 +3,7 @@
package mage.abilities;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
/**
@ -24,7 +25,7 @@ public class SpecialActions extends AbilitiesImpl<SpecialAction> {
* false = only non mana actions get returned
* @return
*/
public LinkedHashMap<UUID, SpecialAction> getControlledBy(UUID controllerId, boolean manaAction) {
public Map<UUID, SpecialAction> getControlledBy(UUID controllerId, boolean manaAction) {
LinkedHashMap<UUID, SpecialAction> controlledBy = new LinkedHashMap<>();
for (SpecialAction action: this) {
if (action.isControlledBy(controllerId) && action.isManaAction() == manaAction) {

View file

@ -1,5 +1,7 @@
package mage.abilities;
import java.util.Optional;
import java.util.UUID;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.costs.Cost;
@ -15,9 +17,6 @@ import mage.game.events.GameEvent;
import mage.game.stack.Spell;
import mage.players.Player;
import java.util.Optional;
import java.util.UUID;
/**
* @author BetaSteward_at_googlemail.com
*/
@ -55,18 +54,31 @@ public class SpellAbility extends ActivatedAbilityImpl {
this.cardName = ability.cardName;
}
/*
* 7/5/19 - jgray1206 - Moved null != game.getContinuesEffects()... into this method instead of having it in
* canActivate. There are abilities that directly use this method that should know when spells
* can be casted that are affected by the CastAsInstant effect.
* (i.e. Vizier of the Menagerie and issue #5816)
*/
public boolean spellCanBeActivatedRegularlyNow(UUID playerId, Game game) {
MageObject object = game.getObject(sourceId);
return timing == TimingRule.INSTANT
if (object == null) {
return false;
}
if (game.getState().getValue("PlayFromNotOwnHandZone" + object.getId()) != null) {
return (Boolean) game.getState().getValue("PlayFromNotOwnHandZone" + object.getId()); // card like Chandra, Torch of Defiance +1 loyal ability)
}
return null != game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.CAST_AS_INSTANT, this, playerId, game) // check this first to allow Offering in main phase
|| timing == TimingRule.INSTANT
|| object.hasAbility(FlashAbility.getInstance().getId(), game)
|| game.canPlaySorcery(playerId);
}
@Override
public ActivationStatus canActivate(UUID playerId, Game game) {
if (null != game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.CAST_AS_INSTANT, this, playerId, game) // check this first to allow Offering in main phase
|| this.spellCanBeActivatedRegularlyNow(playerId, game)) {
if (spellAbilityType == SpellAbilityType.SPLIT || spellAbilityType == SpellAbilityType.SPLIT_AFTERMATH) {
if (this.spellCanBeActivatedRegularlyNow(playerId, game)) {
if (spellAbilityType == SpellAbilityType.SPLIT
|| spellAbilityType == SpellAbilityType.SPLIT_AFTERMATH) {
return ActivationStatus.getFalse();
}
// fix for Gitaxian Probe and casting opponent's spells
@ -78,7 +90,7 @@ public class SpellAbility extends ActivatedAbilityImpl {
}
}
// Check if rule modifying events prevent to cast the spell in check playable mode
if (this.isCheckPlayableMode()) {
if (game.inCheckPlayableState()) {
if (game.getContinuousEffects().preventedByRuleModification(
GameEvent.getEvent(GameEvent.EventType.CAST_SPELL, this.getId(), this.getSourceId(), playerId), this, game, true)) {
return ActivationStatus.getFalse();
@ -87,7 +99,8 @@ public class SpellAbility extends ActivatedAbilityImpl {
// Alternate spell abilities (Flashback, Overload) can't be cast with no mana to pay option
if (getSpellAbilityType() == SpellAbilityType.BASE_ALTERNATE) {
Player player = game.getPlayer(playerId);
if (player != null && getSourceId().equals(player.getCastSourceIdWithAlternateMana())) {
if (player != null
&& player.getCastSourceIdWithAlternateMana().contains(getSourceId())) {
return ActivationStatus.getFalse();
}
}
@ -162,13 +175,15 @@ public class SpellAbility extends ActivatedAbilityImpl {
return 0;
}
// mana cost instances
for (ManaCost manaCost : card.getManaCost()) {
if (manaCost instanceof VariableManaCost) {
xMultiplier = ((VariableManaCost) manaCost).getMultiplier();
xMultiplier = ((VariableManaCost) manaCost).getXInstancesCount();
break;
}
}
// mana cost final X value
boolean hasNonManaXCost = false;
for (Cost cost : getCosts()) {
if (cost instanceof VariableCost) {
@ -184,6 +199,11 @@ public class SpellAbility extends ActivatedAbilityImpl {
return amount * xMultiplier;
}
public void setCardName(String cardName) {
this.cardName = cardName;
setSpellName();
}
private void setSpellName() {
switch (spellAbilityType) {
case SPLIT_FUSED:

View file

@ -1,14 +1,13 @@
package mage.abilities;
import java.util.UUID;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public abstract class StateTriggeredAbility extends TriggeredAbilityImpl {
@ -23,11 +22,7 @@ public abstract class StateTriggeredAbility extends TriggeredAbilityImpl {
public boolean canTrigger(Game game) {
//20100716 - 603.8
Boolean triggered = (Boolean) game.getState().getValue(getSourceId().toString() + "triggered");
if (triggered == null) {
triggered = Boolean.FALSE;
}
return !triggered;
return !Boolean.TRUE.equals(game.getState().getValue(getSourceId().toString() + "triggered"));
}
@Override

View file

@ -1,7 +1,5 @@
package mage.abilities;
import java.util.Locale;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.effects.Effect;
import mage.constants.AbilityType;
@ -13,8 +11,10 @@ import mage.game.events.GameEvent.EventType;
import mage.game.events.ZoneChangeEvent;
import mage.players.Player;
import java.util.Locale;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public abstract class TriggeredAbilityImpl extends AbilityImpl implements TriggeredAbility {
@ -111,7 +111,8 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge
|| ruleLow.startsWith("untap")
|| ruleLow.startsWith("put")
|| ruleLow.startsWith("remove")
|| ruleLow.startsWith("counter")) {
|| ruleLow.startsWith("counter")
|| ruleLow.startsWith("goad")) {
sb.append("you may ");
} else if (!ruleLow.startsWith("its controller may")) {
sb.append("you may have ");
@ -164,7 +165,7 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge
} else if (((ZoneChangeEvent) event).getTarget() != null) {
source = ((ZoneChangeEvent) event).getTarget();
} else {
source = game.getLastKnownInformation(getSourceId(), ((ZoneChangeEvent) event).getZone());
source = game.getLastKnownInformation(getSourceId(), event.getZone());
}
}

View file

@ -12,22 +12,29 @@ import mage.game.permanent.Permanent;
/**
* Constellation
*
*
* @author LevelX2
*/
public class ConstellationAbility extends TriggeredAbilityImpl {
private final boolean thisOr;
public ConstellationAbility(Effect effect) {
this(effect, false);
}
public ConstellationAbility(Effect effect, boolean optional) {
this(effect, optional, true);
}
public ConstellationAbility(Effect effect, boolean optional, boolean thisOr) {
super(Zone.BATTLEFIELD, effect, optional);
this.thisOr = thisOr;
}
public ConstellationAbility(final ConstellationAbility ability) {
super(ability);
this.thisOr = ability.thisOr;
}
@Override
@ -42,17 +49,17 @@ public class ConstellationAbility extends TriggeredAbilityImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (event.getPlayerId().equals(this.getControllerId())) {
Permanent permanent = game.getPermanent(event.getTargetId());
if (permanent != null && permanent.isEnchantment()) {
return true;
}
if (!event.getPlayerId().equals(this.getControllerId())) {
return false;
}
return false;
Permanent permanent = game.getPermanent(event.getTargetId());
return permanent != null && permanent.isEnchantment();
}
@Override
public String getRule() {
return new StringBuilder("<i>Constellation</i> &mdash; Whenever {this} or another enchantment enters the battlefield under your control, ").append(super.getRule()).toString();
return "<i>Constellation</i> &mdash; Whenever "
+ (thisOr ? "{this} or another" : "an")
+ " enchantment enters the battlefield under your control, " + super.getRule();
}
}

View file

@ -1,35 +1,42 @@
package mage.abilities.common;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.SetTargetPointer;
import mage.constants.Zone;
import mage.filter.FilterStackObject;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.StackObject;
import mage.target.targetpointer.FixedTarget;
/**
*
* @author North
*/
public class BecomesTargetTriggeredAbility extends TriggeredAbilityImpl {
private final FilterStackObject filter;
private final SetTargetPointer setTargetPointer;
public BecomesTargetTriggeredAbility(Effect effect) {
this(effect, StaticFilters.FILTER_SPELL_OR_ABILITY);
}
public BecomesTargetTriggeredAbility(Effect effect, FilterStackObject filter) {
this(effect, filter, SetTargetPointer.NONE);
}
public BecomesTargetTriggeredAbility(Effect effect, FilterStackObject filter, SetTargetPointer setTargetPointer) {
super(Zone.BATTLEFIELD, effect);
this.filter = filter.copy();
this.setTargetPointer = setTargetPointer;
}
public BecomesTargetTriggeredAbility(final BecomesTargetTriggeredAbility ability) {
super(ability);
this.filter = ability.filter.copy();
this.setTargetPointer = ability.setTargetPointer;
}
@Override
@ -45,7 +52,25 @@ public class BecomesTargetTriggeredAbility extends TriggeredAbilityImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
StackObject sourceObject = game.getStack().getStackObject(event.getSourceId());
return event.getTargetId().equals(getSourceId()) && filter.match(sourceObject, getSourceId(), getControllerId(), game);
if (!event.getTargetId().equals(getSourceId())
|| !filter.match(sourceObject, getSourceId(), getControllerId(), game)) {
return false;
}
switch (setTargetPointer) {
case PLAYER:
this.getEffects().stream()
.forEach(effect -> effect.setTargetPointer(
new FixedTarget(sourceObject.getControllerId(), game)
));
break;
case SPELL:
this.getEffects().stream()
.forEach(effect -> effect.setTargetPointer(
new FixedTarget(sourceObject.getId(), game)
));
break;
}
return true;
}
@Override

View file

@ -10,11 +10,9 @@ import mage.constants.TargetController;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
/**
*
* @author Jeff
*/
public class BeginningOfUntapTriggeredAbility extends TriggeredAbilityImpl {
@ -59,8 +57,8 @@ public class BeginningOfUntapTriggeredAbility extends TriggeredAbilityImpl {
}
return yours;
case NOT_YOU:
Player controller = game.getPlayer(this.getControllerId());
if (controller != null && controller.getInRange().contains(event.getPlayerId()) && !event.getPlayerId().equals(this.getControllerId())) {
if (game.getState().getPlayersInRange(this.getControllerId(), game).contains(event.getPlayerId())
&& !event.getPlayerId().equals(this.getControllerId())) {
if (getTargets().isEmpty()) {
for (Effect effect : this.getEffects()) {
effect.setTargetPointer(new FixedTarget(event.getPlayerId()));
@ -80,8 +78,7 @@ public class BeginningOfUntapTriggeredAbility extends TriggeredAbilityImpl {
}
break;
case ANY:
controller = game.getPlayer(this.getControllerId());
if (controller != null && controller.getInRange().contains(event.getPlayerId())) {
if (game.getState().getPlayersInRange(this.getControllerId(), game).contains(event.getPlayerId())) {
if (getTargets().isEmpty()) {
for (Effect effect : this.getEffects()) {
effect.setTargetPointer(new FixedTarget(event.getPlayerId()));
@ -89,6 +86,7 @@ public class BeginningOfUntapTriggeredAbility extends TriggeredAbilityImpl {
}
return true;
}
break;
}
return false;
}

View file

@ -1,7 +1,6 @@
package mage.abilities.common;
import java.util.Locale;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.TargetController;
@ -11,8 +10,9 @@ import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
import java.util.Locale;
/**
*
* @author Loki
*/
public class BeginningOfUpkeepTriggeredAbility extends TriggeredAbilityImpl {
@ -91,6 +91,7 @@ public class BeginningOfUpkeepTriggeredAbility extends TriggeredAbilityImpl {
}
break;
case ANY:
case ACTIVE:
if (setTargetPointer && getTargets().isEmpty()) {
for (Effect effect : this.getEffects()) {
effect.setTargetPointer(new FixedTarget(event.getPlayerId()));
@ -137,6 +138,8 @@ public class BeginningOfUpkeepTriggeredAbility extends TriggeredAbilityImpl {
return sb.insert(0, generateZoneString()).insert(0, "At the beginning of each opponent's upkeep, ").toString();
case ANY:
return sb.insert(0, generateZoneString()).insert(0, "At the beginning of each upkeep, ").toString();
case ACTIVE:
return sb.insert(0, generateZoneString()).insert(0, "At the beginning of each player's upkeep, ").toString();
case CONTROLLER_ATTACHED_TO:
return sb.insert(0, generateZoneString()).insert(0, "At the beginning of the upkeep of enchanted creature's controller, ").toString();
}

View file

@ -1,52 +1,28 @@
package mage.abilities.common;
import mage.abilities.SpellAbility;
import mage.abilities.costs.CostsImpl;
import mage.cards.Card;
import mage.constants.SpellAbilityType;
import mage.constants.TimingRule;
import mage.constants.Zone;
import mage.game.Game;
/**
* @author Plopman
* @author Plopman, JayDi85
*/
public class CastCommanderAbility extends SpellAbility {
public CastCommanderAbility(Card card) {
super(card.getManaCost(), card.getName(), Zone.COMMAND, SpellAbilityType.BASE);
if (card.getSpellAbility() != null) {
this.getCosts().addAll(card.getSpellAbility().getCosts().copy());
this.getEffects().addAll(card.getSpellAbility().getEffects().copy());
this.getTargets().addAll(card.getSpellAbility().getTargets().copy());
this.timing = card.getSpellAbility().getTiming();
} else {
this.costs = new CostsImpl<>();
this.timing = TimingRule.SORCERY;
}
this.usesStack = true;
this.controllerId = card.getOwnerId();
this.sourceId = card.getId();
private String ruleText;
public CastCommanderAbility(Card card, SpellAbility spellTemplate) {
super(spellTemplate);
this.newId();
this.setCardName(spellTemplate.getCardName());
this.zone = Zone.COMMAND;
this.spellAbilityType = spellTemplate.getSpellAbilityType();
this.ruleText = spellTemplate.getRule(); // need to support custom rule texts like OverloadAbility
}
public CastCommanderAbility(final CastCommanderAbility ability) {
super(ability);
}
@Override
public boolean activate(Game game, boolean noMana) {
if (super.activate(game, noMana)) {
// save amount of times commander was cast
Integer castCount = (Integer) game.getState().getValue(sourceId + "_castCount");
if (castCount == null) {
castCount = 1;
} else {
castCount++;
}
game.getState().setValue(sourceId + "_castCount", castCount);
return true;
}
return false;
this.ruleText = ability.ruleText;
}
@Override
@ -54,4 +30,9 @@ public class CastCommanderAbility extends SpellAbility {
return new CastCommanderAbility(this);
}
@Override
public String getRule() {
return ruleText;
}
}

View file

@ -30,7 +30,7 @@ public class ControllerDivideCombatDamageAbility extends StaticAbility implement
@Override
public String getRule() {
return "You may assign {this}'s combat damage divided as you choose among defending player and/or any number of creatures he or she controls.";
return "You may assign {this}'s combat damage divided as you choose among defending player and/or any number of creatures they control.";
}
@Override

View file

@ -14,7 +14,6 @@ import mage.game.events.GameEvent.EventType;
import mage.game.permanent.Permanent;
/**
*
* @author jeffwadsworth
*/
public class ControllerPlaysLandTriggeredAbility extends TriggeredAbilityImpl {
@ -35,7 +34,7 @@ public class ControllerPlaysLandTriggeredAbility extends TriggeredAbilityImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Permanent land = game.getPermanent(event.getTargetId());
return land.getControllerId().equals(controllerId);
return land != null && land.getControllerId().equals(controllerId);
}
@Override

View file

@ -1,15 +1,14 @@
package mage.abilities.common;
import mage.constants.Zone;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.DamagedCreatureEvent;
import mage.game.events.GameEvent;
/**
*
* @author LevelX2
*/
public class DealtDamageToSourceTriggeredAbility extends TriggeredAbilityImpl {
@ -18,16 +17,16 @@ public class DealtDamageToSourceTriggeredAbility extends TriggeredAbilityImpl {
private final boolean useValue;
private boolean usedForCombatDamageStep;
public DealtDamageToSourceTriggeredAbility(Zone zone, Effect effect, boolean optional) {
this(zone, effect, optional, false);
public DealtDamageToSourceTriggeredAbility(Effect effect, boolean optional) {
this(effect, optional, false);
}
public DealtDamageToSourceTriggeredAbility(Zone zone, Effect effect, boolean optional, boolean enrage) {
this(zone, effect, optional, enrage, false);
public DealtDamageToSourceTriggeredAbility(Effect effect, boolean optional, boolean enrage) {
this(effect, optional, enrage, false);
}
public DealtDamageToSourceTriggeredAbility(Zone zone, Effect effect, boolean optional, boolean enrage, boolean useValue) {
super(zone, effect, optional);
public DealtDamageToSourceTriggeredAbility(Effect effect, boolean optional, boolean enrage, boolean useValue) {
super(Zone.BATTLEFIELD, effect, optional);
this.enrage = enrage;
this.useValue = useValue;
this.usedForCombatDamageStep = false;

View file

@ -2,6 +2,7 @@ package mage.abilities.common;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.cards.Card;
import mage.constants.SetTargetPointer;
import mage.constants.Zone;
import mage.game.Game;
@ -33,7 +34,8 @@ public class DiesAttachedTriggeredAbility extends TriggeredAbilityImpl {
this(effect, attachedDescription, optional, diesRuleText, SetTargetPointer.NONE);
}
public DiesAttachedTriggeredAbility(Effect effect, String attachedDescription, boolean optional, boolean diesRuleText, SetTargetPointer setTargetPointer) {
public DiesAttachedTriggeredAbility(Effect effect, String attachedDescription, boolean optional,
boolean diesRuleText, SetTargetPointer setTargetPointer) {
super(Zone.ALL, effect, optional); // because the trigger only triggers if the object was attached, it doesn't matter where the Attachment was moved to (e.g. by replacement effect) after the trigger triggered, so Zone.all
this.attachedDescription = attachedDescription;
this.diesRuleText = diesRuleText;
@ -62,18 +64,27 @@ public class DiesAttachedTriggeredAbility extends TriggeredAbilityImpl {
if (((ZoneChangeEvent) event).isDiesEvent()) {
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
boolean triggered = false;
if (zEvent.getTarget() != null && zEvent.getTarget().getAttachments() != null && zEvent.getTarget().getAttachments().contains(this.getSourceId())) {
if (zEvent.getTarget() != null
&& zEvent.getTarget().getAttachments() != null
&& zEvent.getTarget().getAttachments().contains(this.getSourceId())) {
triggered = true;
} else {
// If both (attachment and attached went to graveyard at the same time, the attachemnets can be already removed from the attached object.)
// So check here with the LKI of the enchantment
// If the attachment and attachedTo went to graveyard at the same time, the trigger applies.
// If the attachment is removed beforehand, the trigger fails.
// IE: A player cast Planar Clensing. The attachment is Disenchanted in reponse
// and successfully removed from the attachedTo. The trigger fails.
Permanent attachment = game.getPermanentOrLKIBattlefield(getSourceId());
Card attachmentCard = game.getCard(getSourceId());
if (attachment != null
&& zEvent.getTargetId() != null && attachment.getAttachedTo() != null
&& zEvent.getTargetId() != null
&& attachment.getAttachedTo() != null
&& zEvent.getTargetId().equals(attachment.getAttachedTo())) {
Permanent attachedTo = game.getPermanentOrLKIBattlefield(attachment.getAttachedTo());
if (attachedTo != null
&& attachment.getAttachedToZoneChangeCounter() == attachedTo.getZoneChangeCounter(game)) { // zoneChangeCounter is stored in Permanent
&& game.getState().getZone(attachedTo.getId()) == (Zone.GRAVEYARD) // Demonic Vigor
&& attachmentCard != null
&& attachment.getAttachedToZoneChangeCounter() == attachedTo.getZoneChangeCounter(game)
&& attachment.getZoneChangeCounter(game) == attachmentCard.getZoneChangeCounter(game)) {
triggered = true;
}
}
@ -82,10 +93,13 @@ public class DiesAttachedTriggeredAbility extends TriggeredAbilityImpl {
for (Effect effect : getEffects()) {
if (zEvent.getTarget() != null) {
effect.setValue("attachedTo", zEvent.getTarget());
effect.setValue("zcc", zEvent.getTarget().getZoneChangeCounter(game) + 1); // zone change info from battlefield
if (setTargetPointer == SetTargetPointer.ATTACHED_TO_CONTROLLER) {
Permanent attachment = game.getPermanentOrLKIBattlefield(getSourceId());
if (attachment != null && attachment.getAttachedTo() != null) {
Permanent attachedTo = (Permanent) game.getLastKnownInformation(attachment.getAttachedTo(), Zone.BATTLEFIELD, attachment.getAttachedToZoneChangeCounter());
if (attachment != null
&& attachment.getAttachedTo() != null) {
Permanent attachedTo = (Permanent) game.getLastKnownInformation(attachment.getAttachedTo(),
Zone.BATTLEFIELD, attachment.getAttachedToZoneChangeCounter());
if (attachedTo != null) {
effect.setTargetPointer(new FixedTarget(attachedTo.getControllerId()));
}
@ -95,7 +109,6 @@ public class DiesAttachedTriggeredAbility extends TriggeredAbilityImpl {
}
return true;
}
}
return false;
}
@ -111,4 +124,4 @@ public class DiesAttachedTriggeredAbility extends TriggeredAbilityImpl {
sb.append(super.getRule());
return sb.toString();
}
}
}

View file

@ -1,4 +1,3 @@
package mage.abilities.common;
import mage.MageObject;
@ -34,11 +33,11 @@ public class DiesTriggeredAbility extends ZoneChangeTriggeredAbility {
if (before == null) {
return false;
}
if (!(before instanceof PermanentToken) && !this.hasSourceObjectAbility(game, before, event)) {
if (!this.hasSourceObjectAbility(game, before, event)) { // the permanent does not have the ability so no trigger
return false;
}
// check now it is in graveyard
if (before.getZoneChangeCounter(game) + 1 == game.getState().getZoneChangeCounter(sourceId)) {
// check now it is in graveyard if it is no token
if (!(before instanceof PermanentToken) && before.getZoneChangeCounter(game) + 1 == game.getState().getZoneChangeCounter(sourceId)) {
Zone after = game.getState().getZone(sourceId);
return after != null && Zone.GRAVEYARD.match(after);
} else {

View file

@ -0,0 +1,67 @@
package mage.abilities.common;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.watchers.common.CardsAmountDrawnThisTurnWatcher;
/**
* @author TheElk801
*/
public class DrawSecondCardTriggeredAbility extends TriggeredAbilityImpl {
private boolean triggeredOnce = false;
public DrawSecondCardTriggeredAbility(Effect effect, boolean optional) {
super(Zone.ALL, effect, optional);
this.addWatcher(new CardsAmountDrawnThisTurnWatcher());
}
private DrawSecondCardTriggeredAbility(final DrawSecondCardTriggeredAbility ability) {
super(ability);
this.triggeredOnce = ability.triggeredOnce;
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DREW_CARD
|| event.getType() == GameEvent.EventType.END_PHASE_POST;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (event.getType() == GameEvent.EventType.END_PHASE_POST) {
triggeredOnce = false;
return false;
}
if (event.getType() != GameEvent.EventType.DREW_CARD
|| !event.getPlayerId().equals(controllerId)
|| game.getPermanent(sourceId) == null) {
return false;
}
if (triggeredOnce) {
return false;
}
CardsAmountDrawnThisTurnWatcher watcher = game.getState().getWatcher(CardsAmountDrawnThisTurnWatcher.class);
if (watcher == null) {
return false;
}
if (watcher.getAmountCardsDrawn(controllerId) > 1) {
triggeredOnce = true;
return true;
}
return false;
}
@Override
public String getRule() {
return "Whenever you draw your second card each turn, " + super.getRule();
}
@Override
public DrawSecondCardTriggeredAbility copy() {
return new DrawSecondCardTriggeredAbility(this);
}
}

View file

@ -0,0 +1,40 @@
package mage.abilities.common;
import mage.abilities.effects.Effect;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
/**
* @author TheElk801
*/
public class EntersBattlefieldUntappedTriggeredAbility extends EntersBattlefieldTriggeredAbility {
public EntersBattlefieldUntappedTriggeredAbility(Effect effect, boolean optional) {
super(effect, optional);
this.noRule = true;
}
private EntersBattlefieldUntappedTriggeredAbility(final EntersBattlefieldUntappedTriggeredAbility ability) {
super(ability);
}
@Override
public EntersBattlefieldUntappedTriggeredAbility copy() {
return new EntersBattlefieldUntappedTriggeredAbility(this);
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (!super.checkTrigger(event, game)) {
return false;
}
Permanent permanent = game.getPermanent(event.getTargetId());
return permanent != null && !permanent.isTapped();
}
@Override
public String getRule() {
return "When {this} enters the battlefield untapped, " + super.getRule();
}
}

View file

@ -0,0 +1,88 @@
package mage.abilities.common;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.effects.EntersBattlefieldEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.keyword.EscapeAbility;
import mage.constants.AbilityType;
import mage.constants.Outcome;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.util.CardUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* @author TheElk801
*/
public class EscapesWithAbility extends EntersBattlefieldAbility {
private final int counters;
public EscapesWithAbility(int counters) {
super(new EscapesWithEffect(counters), false);
this.counters = counters;
}
private EscapesWithAbility(final EscapesWithAbility ability) {
super(ability);
this.counters = ability.counters;
}
@Override
public EscapesWithAbility copy() {
return new EscapesWithAbility(this);
}
@Override
public String getRule() {
return "{this} escapes with " + CardUtil.numberToText(counters, "a")
+ " +1/+1 counter" + (counters > 1 ? 's' : "") + " on it.";
}
}
class EscapesWithEffect extends OneShotEffect {
private final int counter;
EscapesWithEffect(int counter) {
super(Outcome.BoostCreature);
this.counter = counter;
}
private EscapesWithEffect(final EscapesWithEffect effect) {
super(effect);
this.counter = effect.counter;
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent == null && source.getAbilityType() == AbilityType.STATIC) {
permanent = game.getPermanentEntering(source.getSourceId());
}
if (permanent == null) {
return false;
}
SpellAbility spellAbility = (SpellAbility) getValue(EntersBattlefieldEffect.SOURCE_CAST_SPELL_ABILITY);
if (!(spellAbility instanceof EscapeAbility)
|| !spellAbility.getSourceId().equals(source.getSourceId())
|| permanent.getZoneChangeCounter(game) != spellAbility.getSourceObjectZoneChangeCounter()
|| !spellAbility.getSourceId().equals(source.getSourceId())) {
return false;
}
List<UUID> appliedEffects = (ArrayList<UUID>) this.getValue("appliedEffects");
permanent.addCounters(CounterType.P1P1.createInstance(counter), source, game, appliedEffects);
return true;
}
@Override
public EscapesWithEffect copy() {
return new EscapesWithEffect(this);
}
}

View file

@ -1,4 +1,3 @@
package mage.abilities.common;
import mage.MageObject;
@ -8,6 +7,7 @@ import mage.constants.SetTargetPointer;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
/**
@ -44,14 +44,18 @@ public class ExploitCreatureTriggeredAbility extends TriggeredAbilityImpl {
@Override
public boolean isInUseableZone(Game game, MageObject source, GameEvent event) {
if (event.getTargetId().equals(getSourceId()) && event.getSourceId().equals(getSourceId())) {
if (!this.hasSourceObjectAbility(game, source, event)) {
return false;
Permanent sourcePermanent = null;
if (game.getState().getZone(getSourceId()) == Zone.BATTLEFIELD) {
sourcePermanent = game.getPermanent(getSourceId());
} else {
if (game.getShortLivingLKI(getSourceId(), Zone.BATTLEFIELD)) {
sourcePermanent = (Permanent) game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD);
}
this.setControllerId(event.getPlayerId());
return true; // if Exploits creature sacrifices itself, exploit triggers
}
return super.isInUseableZone(game, source, event);
if (sourcePermanent == null) {
return false;
}
return hasSourceObjectAbility(game, sourcePermanent, event);
}
@Override

View file

@ -1,5 +1,6 @@
package mage.abilities.common;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
@ -10,6 +11,7 @@ import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
/**
@ -30,7 +32,8 @@ public class GodEternalDiesTriggeredAbility extends TriggeredAbilityImpl {
if (event.getType() == GameEvent.EventType.ZONE_CHANGE) {
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
return zEvent.getFromZone() == Zone.BATTLEFIELD
&& (zEvent.getToZone() == Zone.GRAVEYARD || zEvent.getToZone() == Zone.EXILED);
&& (zEvent.getToZone() == Zone.GRAVEYARD
|| zEvent.getToZone() == Zone.EXILED);
}
return false;
}
@ -46,6 +49,22 @@ public class GodEternalDiesTriggeredAbility extends TriggeredAbilityImpl {
return false;
}
@Override
public boolean isInUseableZone(Game game, MageObject source, GameEvent event) {
Permanent sourcePermanent = null;
if (game.getState().getZone(getSourceId()) == Zone.BATTLEFIELD) {
sourcePermanent = game.getPermanent(getSourceId());
} else {
if (game.getShortLivingLKI(getSourceId(), Zone.BATTLEFIELD)) {
sourcePermanent = (Permanent) game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD);
}
}
if (sourcePermanent == null) {
return false;
}
return hasSourceObjectAbility(game, sourcePermanent, event);
}
@Override
public GodEternalDiesTriggeredAbility copy() {
return new GodEternalDiesTriggeredAbility(this);
@ -53,8 +72,8 @@ public class GodEternalDiesTriggeredAbility extends TriggeredAbilityImpl {
@Override
public String getRule() {
return "When {this} dies or is put into exile from the battlefield, " +
"you may put it into its owner's library third from the top.";
return "When {this} dies or is put into exile from the battlefield, "
+ "you may put it into its owner's library third from the top.";
}
}
@ -89,4 +108,4 @@ class GodEternalEffect extends OneShotEffect {
}
return player.putCardOnTopXOfLibrary(card, game, source, 3);
}
}
}

View file

@ -1,4 +1,3 @@
package mage.abilities.common;
import mage.abilities.effects.Effect;

View file

@ -13,7 +13,6 @@ import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
/**
*
* @author jeffwadsworth
*/
public class OpponentPlaysLandTriggeredAbility extends TriggeredAbilityImpl {
@ -34,7 +33,7 @@ public class OpponentPlaysLandTriggeredAbility extends TriggeredAbilityImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Permanent land = game.getPermanent(event.getTargetId());
return game.getOpponents(controllerId).contains(land.getControllerId());
return land != null && game.getOpponents(controllerId).contains(land.getControllerId());
}
@Override

View file

@ -0,0 +1,24 @@
package mage.abilities.common;
import mage.abilities.PlayLandAbility;
import mage.constants.Zone;
/**
* @author JayDi85
*/
public class PlayLandAsCommanderAbility extends PlayLandAbility {
public PlayLandAsCommanderAbility(PlayLandAbility originalAbility) {
super(originalAbility);
zone = Zone.COMMAND;
}
private PlayLandAsCommanderAbility(PlayLandAsCommanderAbility ability) {
super(ability);
}
@Override
public PlayLandAsCommanderAbility copy() {
return new PlayLandAsCommanderAbility(this);
}
}

View file

@ -0,0 +1,61 @@
package mage.abilities.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.game.Game;
import mage.game.events.GameEvent;
import java.util.UUID;
/**
* For oathbreaker game mode
*
* @author JayDi85
*/
public class SignatureSpellCastOnlyWithOathbreakerEffect extends ContinuousRuleModifyingEffectImpl {
private final Condition condition;
private final UUID signatureSpell;
public SignatureSpellCastOnlyWithOathbreakerEffect(Condition condition, UUID signatureSpell) {
super(Duration.EndOfGame, Outcome.Detriment);
this.condition = condition;
this.signatureSpell = signatureSpell;
staticText = setText();
}
private SignatureSpellCastOnlyWithOathbreakerEffect(final SignatureSpellCastOnlyWithOathbreakerEffect effect) {
super(effect);
this.condition = effect.condition;
this.signatureSpell = effect.signatureSpell;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.CAST_SPELL;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (event.getSourceId().equals(signatureSpell)) {
return condition != null && !condition.apply(game, source);
}
return false; // cast not prevented by this effect
}
@Override
public SignatureSpellCastOnlyWithOathbreakerEffect copy() {
return new SignatureSpellCastOnlyWithOathbreakerEffect(this);
}
private String setText() {
StringBuilder sb = new StringBuilder("cast this spell only ");
if (condition != null) {
sb.append(' ').append(condition.toString());
}
return sb.toString();
}
}

View file

@ -1,5 +1,3 @@
package mage.abilities.common;
import mage.MageObject;
@ -14,7 +12,6 @@ import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
/**
*
* @author LevelX2
*/
@ -27,7 +24,7 @@ public class TurnedFaceUpAllTriggeredAbility extends TriggeredAbilityImpl {
this(effect, filter, false);
}
public TurnedFaceUpAllTriggeredAbility(Effect effect, FilterPermanent filter, boolean setTargetPointer) {
public TurnedFaceUpAllTriggeredAbility(Effect effect, FilterPermanent filter, boolean setTargetPointer) {
this(Zone.BATTLEFIELD, effect, filter, setTargetPointer, false);
}
@ -60,7 +57,7 @@ public class TurnedFaceUpAllTriggeredAbility extends TriggeredAbilityImpl {
if (!event.getTargetId().equals(getSourceId())) {
MageObject sourceObj = this.getSourceObject(game);
if (sourceObj != null) {
if (sourceObj instanceof Card && ((Card)sourceObj).isFaceDown(game)) {
if (sourceObj instanceof Card && ((Card) sourceObj).isFaceDown(game)) {
// if face down and it's not itself that is turned face up, it does not trigger
return false;
}
@ -70,9 +67,9 @@ public class TurnedFaceUpAllTriggeredAbility extends TriggeredAbilityImpl {
}
}
Permanent permanent = game.getPermanent(event.getTargetId());
if (filter.match(permanent, getSourceId(), getControllerId(), game)) {
if (permanent != null && filter.match(permanent, getSourceId(), getControllerId(), game)) {
if (setTargetPointer) {
for (Effect effect: getEffects()) {
for (Effect effect : getEffects()) {
effect.setTargetPointer(new FixedTarget(event.getTargetId()));
}
}

View file

@ -0,0 +1,57 @@
package mage.abilities.condition.common;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.constants.AbilityType;
import mage.constants.ColoredManaSymbol;
import mage.game.Game;
import mage.watchers.common.ManaSpentToCastWatcher;
import java.util.Arrays;
/**
* @author TheElk801
*/
public enum AdamantCondition implements Condition {
WHITE(ColoredManaSymbol.W),
BLUE(ColoredManaSymbol.U),
BLACK(ColoredManaSymbol.B),
RED(ColoredManaSymbol.R),
GREEN(ColoredManaSymbol.G),
ANY(null);
private final ColoredManaSymbol coloredManaSymbol;
private AdamantCondition(ColoredManaSymbol coloredManaSymbol) {
this.coloredManaSymbol = coloredManaSymbol;
}
@Override
public boolean apply(Game game, Ability source) {
if (source.getAbilityType() == AbilityType.SPELL) {
if (coloredManaSymbol == null) {
return Arrays
.stream(ColoredManaSymbol.values())
.map(source.getManaCostsToPay().getPayment()::getColor)
.anyMatch(i -> i > 2);
}
return source.getManaCostsToPay().getPayment().getColor(coloredManaSymbol) > 2;
}
ManaSpentToCastWatcher watcher = game.getState().getWatcher(ManaSpentToCastWatcher.class, source.getSourceId());
if (watcher == null) {
return false;
}
Mana payment = watcher.getAndResetLastPayment();
if (payment == null) {
return false;
}
if (coloredManaSymbol == null) {
return Arrays
.stream(ColoredManaSymbol.values())
.map(payment::getColor)
.anyMatch(i -> i > 2);
}
return payment.getColor(coloredManaSymbol) > 2;
}
}

View file

@ -1,4 +1,3 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
@ -20,7 +19,7 @@ public enum BuybackCondition implements Condition {
if (card != null) {
return card.getAbilities().stream()
.filter(a -> a instanceof BuybackAbility)
.anyMatch(Ability::isActivated);
.anyMatch(a -> ((BuybackAbility) a).isBuybackActivated(game));
}
return false;
}

View file

@ -1,8 +1,8 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.constants.CommanderCardType;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
@ -22,7 +22,7 @@ public enum CommanderInPlayCondition implements Condition {
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
for (UUID commanderId : controller.getCommandersIds()) {
for (UUID commanderId : game.getCommandersIds(controller, CommanderCardType.COMMANDER_OR_OATHBREAKER)) {
Permanent commander = game.getPermanent(commanderId);
if (commander != null && commander.isControlledBy(source.getControllerId())) {
return true;

View file

@ -0,0 +1,74 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.cards.Card;
import mage.filter.FilterMana;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.permanent.PermanentIdPredicate;
import mage.game.Game;
import mage.util.ManaUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
/**
* For Oathbreaker game mode
*
* @author JayDi85
*/
public class OathbreakerOnBattlefieldCondition implements Condition {
private UUID playerId;
private FilterControlledPermanent filter;
private String compatibleNames;
public OathbreakerOnBattlefieldCondition(Game game, UUID playerId, UUID signatureSpellId, Set<UUID> oathbreakersToSearch) {
this.playerId = playerId;
this.filter = new FilterControlledPermanent("oathbreaker on battlefield");
Card spell = game.getCard(signatureSpellId);
FilterMana spellColors = spell != null ? spell.getColorIdentity() : null;
// spell can be casted by any compatible oathbreakers
List<PermanentIdPredicate> compatibleList = new ArrayList<>();
List<String> compatibleNames = new ArrayList<>();
if (oathbreakersToSearch != null && !oathbreakersToSearch.isEmpty()) {
for (UUID id : oathbreakersToSearch) {
Card commander = game.getCard(id);
if (commander != null && ManaUtil.isColorIdentityCompatible(commander.getColorIdentity(), spellColors)) {
compatibleList.add(new PermanentIdPredicate(id));
compatibleNames.add(commander.getName());
}
}
}
this.compatibleNames = String.join("; ", compatibleNames);
if (compatibleList.isEmpty()) {
// random id to disable condition
this.filter.add(new PermanentIdPredicate(UUID.randomUUID()));
} else {
// oathbreaker on battlefield
this.filter.add(Predicates.or(compatibleList));
}
}
public String getCompatibleNames() {
return !this.compatibleNames.isEmpty() ? this.compatibleNames : "you haven't compatible oathbreaker";
}
@Override
public boolean apply(Game game, Ability source) {
// source.getSourceId() is null for commander's effects
int permanentsOnBattlefield = game.getBattlefield().count(this.filter, source.getSourceId(), playerId, game);
return permanentsOnBattlefield > 0;
}
@Override
public String toString() {
return filter.getMessage();
}
}

View file

@ -1,20 +1,23 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.game.Game;
import mage.watchers.common.PlayLandWatcher;
/**
* @author jeffwadsworth
*/
public enum PlayLandCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
PlayLandWatcher watcher = game.getState().getWatcher(PlayLandWatcher.class);
return watcher != null
&& watcher.landPlayed(source.getControllerId());
}
}
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.game.Game;
import mage.watchers.common.PlayLandWatcher;
/**
* @author jeffwadsworth
*/
public enum PlayLandCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
if (game.getTurn().getPhase() == null) { // only for getFrameColor for River of Tears before game started
return false;
}
PlayLandWatcher watcher = game.getState().getWatcher(PlayLandWatcher.class);
return watcher != null
&& watcher.landPlayed(source.getControllerId());
}
}

View file

@ -19,6 +19,7 @@ public enum RaidCondition implements Condition {
return watcher != null && watcher.getNumberOfAttackersCurrentTurn(source.getControllerId()) > 0;
}
@Override
public String toString() {
return "if you attacked with a creature this turn";
}

View file

@ -1,20 +0,0 @@
package mage.abilities.costs;
import mage.abilities.Ability;
import mage.game.Game;
/**
* Interface for abilities that adjust source and only source costs. For the
* cases when some permanent adjusts costs of other spells use
* {@link mage.abilities.effects.CostModificationEffect}.
*
* Example of such source costs adjusting:
* {@link mage.abilities.keyword.AffinityForArtifactsAbility}
*
* @author nantuko
*/
@FunctionalInterface
public interface AdjustingSourceCosts {
void adjustCosts(Ability ability, Game game);
}

View file

@ -27,7 +27,7 @@ public class AlternativeCost2Impl<T extends AlternativeCost2Impl<T>> extends Cos
this.name = name;
this.delimiter = delimiter;
if (reminderText != null) {
this.reminderText = new StringBuilder("<i>").append(reminderText).append("</i>").toString();
this.reminderText = "<i>" + reminderText + "</i>";
}
this.add(cost);
}

View file

@ -1,7 +1,6 @@
package mage.abilities.costs;
import java.util.Iterator;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility;
@ -16,13 +15,14 @@ import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
import java.util.Iterator;
/**
*
* @author LevelX2
*/
public class AlternativeCostSourceAbility extends StaticAbility implements AlternativeSourceCosts {
Costs<AlternativeCost2> alternateCosts = new CostsImpl<>();
private Costs<AlternativeCost2> alternateCosts = new CostsImpl<>();
protected Condition condition;
protected String rule;
protected FilterCard filter;
@ -46,14 +46,13 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
}
/**
*
* @param cost alternate cost to pay
* @param cost alternate cost to pay
* @param condition only if the condition is true it's possible to use the
* alternate costs
* @param rule if != null used as rule text
* @param filter filters the cards this alternate cost can be applied to
* @param onlyMana if true only the mana costs are replaced by this costs,
* other costs stay untouched
* alternate costs
* @param rule if != null used as rule text
* @param filter filters the cards this alternate cost can be applied to
* @param onlyMana if true only the mana costs are replaced by this costs,
* other costs stay untouched
*/
public AlternativeCostSourceAbility(Cost cost, Condition condition, String rule, FilterCard filter, boolean onlyMana) {
super(Zone.ALL, null);
@ -149,10 +148,9 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
if (!onlyMana) {
ability.getCosts().clear();
}
for (Cost cost : alternativeCostsToCheck) {
AlternativeCost2 alternateCost = (AlternativeCost2) cost;
for (AlternativeCost2 alternateCost : alternativeCostsToCheck) {
alternateCost.activate();
for (Iterator it = ((Costs) alternateCost).iterator(); it.hasNext();) {
for (Iterator it = ((Costs) alternateCost).iterator(); it.hasNext(); ) {
Cost costDeailed = (Cost) it.next();
if (costDeailed instanceof ManaCost) {
ability.getManaCostsToPay().add((ManaCost) costDeailed.copy());
@ -223,7 +221,8 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
if (alternativeCost.getCost() instanceof ManaCost) {
sb.append("pay ");
}
sb.append(alternativeCost.getText(true));
String text = alternativeCost.getText(true);
sb.append(Character.toLowerCase(text.charAt(0)) + text.substring(1));
}
++numberCosts;
}

View file

@ -14,7 +14,7 @@ import mage.game.Game;
public interface AlternativeSourceCosts {
/**
* Ask the player if he wants to use the alternative costs
* Ask the player if they want to use the alternative costs
*
* @param ability ability the alternative cost is activated for
* @param game

View file

@ -1,8 +1,6 @@
package mage.abilities.costs;
/**
*
* @author LevelX2
*/
public class OptionalAdditionalCostImpl extends CostsImpl<Cost> implements OptionalAdditionalCost {
@ -32,9 +30,10 @@ public class OptionalAdditionalCostImpl extends CostsImpl<Cost> implements Optio
super(cost);
this.name = cost.name;
this.reminderText = cost.reminderText;
this.delimiter = cost.delimiter;
this.activated = cost.activated;
this.activatedCounter = cost.activatedCounter;
this.delimiter = cost.delimiter;
this.repeatable = cost.repeatable;
}
@Override
@ -77,7 +76,7 @@ public class OptionalAdditionalCostImpl extends CostsImpl<Cost> implements Optio
* message.
*
* @param position - if there are multiple costs, it's the postion the cost
* is set (starting with 0)
* is set (starting with 0)
* @return
*/
@Override
@ -95,7 +94,6 @@ public class OptionalAdditionalCostImpl extends CostsImpl<Cost> implements Optio
/**
* If the player intends to pay the cost, the cost will be activated
*
*/
@Override
public void activate() {
@ -105,7 +103,6 @@ public class OptionalAdditionalCostImpl extends CostsImpl<Cost> implements Optio
/**
* Reset the activate and count information
*
*/
@Override
public void reset() {
@ -145,6 +142,7 @@ public class OptionalAdditionalCostImpl extends CostsImpl<Cost> implements Optio
/**
* Returns the number of times the cost was activated
*
* @return
*/
@Override

View file

@ -1,27 +1,27 @@
package mage.abilities.costs;
import mage.abilities.Ability;
import mage.game.Game;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public interface VariableCost {
/**
* Returns the variable amount if already set
*
*
* @return
*/
int getAmount();
/**
* Sets the variable amount
*
* @param amount
* @param xValue - value of X
* @param xPay - total value of pays for X (X * xMultiplier * xInstancesCount)
* @param isPayed - is that was real payed or just value setup
*/
void setAmount(int amount);
void setAmount(int xValue, int xPay, boolean isPayed);
/**
* returns the action text (e.g. "creature cards to exile from your hand", "life to pay")
@ -29,6 +29,7 @@ public interface VariableCost {
* @return
*/
String getActionText();
/**
* Return a min value to announce
*
@ -37,6 +38,7 @@ public interface VariableCost {
* @return
*/
int getMinValue(Ability source, Game game);
/**
* Returns a max value to announce
*
@ -45,13 +47,16 @@ public interface VariableCost {
* @return
*/
int getMaxValue(Ability source, Game game);
/**
* Asks the controller to announce the variable value
*
* @param source
* @param game
* @return
*/
int announceXValue(Ability source, Game game);
/**
* Returns a fixed cost with the announced variable value
*

View file

@ -1,7 +1,5 @@
package mage.abilities.costs;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.mana.ManaAbility;
import mage.game.Game;
@ -10,8 +8,9 @@ import mage.players.Player;
import mage.target.Target;
import mage.target.Targets;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public abstract class VariableCostImpl implements Cost, VariableCost {
@ -29,10 +28,9 @@ public abstract class VariableCostImpl implements Cost, VariableCost {
}
/**
*
* @param xText string for the defined value
* @param xText string for the defined value
* @param actionText what happens with the value (e.g. "to tap", "to exile
* from your graveyard")
* from your graveyard")
*/
public VariableCostImpl(String xText, String actionText) {
id = UUID.randomUUID();
@ -125,8 +123,8 @@ public abstract class VariableCostImpl implements Cost, VariableCost {
}
@Override
public void setAmount(int amount) {
amountPaid = amount;
public void setAmount(int xValue, int xPay, boolean isPayed) {
amountPaid = xPay;
}
@Override

View file

@ -40,9 +40,8 @@ public class DiscardSourceCost extends CostImpl {
Player player = game.getPlayer(controllerId);
if (player != null) {
Card card = player.getHand().get(sourceId, game);
if (card != null) {
paid = player.discard(card, null, game);
}
paid = player.discard(card, null, game);
}
return paid;
}

View file

@ -0,0 +1,44 @@
package mage.abilities.costs.common;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.game.Game;
import mage.util.ManaUtil;
import java.util.UUID;
public class DynamicValueGenericManaCost extends CostImpl {
DynamicValue amount;
public DynamicValueGenericManaCost(DynamicValue amount, String text) {
this.amount = amount;
setText(text);
}
public DynamicValueGenericManaCost(DynamicValueGenericManaCost cost) {
super(cost);
this.amount = cost.amount;
}
@Override
public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) {
Cost cost = ManaUtil.createManaCost(amount, game, ability, null);
return cost.canPay(ability, sourceId, controllerId, game);
}
@Override
public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana, Cost costToPay) {
Cost cost = ManaUtil.createManaCost(amount, game, ability, null);
paid = cost.pay(ability, game, sourceId, controllerId, noMana);
return paid;
}
@Override
public DynamicValueGenericManaCost copy() {
return new DynamicValueGenericManaCost(this);
}
}

View file

@ -1,8 +1,5 @@
package mage.abilities.costs.common;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
@ -16,8 +13,11 @@ import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetCardInHand;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public class ExileFromHandCost extends CostImpl {
@ -30,10 +30,9 @@ public class ExileFromHandCost extends CostImpl {
}
/**
*
* @param target
* @param setXFromCMC the spells X value on the stack is set to the
* converted mana costs of the exiled card
* converted mana costs of the exiled card
*/
public ExileFromHandCost(TargetCardInHand target, boolean setXFromCMC) {
this.addTarget(target);
@ -68,7 +67,9 @@ public class ExileFromHandCost extends CostImpl {
paid = true;
if (setXFromCMC) {
VariableManaCost vmc = new VariableManaCost();
vmc.setAmount(cmc);
// no x events - rules from Unbound Flourishing:
// - Spells with additional costs that include X won't be affected by Unbound Flourishing. X must be in the spell's mana cost.
vmc.setAmount(cmc, cmc, false);
vmc.setPaid();
ability.getManaCostsToPay().add(vmc);
}

View file

@ -36,8 +36,8 @@ public class PayLifeCost extends CostImpl {
public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) {
//118.4. If a cost or effect allows a player to pay an amount of life greater than 0,
//the player may do so only if their life total is greater than or equal to the
//amount of the payment. If a player pays life, the payment is subtracted from his or
//her life total; in other words, the player loses that much life. (Players can always pay 0 life.)
//amount of the payment. If a player pays life, the payment is subtracted from their
//life total; in other words, the player loses that much life. (Players can always pay 0 life.)
int lifeToPayAmount = amount.calculate(game, ability, null);
// Paying 0 life is not considered paying any life.
if (lifeToPayAmount > 0 && !game.getPlayer(controllerId).canPayLifeCost()) {

View file

@ -1,4 +1,3 @@
package mage.abilities.costs.mana;
import mage.Mana;
@ -12,6 +11,9 @@ public class GenericManaCost extends ManaCostImpl {
protected int mana;
/**
* warning, use ManaUtil.createManaCost to create generic cost
*/
public GenericManaCost(int mana) {
this.mana = mana;
this.cost = Mana.GenericMana(mana);
@ -42,7 +44,7 @@ public class GenericManaCost extends ManaCostImpl {
@Override
public void assignPayment(Game game, Ability ability, ManaPool pool, Cost costsToPay) {
this.assignGeneric(ability, game, pool, mana, costsToPay);
this.assignGeneric(ability, game, pool, mana, null, costsToPay);
}
@Override

View file

@ -1,9 +1,5 @@
package mage.abilities.costs.mana;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
@ -12,11 +8,16 @@ import mage.abilities.mana.ManaOptions;
import mage.constants.ColoredManaSymbol;
import mage.constants.ManaType;
import mage.filter.Filter;
import mage.filter.FilterMana;
import mage.game.Game;
import mage.players.ManaPool;
import mage.players.Player;
import mage.util.ManaUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public abstract class ManaCostImpl extends CostImpl implements ManaCost {
protected Mana payment;
@ -143,33 +144,49 @@ public abstract class ManaCostImpl extends CostImpl implements ManaCost {
}
}
protected boolean assignGeneric(Ability ability, Game game, ManaPool pool, int mana, Cost costToPay) {
int conditionalCount = pool.getConditionalCount(ability, game, null, costToPay);
protected boolean assignGeneric(Ability ability, Game game, ManaPool pool, int mana, FilterMana filterMana, Cost costToPay) {
int conditionalCount = pool.getConditionalCount(ability, game, filterMana, costToPay);
while (mana > payment.count() && (pool.count() > 0 || conditionalCount > 0)) {
if (pool.pay(ManaType.COLORLESS, ability, sourceFilter, game, costToPay, usedManaToPay)) {
// try to use different mana to pay (conditional mana will used in pool.pay)
// filterMana can be null, uses for spells like "spend only black mana on X"
// {C}
if ((filterMana == null || filterMana.isColorless()) && pool.pay(ManaType.COLORLESS, ability, sourceFilter, game, costToPay, usedManaToPay)) {
this.payment.increaseColorless();
continue;
}
if (pool.pay(ManaType.BLACK, ability, sourceFilter, game, costToPay, usedManaToPay)) {
// {B}
if ((filterMana == null || filterMana.isBlack()) && pool.pay(ManaType.BLACK, ability, sourceFilter, game, costToPay, usedManaToPay)) {
this.payment.increaseBlack();
continue;
}
if (pool.pay(ManaType.BLUE, ability, sourceFilter, game, costToPay, usedManaToPay)) {
// {U}
if ((filterMana == null || filterMana.isBlue()) && pool.pay(ManaType.BLUE, ability, sourceFilter, game, costToPay, usedManaToPay)) {
this.payment.increaseBlue();
continue;
}
if (pool.pay(ManaType.WHITE, ability, sourceFilter, game, costToPay, usedManaToPay)) {
// {W}
if ((filterMana == null || filterMana.isWhite()) && pool.pay(ManaType.WHITE, ability, sourceFilter, game, costToPay, usedManaToPay)) {
this.payment.increaseWhite();
continue;
}
if (pool.pay(ManaType.GREEN, ability, sourceFilter, game, costToPay, usedManaToPay)) {
// {G}
if ((filterMana == null || filterMana.isGreen()) && pool.pay(ManaType.GREEN, ability, sourceFilter, game, costToPay, usedManaToPay)) {
this.payment.increaseGreen();
continue;
}
if (pool.pay(ManaType.RED, ability, sourceFilter, game, costToPay, usedManaToPay)) {
// {R}
if ((filterMana == null || filterMana.isRed()) && pool.pay(ManaType.RED, ability, sourceFilter, game, costToPay, usedManaToPay)) {
this.payment.increaseRed();
continue;
}
// nothing to pay
break;
}
return mana > payment.count();
@ -208,14 +225,14 @@ public abstract class ManaCostImpl extends CostImpl implements ManaCost {
}
Player player = game.getPlayer(controllerId);
if (!player.getManaPool().isForcedToPay()) {
assignPayment(game, ability, player.getManaPool(), costToPay);
assignPayment(game, ability, player.getManaPool(), costToPay != null ? costToPay : this);
}
game.getState().getSpecialActions().removeManaActions();
while (!isPaid()) {
ManaCost unpaid = this.getUnpaid();
String promptText = ManaUtil.addSpecialManaPayAbilities(ability, game, unpaid);
if (player.playMana(ability, unpaid, promptText, game)) {
assignPayment(game, ability, player.getManaPool(), costToPay);
assignPayment(game, ability, player.getManaPool(), costToPay != null ? costToPay : this);
} else {
return false;
}

View file

@ -1,4 +1,3 @@
package mage.abilities.costs.mana;
import mage.Mana;
@ -11,9 +10,8 @@ import java.util.UUID;
import java.util.stream.Collectors;
/**
*
* @author BetaSteward_at_googlemail.com
* @param <T>
* @author BetaSteward_at_googlemail.com
*/
public interface ManaCosts<T extends ManaCost> extends List<T>, ManaCost {
@ -21,9 +19,15 @@ public interface ManaCosts<T extends ManaCost> extends List<T>, ManaCost {
List<VariableCost> getVariableCosts();
boolean containsX();
int getX();
void setX(int x);
/**
* @param xValue final X value -- announced X * xMultiplier, where xMultiplier can be changed by replace events like Unbound Flourishing)
* @param xPay real number of pay amount (x * xMultiplier * xInstances, where xInstances is number of {X} in pay like 1, 2, 3)
*/
void setX(int xValue, int xPay);
void load(String mana);
@ -41,7 +45,7 @@ public interface ManaCosts<T extends ManaCost> extends List<T>, ManaCost {
static ManaCosts<ManaCost> removeVariableManaCost(ManaCosts<ManaCost> m) {
return m.stream()
.filter(mc -> !(mc instanceof VariableManaCost))
.collect(Collectors.toCollection(ManaCostsImpl<ManaCost>::new));
.collect(Collectors.toCollection(ManaCostsImpl::new));
}

View file

@ -1,7 +1,5 @@
package mage.abilities.costs.mana;
import java.util.*;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
@ -14,15 +12,18 @@ import mage.constants.ColoredManaSymbol;
import mage.constants.ManaType;
import mage.constants.Outcome;
import mage.filter.Filter;
import mage.filter.FilterMana;
import mage.game.Game;
import mage.players.ManaPool;
import mage.players.Player;
import mage.target.Targets;
import mage.util.ManaUtil;
import java.util.*;
/**
* @author BetaSteward_at_googlemail.com
* @param <T>
* @author BetaSteward_at_googlemail.com
*/
public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements ManaCosts<T> {
@ -117,6 +118,7 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
}
Player player = game.getPlayer(controllerId);
handleKrrikPhyrexianManaCosts(controllerId, ability, game);
if (!player.getManaPool().isForcedToPay()) {
assignPayment(game, ability, player.getManaPool(), this);
}
@ -166,6 +168,11 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
while (manaCostIterator.hasNext()) {
ManaCost manaCost = manaCostIterator.next();
PhyrexianManaCost tempPhyrexianCost = null;
Mana mana = manaCost.getMana();
FilterMana phyrexianColors = player.getPhyrexianColors();
if (manaCost instanceof PhyrexianManaCost) {
PhyrexianManaCost phyrexianManaCost = (PhyrexianManaCost) manaCost;
PayLifeCost payLifeCost = new PayLifeCost(2);
@ -179,6 +186,56 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
tempCosts.pay(source, game, source.getSourceId(), player.getId(), false, null);
}
private void handleKrrikPhyrexianManaCosts(UUID payingPlayerId, Ability source, Game game) {
Player player = game.getPlayer(payingPlayerId);
if (this == null || player == null) {
return; // nothing to be done without any mana costs. prevents NRE from occurring here
}
Iterator<T> manaCostIterator = this.iterator();
Costs<PayLifeCost> tempCosts = new CostsImpl<>();
while (manaCostIterator.hasNext()) {
ManaCost manaCost = manaCostIterator.next();
Mana mana = manaCost.getMana();
PhyrexianManaCost tempPhyrexianCost = null;
FilterMana phyrexianColors = player.getPhyrexianColors();
/* K'rrik, Son of Yawgmoth ability check */
if (phyrexianColors != null) {
int phyrexianEnabledPips = mana.count(phyrexianColors);
if (phyrexianEnabledPips > 0) {
/* find which color mana is in the cost and set it in the temp Phyrexian cost */
if (phyrexianColors.isWhite() && mana.getWhite() > 0) {
tempPhyrexianCost = new PhyrexianManaCost(ColoredManaSymbol.W);
}
else if (phyrexianColors.isBlue() && mana.getBlue() > 0) {
tempPhyrexianCost = new PhyrexianManaCost(ColoredManaSymbol.U);
}
else if (phyrexianColors.isBlack() && mana.getBlack() > 0) {
tempPhyrexianCost = new PhyrexianManaCost(ColoredManaSymbol.B);
}
else if (phyrexianColors.isRed() && mana.getRed() > 0) {
tempPhyrexianCost = new PhyrexianManaCost(ColoredManaSymbol.R);
}
else if (phyrexianColors.isGreen() && mana.getGreen() > 0) {
tempPhyrexianCost = new PhyrexianManaCost(ColoredManaSymbol.G);
}
if (tempPhyrexianCost != null) {
PayLifeCost payLifeCost = new PayLifeCost(2);
if (payLifeCost.canPay(source, source.getSourceId(), player.getId(), game)
&& player.chooseUse(Outcome.LoseLife, "Pay 2 life (using an active ability) instead of " + tempPhyrexianCost.getBaseText() + '?', source, game)) {
manaCostIterator.remove();
tempCosts.add(payLifeCost);
}
}
}
}
}
tempCosts.pay(source, game, source.getSourceId(), player.getId(), false, null);
}
@Override
public ManaCosts<T> getUnpaid() {
@ -213,6 +270,11 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
return variableCosts;
}
@Override
public boolean containsX() {
return !getVariableCosts().isEmpty();
}
@Override
public int getX() {
int amount = 0;
@ -224,10 +286,10 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
}
@Override
public void setX(int x) {
public void setX(int xValue, int xPay) {
List<VariableCost> variableCosts = getVariableCosts();
if (!variableCosts.isEmpty()) {
variableCosts.get(0).setAmount(x);
variableCosts.get(0).setAmount(xValue, xPay, false);
}
}
@ -339,7 +401,12 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
if (player != null) {
game.undo(playerId);
this.clearPaid();
this.setX(referenceCosts.getX());
// TODO: checks Word of Command with Unbound Flourishing's X multiplier
// TODO: checks Word of Command with {X}{X} cards
int xValue = referenceCosts.getX();
this.setX(xValue, xValue);
player.getManaPool().restoreMana(pool.getPoolBookmark());
game.bookmarkState();
}
@ -378,15 +445,15 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
} else if (!symbol.equals("X")) {
this.add(new ColoredManaCost(ColoredManaSymbol.lookup(symbol.charAt(0))));
} else // check X wasn't added before
if (modifierForX == 0) {
// count X occurence
for (String s : symbols) {
if (s.equals("X")) {
modifierForX++;
if (modifierForX == 0) {
// count X occurence
for (String s : symbols) {
if (s.equals("X")) {
modifierForX++;
}
}
}
this.add(new VariableManaCost(modifierForX));
} //TODO: handle multiple {X} and/or {Y} symbols
this.add(new VariableManaCost(modifierForX));
} //TODO: handle multiple {X} and/or {Y} symbols
} else if (Character.isDigit(symbol.charAt(0))) {
this.add(new MonoHybridManaCost(ColoredManaSymbol.lookup(symbol.charAt(2))));
} else if (symbol.contains("P")) {

View file

@ -1,8 +1,5 @@
package mage.abilities.costs.mana;
import java.util.ArrayList;
import java.util.List;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
@ -10,6 +7,9 @@ import mage.constants.ColoredManaSymbol;
import mage.game.Game;
import mage.players.ManaPool;
import java.util.ArrayList;
import java.util.List;
public class MonoHybridManaCost extends ManaCostImpl {
private final ColoredManaSymbol mana;
@ -45,7 +45,7 @@ public class MonoHybridManaCost extends ManaCostImpl {
@Override
public void assignPayment(Game game, Ability ability, ManaPool pool, Cost costToPay) {
if (!assignColored(ability, game, pool, mana, costToPay)) {
assignGeneric(ability, game, pool, mana2, costToPay);
assignGeneric(ability, game, pool, mana2, null, costToPay);
}
}

View file

@ -1,4 +1,3 @@
package mage.abilities.costs.mana;
import mage.Mana;
@ -36,7 +35,7 @@ public class SnowManaCost extends ManaCostImpl {
@Override
public void assignPayment(Game game, Ability ability, ManaPool pool, Cost costToPay) {
this.assignGeneric(ability, game, pool, 1, costToPay);
this.assignGeneric(ability, game, pool, 1, null, costToPay);
}
@Override

View file

@ -1,4 +1,3 @@
package mage.abilities.costs.mana;
import mage.Mana;
@ -11,13 +10,20 @@ import mage.game.Game;
import mage.players.ManaPool;
/**
*
* @author BetaSteward_at_googlemail.com
* @author BetaSteward_at_googlemail.com, JayDi85
*/
public class VariableManaCost extends ManaCostImpl implements VariableCost {
public final class VariableManaCost extends ManaCostImpl implements VariableCost {
protected int multiplier;
protected FilterMana filter;
// variable mana cost usage on 2019-06-20:
// 1. as X value in spell/ability cast (announce X, set VariableManaCost as paid and add generic mana to pay instead)
// 2. as X value in direct pay (X already announced, cost is unpaid, need direct pay)
protected int xInstancesCount; // number of {X} instances in cost like {X} or {X}{X}
protected int xValue = 0; // final X value after announce and replace events
protected int xPay = 0; // final/total need pay after announce and replace events (example: {X}{X}, X=3, xPay = 6)
protected boolean wasAnnounced = false;
protected FilterMana filter; // mana filter that can be used for that cost
protected int minX = 0;
protected int maxX = Integer.MAX_VALUE;
@ -25,15 +31,18 @@ public class VariableManaCost extends ManaCostImpl implements VariableCost {
this(1);
}
public VariableManaCost(int multiplier) {
this.multiplier = multiplier;
public VariableManaCost(int xInstancesCount) {
this.xInstancesCount = xInstancesCount;
this.cost = new Mana();
options.add(new Mana());
}
public VariableManaCost(final VariableManaCost manaCost) {
super(manaCost);
this.multiplier = manaCost.multiplier;
this.xInstancesCount = manaCost.xInstancesCount;
this.xValue = manaCost.xValue;
this.xPay = manaCost.xPay;
this.wasAnnounced = manaCost.wasAnnounced;
if (manaCost.filter != null) {
this.filter = manaCost.filter.copy();
}
@ -48,16 +57,15 @@ public class VariableManaCost extends ManaCostImpl implements VariableCost {
@Override
public void assignPayment(Game game, Ability ability, ManaPool pool, Cost costToPay) {
payment.add(pool.getMana(filter));
payment.add(pool.getAllConditionalMana(ability, game, filter));
pool.payX(ability, game, filter);
// X mana cost always pays as generic mana
this.assignGeneric(ability, game, pool, xPay, filter, costToPay);
}
@Override
public String getText() {
if (multiplier > 1) {
StringBuilder symbol = new StringBuilder(multiplier);
for (int i = 0; i < multiplier; i++) {
if (xInstancesCount > 1) {
StringBuilder symbol = new StringBuilder(xInstancesCount);
for (int i = 0; i < xInstancesCount; i++) {
symbol.append("{X}");
}
return symbol.toString();
@ -66,6 +74,14 @@ public class VariableManaCost extends ManaCostImpl implements VariableCost {
}
}
@Override
public boolean isPaid() {
if (!wasAnnounced) return false;
if (paid) return true;
return this.isColorlessPaid(xPay);
}
@Override
public VariableManaCost getUnpaid() {
return this;
@ -73,17 +89,24 @@ public class VariableManaCost extends ManaCostImpl implements VariableCost {
@Override
public int getAmount() {
return payment.count() / multiplier;
// must return X value
return this.xValue;
}
@Override
public void setAmount(int amount) {
payment.setGeneric(amount);
public void setAmount(int xValue, int xPay, boolean isPayed) {
// xPay is total pay value (X * instances)
this.xValue = xValue;
this.xPay = xPay;
if (isPayed) {
payment.setGeneric(xPay);
}
this.wasAnnounced = true;
}
@Override
public boolean testPay(Mana testMana) {
return true;
return true; // TODO: need rework to generic mana style?
}
@Override
@ -91,8 +114,8 @@ public class VariableManaCost extends ManaCostImpl implements VariableCost {
return new VariableManaCost(this);
}
public int getMultiplier() {
return multiplier;
public int getXInstancesCount() {
return this.xInstancesCount;
}
public int getMinX() {
@ -118,27 +141,27 @@ public class VariableManaCost extends ManaCostImpl implements VariableCost {
@Override
public int announceXValue(Ability source, Game game) {
throw new UnsupportedOperationException("Not supported."); //To change body of generated methods, choose Tools | Templates.
throw new UnsupportedOperationException("Not supported.");
}
@Override
public Cost getFixedCostsFromAnnouncedValue(int xValue) {
throw new UnsupportedOperationException("Not supported."); //To change body of generated methods, choose Tools | Templates.
throw new UnsupportedOperationException("Not supported.");
}
@Override
public String getActionText() {
throw new UnsupportedOperationException("Not supported."); //To change body of generated methods, choose Tools | Templates.
throw new UnsupportedOperationException("Not supported.");
}
@Override
public int getMinValue(Ability source, Game game) {
throw new UnsupportedOperationException("Not supported."); //To change body of generated methods, choose Tools | Templates.
throw new UnsupportedOperationException("Not supported.");
}
@Override
public int getMaxValue(Ability source, Game game) {
throw new UnsupportedOperationException("Not supported."); //To change body of generated methods, choose Tools | Templates.
throw new UnsupportedOperationException("Not supported.");
}
public FilterMana getFilter() {

View file

@ -50,13 +50,13 @@ public class ConditionalContinuousEffect extends ContinuousEffectImpl {
// checks for compatibility
EffectType needType = EffectType.CONTINUOUS;
if (effect != null && !effect.getEffectType().equals(needType)) {
if (effect.getEffectType() != needType) {
Assert.fail("ConditionalContinuousEffect supports only " + needType.toString() + " but found " + effect.getEffectType().toString());
}
if (otherwiseEffect != null && !otherwiseEffect.getEffectType().equals(needType)) {
if (otherwiseEffect != null && otherwiseEffect.getEffectType() != needType) {
Assert.fail("ConditionalContinuousEffect supports only " + needType.toString() + " but found " + effect.getEffectType().toString());
}
if (effect != null && otherwiseEffect != null && !effect.getEffectType().equals(otherwiseEffect.getEffectType())) {
if (otherwiseEffect != null && effect.getEffectType() != otherwiseEffect.getEffectType()) {
Assert.fail("ConditionalContinuousEffect must be same but found " + effect.getEffectType().toString() + " and " + otherwiseEffect.getEffectType().toString());
}
}
@ -119,7 +119,7 @@ public class ConditionalContinuousEffect extends ContinuousEffectImpl {
if (condition == null && baseCondition != null) {
condition = baseCondition;
}
boolean conditionState = condition.apply(game, source);
boolean conditionState = condition != null && condition.apply(game, source);
if (conditionState) {
effect.setTargetPointer(this.targetPointer);
return effect.apply(game, source);
@ -127,10 +127,10 @@ public class ConditionalContinuousEffect extends ContinuousEffectImpl {
otherwiseEffect.setTargetPointer(this.targetPointer);
return otherwiseEffect.apply(game, source);
}
if (!conditionState && effect.getDuration() == Duration.OneUse) {
if (effect.getDuration() == Duration.OneUse) {
used = true;
}
if (!conditionState && effect.getDuration() == Duration.Custom) {
if (effect.getDuration() == Duration.Custom) {
this.discard();
}
return false;

View file

@ -9,6 +9,9 @@ import mage.abilities.effects.Effects;
import mage.constants.EffectType;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.watchers.Watcher;
import java.util.List;
/**
* Adds condition to {@link mage.abilities.effects.ContinuousEffect}. Acts as
@ -34,7 +37,6 @@ public class ConditionalInterveningIfTriggeredAbility extends TriggeredAbilityIm
public ConditionalInterveningIfTriggeredAbility(TriggeredAbility ability, Condition condition, String text) {
super(ability.getZone(), null);
this.ability = ability;
this.modes = ability.getModes();
this.condition = condition;
this.abilityText = text;
}
@ -91,6 +93,16 @@ public class ConditionalInterveningIfTriggeredAbility extends TriggeredAbilityIm
return ability.getModes();
}
@Override
public List<Watcher> getWatchers() {
return ability.getWatchers();
}
@Override
public void addWatcher(Watcher watcher) {
ability.addWatcher(watcher);
}
@Override
public Effects getEffects(Game game, EffectType effectType) {
return ability.getEffects(game, effectType);
@ -100,5 +112,4 @@ public class ConditionalInterveningIfTriggeredAbility extends TriggeredAbilityIm
public boolean isOptional() {
return ability.isOptional();
}
}

View file

@ -9,6 +9,9 @@ import mage.abilities.effects.Effects;
import mage.constants.EffectType;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.watchers.Watcher;
import java.util.List;
/**
* Adds condition to {@link mage.abilities.effects.ContinuousEffect}. Acts as
@ -34,7 +37,6 @@ public class ConditionalTriggeredAbility extends TriggeredAbilityImpl {
public ConditionalTriggeredAbility(TriggeredAbility ability, Condition condition, String text) {
super(ability.getZone(), null);
this.ability = ability;
this.modes = ability.getModes();
this.condition = condition;
this.abilityText = text;
}
@ -86,6 +88,16 @@ public class ConditionalTriggeredAbility extends TriggeredAbilityImpl {
return ability.getModes();
}
@Override
public List<Watcher> getWatchers() {
return ability.getWatchers();
}
@Override
public void addWatcher(Watcher watcher) {
ability.addWatcher(watcher);
}
@Override
public Effects getEffects(Game game, EffectType effectType) {
return ability.getEffects(game, effectType);

View file

@ -0,0 +1,35 @@
package mage.abilities.dynamicvalue.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.filter.StaticFilters;
import mage.game.Game;
/**
* @author JayDi85
*/
public enum ArtifactsYouControlCount implements DynamicValue {
instance;
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
return game.getBattlefield().count(StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT, sourceAbility.getSourceId(), sourceAbility.getControllerId(), game);
}
@Override
public ArtifactsYouControlCount copy() {
return instance;
}
@Override
public String toString() {
return "X";
}
@Override
public String getMessage() {
return "artifacts you control";
}
}

View file

@ -0,0 +1,39 @@
package mage.abilities.dynamicvalue.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.game.Game;
import mage.watchers.common.CardsDrawnThisTurnWatcher;
/**
* @author TheElk801
*/
public enum CardsDrawnThisTurnDynamicValue implements DynamicValue {
instance;
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
CardsDrawnThisTurnWatcher watcher = game.getState().getWatcher(CardsDrawnThisTurnWatcher.class);
if (watcher != null) {
return watcher.getCardsDrawnThisTurn(sourceAbility.getControllerId());
}
return 0;
}
@Override
public CardsDrawnThisTurnDynamicValue copy() {
return instance;
}
@Override
public String toString() {
return "1";
}
@Override
public String getMessage() {
return "card you've drawn this turn";
}
}

View file

@ -1,4 +1,3 @@
package mage.abilities.dynamicvalue.common;
import java.util.UUID;
@ -25,7 +24,7 @@ public class CardsInAllGraveyardsCount implements DynamicValue {
this.filter = filter;
}
public CardsInAllGraveyardsCount(CardsInAllGraveyardsCount dynamicValue) {
public CardsInAllGraveyardsCount(final CardsInAllGraveyardsCount dynamicValue) {
this.filter = dynamicValue.filter.copy();
}

View file

@ -4,6 +4,7 @@ import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.filter.FilterCard;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.players.Player;
@ -17,7 +18,7 @@ public class CardsInControllerGraveyardCount implements DynamicValue {
private Integer amount;
public CardsInControllerGraveyardCount() {
this(new FilterCard(), 1);
this(StaticFilters.FILTER_CARD, 1);
}
public CardsInControllerGraveyardCount(FilterCard filter) {

View file

@ -0,0 +1,52 @@
package mage.abilities.dynamicvalue.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.game.Game;
import mage.watchers.common.CommanderPlaysCountWatcher;
/**
* @author JayDi85
*/
public class CommanderPlaysCount implements DynamicValue {
private Integer multiplier;
public CommanderPlaysCount() {
this(1);
}
public CommanderPlaysCount(Integer multiplier) {
this.multiplier = multiplier;
}
public CommanderPlaysCount(final CommanderPlaysCount dynamicValue) {
this.multiplier = dynamicValue.multiplier;
}
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
CommanderPlaysCountWatcher watcher = game.getState().getWatcher(CommanderPlaysCountWatcher.class);
int value = 0;
if (watcher != null) {
value = watcher.getPlaysCount(sourceAbility.getSourceId());
}
return value * multiplier;
}
@Override
public CommanderPlaysCount copy() {
return new CommanderPlaysCount(this);
}
@Override
public String toString() {
return "X";
}
@Override
public String getMessage() {
return "";
}
}

View file

@ -99,6 +99,6 @@ public class DomainValue implements DynamicValue {
@Override
public String getMessage() {
return "basic land type among lands " + (countTargetPlayer ? "he or she controls" : "you control");
return "basic land type among lands " + (countTargetPlayer ? "they control" : "you control");
}
}

View file

@ -0,0 +1,69 @@
package mage.abilities.dynamicvalue.common;
import mage.abilities.Ability;
import mage.abilities.costs.OptionalAdditionalCost;
import mage.abilities.costs.OptionalAdditionalCostImpl;
import mage.abilities.costs.VariableCost;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.abilities.keyword.KickerAbility;
import mage.game.Game;
import mage.game.stack.Spell;
import java.util.List;
/**
* @author JayDi85
*/
public enum GetKickerXValue implements DynamicValue {
instance;
@Override
public int calculate(Game game, Ability source, Effect effect) {
// calcs only kicker with X values
// kicker adds additional costs to spell ability
// only one X value per card possible
// kicker can be calls multiple times (use getKickedCounter)
int finalValue = 0;
Spell spell = game.getSpellOrLKIStack(source.getSourceId());
if (spell != null && spell.getSpellAbility() != null) {
int xValue = spell.getSpellAbility().getManaCostsToPay().getX();
for (Ability ability : spell.getAbilities()) {
if (ability instanceof KickerAbility) {
// search that kicker used X value
KickerAbility kickerAbility = (KickerAbility) ability;
boolean haveVarCost = kickerAbility.getKickerCosts()
.stream()
.anyMatch(varCost -> !((OptionalAdditionalCostImpl) varCost).getVariableCosts().isEmpty());
if (haveVarCost) {
int kickedCount = ((KickerAbility) ability).getKickedCounter(game, source);
if (kickedCount > 0) {
finalValue += kickedCount * xValue;
}
}
}
}
}
return finalValue;
}
@Override
public GetKickerXValue copy() {
return GetKickerXValue.instance;
}
@Override
public String toString() {
return "X";
}
@Override
public String getMessage() {
return "";
}
}

View file

@ -3,6 +3,7 @@ package mage.abilities.dynamicvalue.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.permanent.Permanent;
@ -29,7 +30,10 @@ public class SourcePermanentPowerCount implements DynamicValue {
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(sourceAbility.getSourceId());
Permanent sourcePermanent = game.getPermanent(sourceAbility.getSourceId());
if (sourcePermanent == null || sourcePermanent.getZoneChangeCounter(game) > sourceAbility.getSourceObjectZoneChangeCounter()) {
sourcePermanent = (Permanent) game.getLastKnownInformation(sourceAbility.getSourceId(), Zone.BATTLEFIELD);
}
if (sourcePermanent != null
&& (allowNegativeValues || sourcePermanent.getPower().getValue() >= 0)) {
return sourcePermanent.getPower().getValue();

View file

@ -1,6 +1,5 @@
package mage.abilities.effects;
import java.util.UUID;
import mage.abilities.Ability;
import mage.constants.AsThoughEffectType;
import mage.constants.Duration;
@ -8,8 +7,9 @@ import mage.constants.EffectType;
import mage.constants.Outcome;
import mage.game.Game;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements AsThoughEffect {
@ -29,10 +29,11 @@ public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements
@Override
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
// affectedControllerId = player to check
if (getAsThoughEffectType().equals(AsThoughEffectType.LOOK_AT_FACE_DOWN)) {
return applies(objectId, source, playerId, game);
} else {
return applies(objectId, source, affectedAbility.getControllerId(), game);
return applies(objectId, source, playerId, game);
}
}

View file

@ -7,6 +7,7 @@ import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.SubLayer;
import mage.game.Game;
import mage.target.targetpointer.TargetPointer;
import java.util.EnumSet;
import java.util.List;
@ -77,4 +78,7 @@ public interface ContinuousEffect extends Effect {
boolean isTemporary();
void setTemporary(boolean temporary);
@Override
ContinuousEffect setTargetPointer(TargetPointer targetPointer);
}

View file

@ -10,6 +10,7 @@ import mage.abilities.dynamicvalue.common.StaticValue;
import mage.constants.*;
import mage.game.Game;
import mage.players.Player;
import mage.target.targetpointer.TargetPointer;
import java.util.*;
@ -216,7 +217,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
boolean canDelete = false;
Player player = game.getPlayer(startingControllerId);
// discard on start of turn for leave player
// discard on start of turn for leaved player
// 800.4i When a player leaves the game, any continuous effects with durations that last until that player's next turn
// or until a specific point in that turn will last until that turn would have begun.
// They neither expire immediately nor last indefinitely.
@ -334,4 +335,10 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
dependendToTypes.add(dependencyType);
}
@Override
public ContinuousEffect setTargetPointer(TargetPointer targetPointer) {
super.setTargetPointer(targetPointer);
return this;
}
}

View file

@ -1,15 +1,15 @@
package mage.abilities.effects;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.*;
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect;
import mage.abilities.effects.common.continuous.CommanderReplacementEffect;
import mage.abilities.keyword.SpliceOntoArcaneAbility;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.cards.SplitCardHalf;
import mage.cards.*;
import mage.constants.*;
import mage.filter.FilterCard;
import mage.filter.predicate.Predicate;
@ -27,11 +27,6 @@ import mage.players.Player;
import mage.target.common.TargetCardInHand;
import org.apache.log4j.Logger;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
/**
* @author BetaSteward_at_googlemail.com
*/
@ -337,7 +332,7 @@ public class ContinuousEffects implements Serializable {
}
// boolean checkLKI = event.getType().equals(EventType.ZONE_CHANGE) || event.getType().equals(EventType.DESTROYED_PERMANENT);
//get all applicable transient Replacement effects
for (Iterator<ReplacementEffect> iterator = replacementEffects.iterator(); iterator.hasNext(); ) {
for (Iterator<ReplacementEffect> iterator = replacementEffects.iterator(); iterator.hasNext();) {
ReplacementEffect effect = iterator.next();
if (!effect.checksEventType(event, game)) {
continue;
@ -370,7 +365,7 @@ public class ContinuousEffects implements Serializable {
}
}
for (Iterator<PreventionEffect> iterator = preventionEffects.iterator(); iterator.hasNext(); ) {
for (Iterator<PreventionEffect> iterator = preventionEffects.iterator(); iterator.hasNext();) {
PreventionEffect effect = iterator.next();
if (!effect.checksEventType(event, game)) {
continue;
@ -509,9 +504,20 @@ public class ContinuousEffects implements Serializable {
UUID idToCheck;
if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof SplitCardHalf) {
idToCheck = ((SplitCardHalf) affectedAbility.getSourceObject(game)).getParentCard().getId();
} else if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof AdventureCardSpell
&& type != AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE
&& type != AsThoughEffectType.CAST_AS_INSTANT) {
// adventure spell uses alternative characteristics for spell/stack
idToCheck = ((AdventureCardSpell) affectedAbility.getSourceObject(game)).getParentCard().getId();
} else {
if (game.getObject(objectId) instanceof SplitCardHalf) {
idToCheck = ((SplitCardHalf) game.getObject(objectId)).getParentCard().getId();
Card card = game.getCard(objectId);
if (card instanceof SplitCardHalf) {
idToCheck = ((SplitCardHalf) card).getParentCard().getId();
} else if (card instanceof AdventureCardSpell
&& type != AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE
&& type != AsThoughEffectType.CAST_AS_INSTANT) {
// adventure spell uses alternative characteristics for spell/stack
idToCheck = ((AdventureCardSpell) card).getParentCard().getId();
} else {
idToCheck = objectId;
}
@ -663,12 +669,12 @@ public class ContinuousEffects implements Serializable {
}
List<SpliceCardEffect> spliceEffects = getApplicableSpliceCardEffects(game, abilityToModify.getControllerId());
// get the applyable splice abilities
List<SpliceOntoArcaneAbility> spliceAbilities = new ArrayList<>();
List<Ability> spliceAbilities = new ArrayList<>();
for (SpliceCardEffect effect : spliceEffects) {
Set<Ability> abilities = spliceCardEffects.getAbility(effect.getId());
for (Ability ability : abilities) {
if (effect.applies(abilityToModify, ability, game)) {
spliceAbilities.add((SpliceOntoArcaneAbility) ability);
spliceAbilities.add(ability);
}
}
}
@ -681,7 +687,7 @@ public class ContinuousEffects implements Serializable {
do {
FilterCard filter = new FilterCard("a card to splice");
ArrayList<Predicate<MageObject>> idPredicates = new ArrayList<>();
for (SpliceOntoArcaneAbility ability : spliceAbilities) {
for (Ability ability : spliceAbilities) {
idPredicates.add(new CardIdPredicate((ability.getSourceId())));
}
filter.add(Predicates.or(idPredicates));
@ -689,8 +695,8 @@ public class ContinuousEffects implements Serializable {
controller.chooseTarget(Outcome.Benefit, target, abilityToModify, game);
UUID cardId = target.getFirstTarget();
if (cardId != null) {
SpliceOntoArcaneAbility selectedAbility = null;
for (SpliceOntoArcaneAbility ability : spliceAbilities) {
Ability selectedAbility = null;
for (Ability ability : spliceAbilities) {
if (ability.getSourceId().equals(cardId)) {
selectedAbility = ability;
break;
@ -713,10 +719,10 @@ public class ContinuousEffects implements Serializable {
* Checks if an event won't happen because of an rule modifying effect
*
* @param event
* @param targetAbility ability the event is attached to. can be null.
* @param targetAbility ability the event is attached to. can be null.
* @param game
* @param checkPlayableMode true if the event does not really happen but
* it's checked if the event would be replaced
* it's checked if the event would be replaced
* @return
*/
public boolean preventedByRuleModification(GameEvent event, Ability targetAbility, Game game, boolean checkPlayableMode) {
@ -730,10 +736,7 @@ public class ContinuousEffects implements Serializable {
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
effect.setValue("targetAbility", targetAbility);
if (effect.applies(event, sourceAbility, game)) {
if (targetAbility instanceof ActivatedAbility && ((ActivatedAbility) targetAbility).isCheckPlayableMode()) {
checkPlayableMode = true;
}
if (!checkPlayableMode) {
if (!game.inCheckPlayableState()) {
String message = effect.getInfoMessage(sourceAbility, event, game);
if (message != null && !message.isEmpty()) {
if (effect.sendMessageToUser()) {
@ -763,7 +766,7 @@ public class ContinuousEffects implements Serializable {
do {
Map<ReplacementEffect, Set<Ability>> rEffects = getApplicableReplacementEffects(event, game);
// Remove all consumed effects (ability dependant)
for (Iterator<ReplacementEffect> it1 = rEffects.keySet().iterator(); it1.hasNext(); ) {
for (Iterator<ReplacementEffect> it1 = rEffects.keySet().iterator(); it1.hasNext();) {
ReplacementEffect entry = it1.next();
if (consumed.containsKey(entry.getId()) /*&& !(entry instanceof CommanderReplacementEffect) */) { // 903.9.
Set<UUID> consumedAbilitiesIds = consumed.get(entry.getId());
@ -954,7 +957,7 @@ public class ContinuousEffects implements Serializable {
if (!waitingEffects.isEmpty()) {
// check if waiting effects can be applied now
for (Iterator<Map.Entry<ContinuousEffect, Set<UUID>>> iterator = waitingEffects.entrySet().iterator(); iterator.hasNext(); ) {
for (Iterator<Map.Entry<ContinuousEffect, Set<UUID>>> iterator = waitingEffects.entrySet().iterator(); iterator.hasNext();) {
Map.Entry<ContinuousEffect, Set<UUID>> entry = iterator.next();
if (appliedEffects.containsAll(entry.getValue())) { // all dependent to effects are applied now so apply the effect itself
appliedAbilities = appliedEffectAbilities.get(entry.getKey());

View file

@ -107,7 +107,7 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
which give that player control of any objects or players end. Then, if that player controlled any objects on the stack
not represented by cards, those objects cease to exist. Then, if there are any objects still controlled by that player,
those objects are exiled. This is not a state-based action. It happens as soon as the player leaves the game.
If the player who left the game had priority at the time he or she left, priority passes to the next player in turn
If the player who left the game had priority at the time they left, priority passes to the next player in turn
order whos still in the game.
*/
// objects removes doing in player.leave() call... effects removes is here

View file

@ -32,7 +32,7 @@ public interface ContinuousRuleModifyingEffect extends ContinuousEffect {
/**
* Defines if the user should get a message about the rule modifying effect
* if he was applied
* if it was applied
*
* @return true if user should be informed
*/
@ -40,13 +40,13 @@ public interface ContinuousRuleModifyingEffect extends ContinuousEffect {
/**
* Defines if the a message should be send to game log about the rule modifying effect
* if he was applied
* if it was applied
*
* @return true if message should go to game log
*/
boolean sendMessageToGameLog();
/**
* Returns a message text that informs the player why he can't do something.
* Returns a message text that informs the player why they can't do something.
*
* @param source the ability of the effect
* @param event

View file

@ -13,18 +13,21 @@ import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
/**
*
* @author antoni-g
*/
public class PreventDamageAndRemoveCountersEffect extends PreventionEffectImpl {
private final boolean thatMany;
public PreventDamageAndRemoveCountersEffect() {
public PreventDamageAndRemoveCountersEffect(boolean thatMany) {
super(Duration.WhileOnBattlefield, Integer.MAX_VALUE, false, false);
staticText = "If damage would be dealt to {this}, prevent that damage and remove that many +1/+1 counters from it";
this.thatMany = thatMany;
staticText = "If damage would be dealt to {this} while it has a +1/+1 counter on it, " +
"prevent that damage and remove " + (thatMany ? "that many +1/+1 counters" : "a +1/+1 counter") + " from it";
}
public PreventDamageAndRemoveCountersEffect(final PreventDamageAndRemoveCountersEffect effect) {
private PreventDamageAndRemoveCountersEffect(final PreventDamageAndRemoveCountersEffect effect) {
super(effect);
this.thatMany = effect.thatMany;
}
@Override
@ -42,19 +45,22 @@ public class PreventDamageAndRemoveCountersEffect extends PreventionEffectImpl {
int damage = event.getAmount();
preventDamageAction(event, source, game);
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null) {
permanent.removeCounters(CounterType.P1P1.createInstance(damage), game); //MTG ruling (this) loses counters even if the damage isn't prevented
if (permanent == null) {
return false;
}
if (!thatMany) {
damage = 1;
}
permanent.removeCounters(CounterType.P1P1.createInstance(damage), game); //MTG ruling (this) loses counters even if the damage isn't prevented
return false;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (super.applies(event, source, game)) {
if (event.getTargetId().equals(source.getSourceId())) {
return true;
}
}
return false;
Permanent permanent = game.getPermanent(event.getTargetId());
return super.applies(event, source, game)
&& permanent != null
&& event.getTargetId().equals(source.getSourceId())
&& permanent.getCounters(game).containsKey(CounterType.P1P1);
}
}

View file

@ -30,7 +30,8 @@ public class AffinityEffect extends CostModificationEffectImpl {
SpellAbility spellAbility = (SpellAbility)abilityToModify;
Mana mana = spellAbility.getManaCostsToPay().getMana();
if (mana.getGeneric() > 0) {
int count = game.getBattlefield().count(filter, source.getSourceId(), source.getControllerId(), game);
// the following works with Sen Triplets and in multiplayer games
int count = game.getBattlefield().getActivePermanents(filter, abilityToModify.getControllerId(), source.getId(), game).size();
int newCount = mana.getGeneric() - count;
if (newCount < 0) {
newCount = 0;

View file

@ -1,7 +1,5 @@
package mage.abilities.effects.common;
import java.io.ObjectStreamException;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.MageSingleton;
@ -20,6 +18,8 @@ import mage.players.PlayerList;
import mage.target.Target;
import mage.target.common.TargetOpponent;
import java.io.ObjectStreamException;
/**
* 1. The controller of the spell or ability chooses an opponent. (This doesn't
* target the opponent.) 2. Each player involved in the clash reveals the top
@ -28,15 +28,15 @@ import mage.target.common.TargetOpponent;
* their revealed card on either the top or bottom of their library.
* (Note that the player whose turn it is does this first, not necessarily the
* controller of the clash spell or ability.) When the second player makes this
* decision, he or she will know what the first player chose. Then all cards are
* decision, they will know what the first player chose. Then all cards are
* moved at the same time. 5. The clash is over. If one player in the clash
* revealed a card with a higher converted mana cost than all other cards
* revealed in the clash, that player wins the clash. 6. If any abilities
* trigger when a player clashes, they trigger and wait to be put on the stack.
* 7. The clash spell or ability finishes resolving. That usually involves a
* bonus gained by the controller of the clash spell or ability if he or she won
* bonus gained by the controller of the clash spell or ability if they won
* the clash. 8. Abilities that triggered during the clash are put on the stack.
*
* <p>
* There are no draws or losses in a clash. Either you win it or you don't. Each
* spell or ability with clash says what happens if you (the controller of that
* spell or ability) win the clash. Typically, if you don't win the clash,
@ -148,7 +148,7 @@ public class ClashEffect extends OneShotEffect implements MageSingleton {
if (cardOpponent != null && current.getId().equals(opponent.getId())) {
topOpponent = current.chooseUse(Outcome.Detriment, "Put " + cardOpponent.getLogName() + " back on top of your library? (otherwise it goes to bottom)", source, game);
}
nextPlayer = playerList.getNext(game);
nextPlayer = playerList.getNext(game, false);
} while (nextPlayer != null && !nextPlayer.getId().equals(game.getActivePlayerId()));
// put the cards back to library
if (cardController != null) {

View file

@ -1,4 +1,3 @@
package mage.abilities.effects.common;
import mage.MageItem;
@ -11,6 +10,7 @@ import mage.filter.FilterImpl;
import mage.filter.FilterInPlay;
import mage.filter.predicate.mageobject.FromSetPredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.Spell;
import mage.players.Player;
import mage.target.Target;
@ -29,7 +29,8 @@ public abstract class CopySpellForEachItCouldTargetEffect<T extends MageItem> ex
public CopySpellForEachItCouldTargetEffect(FilterInPlay<T> filter) {
super(Outcome.Copy);
this.staticText = "copy the spell for each other " + filter.getMessage() + " that spell could target. Each copy targets a different one";
this.staticText = "copy the spell for each other " + filter.getMessage()
+ " that spell could target. Each copy targets a different one";
this.filter = filter;
}
@ -67,7 +68,8 @@ public abstract class CopySpellForEachItCouldTargetEffect<T extends MageItem> ex
for (TargetAddress addr : TargetAddress.walk(spell)) {
Target targetInstance = addr.getTarget(spell);
if (targetInstance.getNumberOfTargets() > 1) {
throw new UnsupportedOperationException("Changing Target instances with multiple targets is unsupported");
throw new UnsupportedOperationException("Changing Target instances "
+ "with multiple targets is unsupported");
}
if (changeTarget(targetInstance, game, source)) {
targetsToBeChanged.add(addr);
@ -142,25 +144,28 @@ public abstract class CopySpellForEachItCouldTargetEffect<T extends MageItem> ex
FilterInPlay<T> setFilter = filter.copy();
setFilter.add(new FromSetPredicate(targetCopyMap.keySet()));
Target target = new TargetWithAdditionalFilter(sampleTarget, setFilter);
target.setNotTarget(false); // it is targeted, not chosen
target.setMinNumberOfTargets(0);
target.setMaxNumberOfTargets(1);
target.setTargetName(filter.getMessage() + " that " + spell.getLogName() + " could target (" + targetCopyMap.size() + " remaining)");
target.setTargetName(filter.getMessage() + " that " + spell.getLogName()
+ " could target (" + targetCopyMap.size() + " remaining)");
// shortcut if there's only one possible target remaining
if (targetCopyMap.size() > 1
&& target.canChoose(spell.getId(), player.getId(), game)) {
player.choose(Outcome.Neutral, target, spell.getId(), game);
// The original "source" is not applicable here due to the spell being a copy. ie: Zada, Hedron Grinder
player.chooseTarget(Outcome.Neutral, target, spell.getSpellAbility(), game); // not source, but the spell that is copied
}
Collection<UUID> chosenIds = target.getTargets();
if (chosenIds.isEmpty()) {
chosenIds = targetCopyMap.keySet();
}
List<UUID> toDelete = new ArrayList<>();
for (UUID chosenId : chosenIds) {
Spell chosenCopy = targetCopyMap.get(chosenId);
if (chosenCopy != null) {
game.getStack().push(chosenCopy);
game.fireEvent(new GameEvent(GameEvent.EventType.COPIED_STACKOBJECT,
chosenCopy.getId(), spell.getId(), source.getControllerId()));
toDelete.add(chosenId);
madeACopy = true;
}
@ -254,8 +259,8 @@ class TargetWithAdditionalFilter<T extends MageItem> extends TargetImpl {
}
@Override
public int getMaxNumberOfTargets() {
return originalTarget.getMaxNumberOfTargets();
public int getMinNumberOfTargets() {
return originalTarget.getMinNumberOfTargets();
}
@Override
@ -263,6 +268,11 @@ class TargetWithAdditionalFilter<T extends MageItem> extends TargetImpl {
originalTarget.setMinNumberOfTargets(minNumberOfTargets);
}
@Override
public int getMaxNumberOfTargets() {
return originalTarget.getMaxNumberOfTargets();
}
@Override
public void setMaxNumberOfTargets(int maxNumberOfTargets) {
originalTarget.setMaxNumberOfTargets(maxNumberOfTargets);
@ -323,7 +333,8 @@ class TargetWithAdditionalFilter<T extends MageItem> extends TargetImpl {
@Override
public FilterInPlay<T> getFilter() {
return new CompoundFilter((FilterInPlay<T>) originalTarget.getFilter(), additionalFilter, originalTarget.getFilter().getMessage());
return new CompoundFilter((FilterInPlay<T>) originalTarget.getFilter(),
additionalFilter, originalTarget.getFilter().getMessage());
}
@Override

View file

@ -1,15 +1,12 @@
package mage.abilities.effects.common;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import java.util.UUID;
/**
* @author JRHerlehy
*/
@ -29,7 +26,9 @@ public abstract class CouncilsDilemmaVoteEffect extends OneShotEffect {
for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null) {
if (player.chooseUse(Outcome.Vote, "Choose " + choiceOne + '?', source, game)) {
if (player.chooseUse(Outcome.Vote,
"Choose " + choiceOne + " or " + choiceTwo + "?",
source.getRule(), choiceOne, choiceTwo, source, game)) {
voteOneCount++;
game.informPlayers(player.getName() + " has voted for " + choiceOne);
} else {

View file

@ -1,10 +1,8 @@
package mage.abilities.effects.common;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.costs.Cost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.OneShotEffect;
@ -12,9 +10,9 @@ import mage.constants.Outcome;
import mage.game.Game;
import mage.game.stack.StackObject;
import mage.players.Player;
import mage.util.ManaUtil;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class CounterUnlessPaysEffect extends OneShotEffect {
@ -54,23 +52,27 @@ public class CounterUnlessPaysEffect extends OneShotEffect {
Player player = game.getPlayer(spell.getControllerId());
if (player != null) {
Cost costToPay;
String costValueMessage;
if (cost != null) {
costToPay = cost.copy();
costValueMessage = costToPay.getText();
} else {
costToPay = new GenericManaCost(genericMana.calculate(game, source, this));
costToPay = ManaUtil.createManaCost(genericMana, game, source, this);
costValueMessage = "{" + genericMana.calculate(game, source, this) + "}";
}
String message;
if (costToPay instanceof ManaCost) {
message = "Would you like to pay " + costToPay.getText() + " to prevent counter effect?";
message = "Would you like to pay " + costValueMessage + " to prevent counter effect?";
} else {
message = costToPay.getText() + " to prevent counter effect?";
message = costValueMessage + " to prevent counter effect?";
}
costToPay.clearPaid();
if (!(player.chooseUse(Outcome.Benefit, message, source, game) && costToPay.pay(source, game, spell.getSourceId(), spell.getControllerId(), false, null))) {
game.informPlayers(player.getLogName() + " chooses not to pay " + costToPay.getText() + " to prevent the counter effect");
game.informPlayers(player.getLogName() + " chooses not to pay " + costValueMessage + " to prevent the counter effect");
return game.getStack().counter(spell.getId(), source.getSourceId(), game);
}
game.informPlayers(player.getLogName() + " chooses to pay " + costToPay.getText() + " to prevent the counter effect");
game.informPlayers(player.getLogName() + " chooses to pay " + costValueMessage + " to prevent the counter effect");
return true;
}
}

View file

@ -6,7 +6,7 @@ import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
import mage.abilities.common.delayed.AtTheEndOfCombatDelayedTriggeredAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.HasteAbility;
@ -135,12 +135,14 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect {
} else {
permanent = game.getPermanentOrLKIBattlefield(targetId);
}
// can target card or permanent
Card copyFrom;
ApplyToPermanent applier = new EmptyApplyToPermanent();
if (permanent != null) {
// handle copies of copies
Permanent copyFromPermanent = permanent;
for (Effect effect : game.getState().getContinuousEffects().getLayeredEffects(game)) {
for (ContinuousEffect effect : game.getState().getContinuousEffects().getLayeredEffects(game)) {
if (effect instanceof CopyEffect) {
CopyEffect copyEffect = (CopyEffect) effect;
// there is another copy effect that our targetPermanent copies stats from

View file

@ -1,8 +1,6 @@
package mage.abilities.effects.common;
import java.util.ArrayList;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
import mage.abilities.common.delayed.AtTheEndOfCombatDelayedTriggeredAbility;
@ -17,8 +15,11 @@ import mage.game.permanent.token.Token;
import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class CreateTokenEffect extends OneShotEffect {
@ -84,7 +85,7 @@ public class CreateTokenEffect extends OneShotEffect {
return lastAddedTokenId;
}
public ArrayList<UUID> getLastAddedTokenIds() {
public List<UUID> getLastAddedTokenIds() {
return lastAddedTokenIds;
}
@ -133,7 +134,11 @@ public class CreateTokenEffect extends OneShotEffect {
}
}
if (attacking) {
sb.append(" that are");
if (amount.toString().equals("1")) {
sb.append(" that's");
} else {
sb.append(" that are");
}
if (tapped) {
sb.append(" tapped and");
}

View file

@ -1,6 +1,6 @@
package mage.abilities.effects.common;
import java.util.UUID;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.dynamicvalue.DynamicValue;
@ -12,14 +12,18 @@ import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.Target;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class DamageMultiEffect extends OneShotEffect {
protected DynamicValue amount;
private String sourceName = "{source}";
private final Set<MageObjectReference> damagedSet = new HashSet<>();
public DamageMultiEffect(int amount) {
this(new StaticValue(amount));
@ -37,6 +41,7 @@ public class DamageMultiEffect extends OneShotEffect {
public DamageMultiEffect(final DamageMultiEffect effect) {
super(effect);
this.damagedSet.addAll(effect.damagedSet);
this.amount = effect.amount;
this.sourceName = effect.sourceName;
}
@ -48,17 +53,21 @@ public class DamageMultiEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
if (!source.getTargets().isEmpty()) {
Target multiTarget = source.getTargets().get(0);
for (UUID target : multiTarget.getTargets()) {
Permanent permanent = game.getPermanent(target);
if (permanent != null) {
permanent.damage(multiTarget.getTargetAmount(target), source.getSourceId(), game, false, true);
} else {
Player player = game.getPlayer(target);
if (player != null) {
player.damage(multiTarget.getTargetAmount(target), source.getSourceId(), game, false, true);
}
this.damagedSet.clear();
if (source.getTargets().isEmpty()) {
return true;
}
Target multiTarget = source.getTargets().get(0);
for (UUID target : multiTarget.getTargets()) {
Permanent permanent = game.getPermanent(target);
if (permanent != null) {
if (permanent.damage(multiTarget.getTargetAmount(target), source.getSourceId(), game, false, true) > 0) {
damagedSet.add(new MageObjectReference(permanent, game));
} ;
} else {
Player player = game.getPlayer(target);
if (player != null) {
player.damage(multiTarget.getTargetAmount(target), source.getSourceId(), game, false, true);
}
}
}
@ -83,4 +92,8 @@ public class DamageMultiEffect extends OneShotEffect {
public void setSourceName(String sourceName) {
this.sourceName = sourceName;
}
public Set<MageObjectReference> getDamagedSet() {
return damagedSet;
}
}

View file

@ -1,4 +1,3 @@
package mage.abilities.effects.common;
import mage.abilities.Ability;
@ -25,7 +24,7 @@ import java.util.UUID;
/**
* Effect for the DevourAbility
*
* <p>
* 702.81. Devour 702.81a Devour is a static ability. "Devour N" means "As this
* object enters the battlefield, you may sacrifice any number of creatures.
* This permanent enters the battlefield with N +1/+1 counters on it for each
@ -33,7 +32,6 @@ import java.util.UUID;
* to the number of creatures the permanent devoured. "It devoured" means
* "sacrificed as a result of its devour ability as it entered the battlefield."
*
*
* @author LevelX2
*/
public class DevourEffect extends ReplacementEffectImpl {
@ -43,6 +41,7 @@ public class DevourEffect extends ReplacementEffectImpl {
static {
filter.add(AnotherPredicate.instance);
}
private final DevourFactor devourFactor;
public DevourEffect(DevourFactor devourFactor) {
@ -125,10 +124,10 @@ public class DevourEffect extends ReplacementEffectImpl {
return sb.toString();
}
public List<ArrayList<String>> getSubtypes(Game game, UUID permanentId) {
public List<SubTypeList> getSubtypes(Game game, UUID permanentId) {
Object object = game.getState().getValue(permanentId.toString() + "devoured");
if (object != null) {
return (List<ArrayList<String>>) object;
return (List<SubTypeList>) object;
}
return Collections.emptyList();
}
@ -136,7 +135,7 @@ public class DevourEffect extends ReplacementEffectImpl {
public int getDevouredCreaturesAmount(Game game, UUID permanentId) {
Object object = game.getState().getValue(permanentId.toString() + "devoured");
if (object != null) {
return ((List<ArrayList<String>>) object).size();
return ((List<SubTypeList>) object).size();
}
return 0;
}

View file

@ -0,0 +1,53 @@
package mage.abilities.effects.common;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.filter.FilterCard;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.events.GameEvent;
/**
* @author TheElk801
*/
public class DiscardCardControllerTriggeredAbility extends TriggeredAbilityImpl {
private final FilterCard filter;
public DiscardCardControllerTriggeredAbility(Effect effect, boolean isOptional) {
this(effect, isOptional, StaticFilters.FILTER_CARD);
}
public DiscardCardControllerTriggeredAbility(Effect effect, boolean isOptional, FilterCard filter) {
super(Zone.BATTLEFIELD, effect, isOptional);
this.filter = filter;
}
private DiscardCardControllerTriggeredAbility(final DiscardCardControllerTriggeredAbility ability) {
super(ability);
this.filter = ability.filter;
}
@Override
public DiscardCardControllerTriggeredAbility copy() {
return new DiscardCardControllerTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DISCARDED_CARD;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return event.getPlayerId().equals(getControllerId())
&& filter.match(game.getCard(event.getTargetId()), getId(), getControllerId(), game);
}
@Override
public String getRule() {
return "Whenever you discard " + filter.getMessage() + ", " + super.getRule();
}
}

View file

@ -1,8 +1,3 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package mage.abilities.effects.common;
import mage.abilities.TriggeredAbilityImpl;
@ -13,31 +8,30 @@ import mage.game.Game;
import mage.game.events.GameEvent;
/**
*
* @author jeffwadsworth
*/
public class DiscardsACardPlayerTriggeredAbility extends TriggeredAbilityImpl {
public class DiscardCardPlayerTriggeredAbility extends TriggeredAbilityImpl {
private SetTargetPointer setTargetPointer;
public DiscardsACardPlayerTriggeredAbility(Effect effect, boolean isOptional) {
public DiscardCardPlayerTriggeredAbility(Effect effect, boolean isOptional) {
this(effect, isOptional, SetTargetPointer.NONE);
}
public DiscardsACardPlayerTriggeredAbility(Effect effect, boolean isOptional, SetTargetPointer setTargetPointer) {
public DiscardCardPlayerTriggeredAbility(Effect effect, boolean isOptional, SetTargetPointer setTargetPointer) {
super(Zone.BATTLEFIELD, effect, isOptional);
this.setTargetPointer = setTargetPointer;
}
public DiscardsACardPlayerTriggeredAbility(final DiscardsACardPlayerTriggeredAbility ability) {
private DiscardCardPlayerTriggeredAbility(final DiscardCardPlayerTriggeredAbility ability) {
super(ability);
this.setTargetPointer = ability.setTargetPointer;
}
@Override
public DiscardsACardPlayerTriggeredAbility copy() {
return new DiscardsACardPlayerTriggeredAbility(this);
public DiscardCardPlayerTriggeredAbility copy() {
return new DiscardCardPlayerTriggeredAbility(this);
}
@Override
@ -52,6 +46,6 @@ import mage.game.events.GameEvent;
@Override
public String getRule() {
return "Whenever player discards a card, " + super.getRule();
return "Whenever a player discards a card, " + super.getRule();
}
}

View file

@ -1,11 +1,9 @@
package mage.abilities.effects.common;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.costs.Cost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect;
@ -15,9 +13,11 @@ import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
import mage.util.ManaUtil;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public class DoUnlessAnyPlayerPaysEffect extends OneShotEffect {
@ -65,34 +65,39 @@ public class DoUnlessAnyPlayerPaysEffect extends OneShotEffect {
Player controller = game.getPlayer(source.getControllerId());
MageObject sourceObject = game.getObject(source.getSourceId());
Cost costToPay;
if (controller != null
&& sourceObject != null) {
String costValueMessage;
if (controller != null && sourceObject != null) {
if (cost != null) {
costToPay = cost.copy();
costValueMessage = costToPay.getText();
} else {
costToPay = new GenericManaCost(genericMana.calculate(game, source, this));
costToPay = ManaUtil.createManaCost(genericMana, game, source, this);
costValueMessage = "{" + genericMana.calculate(game, source, this) + "}";
}
String message;
if (chooseUseText == null) {
String effectText = executingEffects.getText(source.getModes().getMode());
message = "Pay " + costToPay.getText() + " to prevent (" + effectText.substring(0, effectText.length() - 1) + ")?";
message = "Pay " + costValueMessage + " to prevent (" + effectText.substring(0, effectText.length() - 1) + ")?";
} else {
message = chooseUseText;
}
message = CardUtil.replaceSourceName(message, sourceObject.getName());
boolean result = true;
boolean doEffect = true;
// check if any player is willing to pay
for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null
&& costToPay.canPay(source, source.getSourceId(), player.getId(), game) && player.chooseUse(Outcome.Detriment, message, source, game)) {
if (player != null && player.canRespond()
&& costToPay.canPay(source, source.getSourceId(), player.getId(), game)
&& player.chooseUse(Outcome.Detriment, message, source, game)) {
costToPay.clearPaid();
if (costToPay.pay(source, game, source.getSourceId(), player.getId(), false, null)) {
if (!game.isSimulation()) {
game.informPlayers(player.getLogName() + " pays the cost to prevent the effect");
}
doEffect = false;
break;
}
}
}

View file

@ -1,11 +1,9 @@
package mage.abilities.effects.common;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.costs.Cost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect;
@ -16,9 +14,9 @@ import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.util.CardUtil;
import mage.util.ManaUtil;
/**
*
* @author MarcoMarin & L_J
*/
public class DoUnlessTargetPlayerOrTargetsControllerPaysEffect extends OneShotEffect {
@ -70,62 +68,65 @@ public class DoUnlessTargetPlayerOrTargetsControllerPaysEffect extends OneShotEf
@Override
public boolean apply(Game game, Ability source) {
Player targetController = game.getPlayer(this.getTargetPointer().getFirst(game, source));
Player player = game.getPlayer(this.getTargetPointer().getFirst(game, source));
Permanent targetPermanent = game.getPermanentOrLKIBattlefield(this.getTargetPointer().getFirst(game, source));
if (targetPermanent != null) {
targetController = game.getPlayer(targetPermanent.getControllerId());
player = game.getPlayer(targetPermanent.getControllerId());
}
if (targetController != null) {
MageObject sourceObject = game.getObject(source.getSourceId());
if (sourceObject != null) {
Cost costToPay;
if (cost != null) {
costToPay = cost.copy();
} else {
costToPay = new GenericManaCost(genericMana.calculate(game, source, this));
}
String message;
if (chooseUseText == null) {
String effectText = executingEffects.getText(source.getModes().getMode());
message = "Pay " + costToPay.getText() + " to prevent (" + effectText.substring(0, effectText.length() - 1) + ")?";
} else {
message = chooseUseText;
}
message = CardUtil.replaceSourceName(message, sourceObject.getName());
boolean result = true;
boolean doEffect = true;
// check if targetController is willing to pay
if (costToPay.canPay(source, source.getSourceId(), targetController.getId(), game) && targetController.chooseUse(Outcome.Detriment, message, source, game)) {
costToPay.clearPaid();
if (costToPay.pay(source, game, source.getSourceId(), targetController.getId(), false, null)) {
if (!game.isSimulation()) {
game.informPlayers(targetController.getLogName() + " pays the cost to prevent the effect");
}
doEffect = false;
}
}
// do the effects if not paid
if (doEffect) {
for (Effect effect : executingEffects) {
effect.setTargetPointer(this.targetPointer);
if (effect instanceof OneShotEffect) {
result &= effect.apply(game, source);
} else {
game.addEffect((ContinuousEffect) effect, source);
}
}
} else if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
if (otherwiseEffect instanceof OneShotEffect) {
result &= otherwiseEffect.apply(game, source);
} else {
game.addEffect((ContinuousEffect) otherwiseEffect, source);
}
}
return result;
MageObject sourceObject = game.getObject(source.getSourceId());
if (player != null && sourceObject != null) {
Cost costToPay;
String costValueMessage;
if (cost != null) {
costToPay = cost.copy();
costValueMessage = costToPay.getText();
} else {
costToPay = ManaUtil.createManaCost(genericMana, game, source, this);
costValueMessage = "{" + genericMana.calculate(game, source, this) + "}";
}
String message;
if (chooseUseText == null) {
String effectText = executingEffects.getText(source.getModes().getMode());
message = "Pay " + costValueMessage + " to prevent (" + effectText.substring(0, effectText.length() - 1) + ")?";
} else {
message = chooseUseText;
}
message = CardUtil.replaceSourceName(message, sourceObject.getName());
boolean result = true;
boolean doEffect = true;
// check if targetController is willing to pay
if (costToPay.canPay(source, source.getSourceId(), player.getId(), game)
&& player.chooseUse(Outcome.Detriment, message, source, game)) {
costToPay.clearPaid();
if (costToPay.pay(source, game, source.getSourceId(), player.getId(), false, null)) {
if (!game.isSimulation()) {
game.informPlayers(player.getLogName() + " pays the cost to prevent the effect");
}
doEffect = false;
}
}
// do the effects if not paid
if (doEffect) {
for (Effect effect : executingEffects) {
effect.setTargetPointer(this.targetPointer);
if (effect instanceof OneShotEffect) {
result &= effect.apply(game, source);
} else {
game.addEffect((ContinuousEffect) effect, source);
}
}
} else if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
if (otherwiseEffect instanceof OneShotEffect) {
result &= otherwiseEffect.apply(game, source);
} else {
game.addEffect((ContinuousEffect) otherwiseEffect, source);
}
}
return result;
}
return false;
}

View file

@ -29,7 +29,7 @@ import mage.players.Player;
* you can't cast spells, and At the beginning of each of your upkeeps for the
* rest of the game, copy this spell except for its epic ability. If the spell
* has any targets, you may choose new targets for the copy. See rule 706.10.
* 702.49b A player can't cast spells once a spell with epic he or she controls
* 702.49b A player can't cast spells once a spell with epic they control
* resolves, but effects (such as the epic ability itself) can still put copies
* of spells onto the stack. *
*/

View file

@ -0,0 +1,107 @@
package mage.abilities.effects.common;
import mage.abilities.Ability;
import mage.abilities.MageSingleton;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.OneShotEffect;
import mage.cards.AdventureCardSpell;
import mage.cards.Card;
import mage.constants.AsThoughEffectType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.game.ExileZone;
import mage.game.Game;
import mage.game.stack.Spell;
import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
import java.util.UUID;
/**
* @author phulin
*/
public class ExileAdventureSpellEffect extends OneShotEffect implements MageSingleton {
private static final ExileAdventureSpellEffect instance = new ExileAdventureSpellEffect();
public static ExileAdventureSpellEffect getInstance() {
return instance;
}
public static UUID adventureExileId(UUID controllerId, Game game) {
return CardUtil.getExileZoneId(controllerId.toString() + "- On an Adventure", game);
}
private ExileAdventureSpellEffect() {
super(Outcome.Exile);
staticText = "";
}
@Override
public ExileAdventureSpellEffect copy() {
return instance;
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
Spell spell = game.getStack().getSpell(source.getId());
if (spell != null && !spell.isCopy()) {
Card spellCard = spell.getCard();
if (spellCard instanceof AdventureCardSpell) {
UUID exileId = adventureExileId(controller.getId(), game);
game.getExile().createZone(exileId, "On an Adventure");
AdventureCardSpell adventureSpellCard = (AdventureCardSpell) spellCard;
Card parentCard = adventureSpellCard.getParentCard();
if (controller.moveCardsToExile(parentCard, source, game, true, exileId, "On an Adventure")) {
ContinuousEffect effect = new AdventureCastFromExileEffect();
effect.setTargetPointer(new FixedTarget(parentCard.getId(), game));
game.addEffect(effect, source);
}
}
}
return true;
}
return false;
}
}
class AdventureCastFromExileEffect extends AsThoughEffectImpl {
public AdventureCastFromExileEffect() {
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit);
staticText = "Then exile this card. You may cast the creature later from exile.";
}
public AdventureCastFromExileEffect(final AdventureCastFromExileEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
return true;
}
@Override
public AdventureCastFromExileEffect copy() {
return new AdventureCastFromExileEffect(this);
}
@Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
UUID targetId = getTargetPointer().getFirst(game, source);
ExileZone adventureExileZone = game.getExile().getExileZone(ExileAdventureSpellEffect.adventureExileId(affectedControllerId, game));
if (targetId == null) {
this.discard();
} else if (objectId.equals(targetId)
&& affectedControllerId.equals(source.getControllerId())
&& adventureExileZone.contains(objectId)) {
Card card = game.getCard(objectId);
return card != null;
}
return false;
}
}

View file

@ -58,7 +58,7 @@ public class ExileReturnBattlefieldOwnerNextEndStepSourceEffect extends OneShotE
int zcc = game.getState().getZoneChangeCounter(permanent.getId());
boolean exiled = controller.moveCardToExileWithInfo(permanent, source.getSourceId(), permanent.getIdName(), source.getSourceId(), game, Zone.BATTLEFIELD, true);
if (exiled || (returnAlways && (zcc == game.getState().getZoneChangeCounter(permanent.getId()) - 1))) {
//create delayed triggered ability and return it from every public zone he was next moved to
//create delayed triggered ability and return it from every public zone it was next moved to
AtTheBeginOfNextEndStepDelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(
new ReturnToBattlefieldUnderOwnerControlSourceEffect(returnTapped, zcc + 1));
game.addDelayedTriggeredAbility(delayedAbility, source);

View file

@ -1,6 +1,5 @@
package mage.abilities.effects.common;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.OneShotEffect;
@ -9,8 +8,9 @@ import mage.constants.Outcome;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class FightTargetsEffect extends OneShotEffect {
@ -57,27 +57,23 @@ public class FightTargetsEffect extends OneShotEffect {
return creature1.fight(creature2, source, game);
}
}
if (!game.isSimulation()) {
game.informPlayers(card.getName() + " has been fizzled.");
}
}
if (!game.isSimulation()) {
game.informPlayers(card.getName() + " has been fizzled.");
}
return false;
}
@Override
public FightTargetsEffect
copy() {
public FightTargetsEffect copy() {
return new FightTargetsEffect(this);
}
@Override
public String
getText(Mode mode
) {
if (staticText
!= null && !staticText
.isEmpty()) {
public String getText(Mode mode) {
if (staticText != null && !staticText.isEmpty()) {
return staticText;
}
@ -86,4 +82,4 @@ public class FightTargetsEffect extends OneShotEffect {
.getTargets().get(1).getTargetName();
}
}
}

View file

@ -9,6 +9,7 @@ import mage.cards.Card;
import mage.constants.Outcome;
import mage.game.ExileZone;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.util.CardUtil;
@ -34,8 +35,14 @@ public class HideawayPlayEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
ExileZone zone = game.getExile().getExileZone(CardUtil.getCardExileZoneId(game, source));
if (zone == null || zone.isEmpty()) {
ExileZone zone = null;
Permanent permanent = game.getPermanentOrLKIBattlefield(source.getSourceId());
if (permanent != null) {
zone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source.getSourceId(), permanent.getZoneChangeCounter(game)));
}
if (zone == null
|| zone.isEmpty()) {
return true;
}
Card card = zone.getCards(game).iterator().next();

View file

@ -1,14 +1,17 @@
package mage.abilities.effects.common;
import mage.constants.Outcome;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class InfoEffect extends OneShotEffect {
@ -32,4 +35,11 @@ public class InfoEffect extends OneShotEffect {
return new InfoEffect(this);
}
public static void addInfoToPermanent(Game game, Ability source, Permanent permanent, String info) {
// add simple static info to permanent's rules
SimpleStaticAbility ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new InfoEffect(info));
GainAbilityTargetEffect gainAbilityEffect = new GainAbilityTargetEffect(ability, Duration.WhileOnBattlefield);
gainAbilityEffect.setTargetPointer(new FixedTarget(permanent.getId()));
game.addEffect(gainAbilityEffect, source);
}
}

View file

@ -29,8 +29,6 @@
*/
package mage.abilities.effects.common;
import static java.lang.Integer.min;
import java.util.Locale;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.dynamicvalue.DynamicValue;
@ -45,8 +43,11 @@ import mage.players.Player;
import mage.target.TargetCard;
import mage.util.CardUtil;
import java.util.Locale;
import static java.lang.Integer.min;
/**
*
* @author LevelX
*/
public class LookLibraryAndPickControllerEffect extends LookLibraryControllerEffect {
@ -63,36 +64,35 @@ public class LookLibraryAndPickControllerEffect extends LookLibraryControllerEff
//TODO: These constructors are a mess
public LookLibraryAndPickControllerEffect(DynamicValue numberOfCards,
boolean mayShuffleAfter, DynamicValue numberToPick,
FilterCard pickFilter, boolean putOnTop) {
boolean mayShuffleAfter, DynamicValue numberToPick,
FilterCard pickFilter, boolean putOnTop) {
this(numberOfCards, mayShuffleAfter, numberToPick, pickFilter,
putOnTop, true);
}
public LookLibraryAndPickControllerEffect(DynamicValue numberOfCards,
boolean mayShuffleAfter, DynamicValue numberToPick,
FilterCard pickFilter, boolean putOnTop, boolean reveal) {
boolean mayShuffleAfter, DynamicValue numberToPick,
FilterCard pickFilter, boolean putOnTop, boolean reveal) {
this(numberOfCards, mayShuffleAfter, numberToPick, pickFilter,
Zone.LIBRARY, putOnTop, reveal);
}
public LookLibraryAndPickControllerEffect(DynamicValue numberOfCards,
boolean mayShuffleAfter, DynamicValue numberToPick,
FilterCard pickFilter, Zone targetZoneLookedCards,
boolean putOnTop, boolean reveal) {
boolean mayShuffleAfter, DynamicValue numberToPick,
FilterCard pickFilter, Zone targetZoneLookedCards,
boolean putOnTop, boolean reveal) {
this(numberOfCards, mayShuffleAfter, numberToPick, pickFilter,
targetZoneLookedCards, putOnTop, reveal, reveal);
}
public LookLibraryAndPickControllerEffect(int numberOfCards,
int numberToPick, FilterCard pickFilter, boolean upTo) {
int numberToPick, FilterCard pickFilter, boolean upTo) {
this(new StaticValue(numberOfCards), false,
new StaticValue(numberToPick), pickFilter, Zone.LIBRARY, false,
true, upTo);
}
/**
*
* @param numberOfCards
* @param numberToPick
* @param pickFilter
@ -102,8 +102,8 @@ public class LookLibraryAndPickControllerEffect extends LookLibraryControllerEff
* @param optional
*/
public LookLibraryAndPickControllerEffect(int numberOfCards,
int numberToPick, FilterCard pickFilter, boolean reveal,
boolean upTo, Zone targetZonePickedCards, boolean optional) {
int numberToPick, FilterCard pickFilter, boolean reveal,
boolean upTo, Zone targetZonePickedCards, boolean optional) {
this(new StaticValue(numberOfCards), false,
new StaticValue(numberToPick), pickFilter, Zone.LIBRARY, false,
reveal, upTo, targetZonePickedCards, optional, true, true);
@ -111,45 +111,43 @@ public class LookLibraryAndPickControllerEffect extends LookLibraryControllerEff
}
/**
*
* @param numberOfCards
* @param mayShuffleAfter
* @param numberToPick
* @param pickFilter
* @param targetZoneLookedCards
* @param putOnTop if zone for the rest is library decide if cards go to top
* or bottom
* @param putOnTop if zone for the rest is library decide if cards go to top
* or bottom
* @param reveal
* @param upTo
*/
public LookLibraryAndPickControllerEffect(DynamicValue numberOfCards,
boolean mayShuffleAfter, DynamicValue numberToPick,
FilterCard pickFilter, Zone targetZoneLookedCards,
boolean putOnTop, boolean reveal, boolean upTo) {
boolean mayShuffleAfter, DynamicValue numberToPick,
FilterCard pickFilter, Zone targetZoneLookedCards,
boolean putOnTop, boolean reveal, boolean upTo) {
this(numberOfCards, mayShuffleAfter, numberToPick, pickFilter,
targetZoneLookedCards, putOnTop, reveal, upTo, Zone.HAND,
false, true, true);
}
/**
*
* @param numberOfCards
* @param mayShuffleAfter
* @param numberToPick
* @param pickFilter
* @param targetZoneLookedCards
* @param putOnTop if zone for the rest is library decide if cards go to top
* or bottom
* @param putOnTop if zone for the rest is library decide if cards go to top
* or bottom
* @param reveal
* @param upTo
* @param targetZonePickedCards
* @param optional
*/
public LookLibraryAndPickControllerEffect(DynamicValue numberOfCards,
boolean mayShuffleAfter, DynamicValue numberToPick,
FilterCard pickFilter, Zone targetZoneLookedCards, boolean putOnTop,
boolean reveal, boolean upTo, Zone targetZonePickedCards,
boolean optional) {
boolean mayShuffleAfter, DynamicValue numberToPick,
FilterCard pickFilter, Zone targetZoneLookedCards, boolean putOnTop,
boolean reveal, boolean upTo, Zone targetZonePickedCards,
boolean optional) {
this(numberOfCards, mayShuffleAfter, numberToPick, pickFilter,
targetZoneLookedCards, putOnTop, reveal, upTo,
@ -157,14 +155,13 @@ public class LookLibraryAndPickControllerEffect extends LookLibraryControllerEff
}
/**
*
* @param numberOfCards
* @param mayShuffleAfter
* @param numberToPick
* @param pickFilter
* @param targetZoneLookedCards
* @param putOnTop if zone for the rest is library decide if cards go to top
* or bottom
* @param putOnTop if zone for the rest is library decide if cards go to top
* or bottom
* @param reveal
* @param upTo
* @param targetZonePickedCards
@ -173,10 +170,10 @@ public class LookLibraryAndPickControllerEffect extends LookLibraryControllerEff
* @param anyOrder
*/
public LookLibraryAndPickControllerEffect(DynamicValue numberOfCards,
boolean mayShuffleAfter, DynamicValue numberToPick,
FilterCard pickFilter, Zone targetZoneLookedCards, boolean putOnTop,
boolean reveal, boolean upTo, Zone targetZonePickedCards,
boolean optional, boolean putOnTopSelected, boolean anyOrder) {
boolean mayShuffleAfter, DynamicValue numberToPick,
FilterCard pickFilter, Zone targetZoneLookedCards, boolean putOnTop,
boolean reveal, boolean upTo, Zone targetZonePickedCards,
boolean optional, boolean putOnTopSelected, boolean anyOrder) {
super(Outcome.DrawCard, numberOfCards, mayShuffleAfter,
targetZoneLookedCards, putOnTop);
this.numberToPick = numberToPick;
@ -334,7 +331,7 @@ public class LookLibraryAndPickControllerEffect extends LookLibraryControllerEff
sb.append("on the bottom");
}
sb.append(" of your library in ");
if (anyOrder) {
if (anyOrder && !backInRandomOrder) {
sb.append("any");
} else {
sb.append("a random");

View file

@ -1,11 +1,11 @@
package mage.abilities.effects.common;
import mage.constants.Outcome;
import mage.constants.TargetController;
import mage.abilities.Ability;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.constants.TargetController;
import mage.filter.FilterPermanent;
import mage.filter.predicate.permanent.ControllerPredicate;
import mage.filter.predicate.permanent.TokenPredicate;
@ -17,7 +17,6 @@ import mage.target.TargetPermanent;
import mage.target.targetpointer.FixedTarget;
/**
*
* @author LevelX2
*/
//
@ -31,6 +30,7 @@ import mage.target.targetpointer.FixedTarget;
//
public class PopulateEffect extends OneShotEffect {
private final boolean tappedAndAttacking;
private static final FilterPermanent filter = new FilterPermanent("token for populate");
static {
@ -43,35 +43,44 @@ public class PopulateEffect extends OneShotEffect {
}
public PopulateEffect(String prefixText) {
super(Outcome.Copy);
this(false);
this.staticText = (!prefixText.isEmpty() ? prefixText + " p" : "P") + "opulate <i>(Create a token that's a copy of a creature token you control.)</i>";
}
public PopulateEffect(boolean tappedAndAttacking) {
super(Outcome.Copy);
this.tappedAndAttacking = tappedAndAttacking;
this.staticText = "populate. The token enters the battlefield tapped and attacking. " +
"<i>(To populate, create a token that's a copy of a creature token you control.)</i>";
}
public PopulateEffect(final PopulateEffect effect) {
super(effect);
this.tappedAndAttacking = effect.tappedAndAttacking;
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player != null) {
Target target = new TargetPermanent(filter);
target.setNotTarget(true);
if (target.canChoose(source.getControllerId(), game)) {
player.choose(Outcome.Copy, target, source.getSourceId(), game);
Permanent tokenToCopy = game.getPermanent(target.getFirstTarget());
if (tokenToCopy != null) {
if (!game.isSimulation()) {
game.informPlayers("Token selected for populate: " + tokenToCopy.getLogName());
}
Effect effect = new CreateTokenCopyTargetEffect();
effect.setTargetPointer(new FixedTarget(target.getFirstTarget()));
return effect.apply(game, source);
}
}
if (player == null) {
return false;
}
Target target = new TargetPermanent(filter);
target.setNotTarget(true);
if (!target.canChoose(source.getControllerId(), game)) {
return true;
}
return false;
player.choose(Outcome.Copy, target, source.getSourceId(), game);
Permanent tokenToCopy = game.getPermanent(target.getFirstTarget());
if (tokenToCopy == null) {
return true;
}
game.informPlayers("Token selected for populate: " + tokenToCopy.getLogName());
Effect effect = new CreateTokenCopyTargetEffect(
null, null, false, 1, tappedAndAttacking, tappedAndAttacking
);
effect.setTargetPointer(new FixedTarget(target.getFirstTarget()));
return effect.apply(game, source);
}
@Override

View file

@ -1,44 +1,46 @@
package mage.abilities.effects.common;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.PreventionEffectImpl;
import mage.constants.Duration;
import mage.filter.FilterInPlay;
import mage.filter.common.FilterCreatureOrPlayer;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.FilterPermanent;
import mage.filter.FilterPlayer;
import mage.filter.common.FilterPermanentOrPlayer;
import mage.filter.predicate.other.PlayerIdPredicate;
import mage.filter.predicate.permanent.PermanentIdPredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class PreventAllDamageToAllEffect extends PreventionEffectImpl {
protected FilterInPlay filter;
public PreventAllDamageToAllEffect(Duration duration, FilterCreaturePermanent filter) {
this(duration, createFilter(filter));
protected FilterPermanentOrPlayer filter;
public PreventAllDamageToAllEffect(Duration duration, FilterPermanent filterPermanent) {
this(duration, createFilter(filterPermanent, null));
}
public PreventAllDamageToAllEffect(Duration duration, FilterInPlay filter) {
public PreventAllDamageToAllEffect(Duration duration, FilterPermanent filterPermanent, boolean onlyCombat) {
this(duration, createFilter(filterPermanent, null), onlyCombat);
}
public PreventAllDamageToAllEffect(Duration duration, FilterPermanentOrPlayer filter) {
this(duration, filter, false);
}
public PreventAllDamageToAllEffect(Duration duration, FilterInPlay filter, boolean onlyCombat) {
public PreventAllDamageToAllEffect(Duration duration, FilterPermanentOrPlayer filter, boolean onlyCombat) {
super(duration, Integer.MAX_VALUE, onlyCombat);
this.filter = filter;
staticText = "Prevent all "
+ (onlyCombat ? "combat ":"")
+ "damage that would be dealt to "
+ (onlyCombat ? "combat " : "")
+ "damage that would be dealt to "
+ filter.getMessage()
+ (duration.toString().isEmpty() ?"": ' ' + duration.toString());
+ (duration.toString().isEmpty() ? "" : ' ' + duration.toString());
}
public PreventAllDamageToAllEffect(final PreventAllDamageToAllEffect effect) {
@ -46,13 +48,25 @@ public class PreventAllDamageToAllEffect extends PreventionEffectImpl {
this.filter = effect.filter.copy();
}
private static FilterInPlay createFilter(FilterCreaturePermanent filter) {
FilterCreatureOrPlayer newfilter = new FilterCreatureOrPlayer(filter.getMessage());
newfilter.setCreatureFilter(filter);
newfilter.getPlayerFilter().add(new PlayerIdPredicate(UUID.randomUUID()));
return newfilter;
private static FilterPermanentOrPlayer createFilter(FilterPermanent filterPermanent, FilterPlayer filterPlayer) {
String message = String.join(
" and ",
filterPermanent != null ? filterPermanent.getMessage() : "",
filterPlayer != null ? filterPlayer.getMessage() : "");
FilterPermanent filter1 = filterPermanent;
if (filter1 == null) {
filter1 = new FilterPermanent();
filter1.add(new PermanentIdPredicate(UUID.randomUUID())); // disable filter
}
FilterPlayer filter2 = filterPlayer;
if (filter2 == null) {
filter2 = new FilterPlayer();
filter2.add(new PlayerIdPredicate(UUID.randomUUID())); // disable filter
}
return new FilterPermanentOrPlayer(message, filter1, filter2);
}
@Override
public PreventAllDamageToAllEffect copy() {
return new PreventAllDamageToAllEffect(this);
@ -66,17 +80,9 @@ public class PreventAllDamageToAllEffect extends PreventionEffectImpl {
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (super.applies(event, source, game)) {
Permanent permanent = game.getPermanent(event.getTargetId());
if (permanent != null) {
if (filter.match(permanent, source.getSourceId(), source.getControllerId(), game)) {
return true;
}
}
else {
Player player = game.getPlayer(event.getTargetId());
if (player != null && filter.match(player, source.getSourceId(), source.getControllerId(), game)) {
return true;
}
MageObject object = game.getObject(event.getTargetId());
if (object != null) {
return filter.match(object, source.getSourceId(), source.getControllerId(), game);
}
}
return false;

View file

@ -1,5 +1,3 @@
package mage.abilities.effects.common;
import mage.abilities.Ability;
@ -9,33 +7,27 @@ import mage.game.Game;
import mage.game.events.GameEvent;
/**
*
* @author jeffwadsworth
*/
public class PreventCombatDamageBySourceEffect extends PreventionEffectImpl {
public PreventCombatDamageBySourceEffect(Duration duration) {
super(duration, Integer.MAX_VALUE, true);
staticText = "Prevent all combat damage that would be dealt by {this}" + duration.toString();
super(duration, Integer.MAX_VALUE, true);
staticText = "Prevent all combat damage that would be dealt by {this}" + duration.toString();
}
public PreventCombatDamageBySourceEffect(final PreventCombatDamageBySourceEffect effect) {
super(effect);
super(effect);
}
@Override
public PreventCombatDamageBySourceEffect copy() {
return new PreventCombatDamageBySourceEffect(this);
return new PreventCombatDamageBySourceEffect(this);
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (super.applies(event, source, game)) {
if (event.getSourceId().equals(source.getSourceId())) {
return true;
}
}
return false;
return super.applies(event, source, game)
&& event.getSourceId().equals(source.getSourceId());
}
}
}

View file

@ -1,8 +1,5 @@
package mage.abilities.effects.common;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.Mode;
@ -10,14 +7,19 @@ import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.PreventionEffectImpl;
import mage.constants.Duration;
import mage.game.Game;
import mage.game.events.DamageEvent;
import mage.game.events.GameEvent;
import mage.game.events.PreventDamageEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.Target;
import mage.target.TargetAmount;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public class PreventDamageToTargetMultiAmountEffect extends PreventionEffectImpl {
@ -77,7 +79,7 @@ public class PreventDamageToTargetMultiAmountEffect extends PreventionEffectImpl
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
int targetAmount = targetAmountMap.get(event.getTargetId());
GameEvent preventEvent = new GameEvent(GameEvent.EventType.PREVENT_DAMAGE, event.getTargetId(), source.getSourceId(), source.getControllerId(), event.getAmount(), false);
GameEvent preventEvent = new PreventDamageEvent(event.getTargetId(), source.getSourceId(), source.getControllerId(), event.getAmount(), ((DamageEvent) event).isCombatDamage());
if (!game.replaceEvent(preventEvent)) {
if (event.getAmount() >= targetAmount) {
int damage = targetAmount;

View file

@ -82,9 +82,9 @@ public class PutCardFromHandOntoBattlefieldEffect extends OneShotEffect {
}
if (useTargetController) {
return "that player may put " + filter.getMessage() + " from their hand onto the battlefield";
return "that player may put " + filter.getMessage() + " from their hand onto the battlefield" + (this.tapped ? " tapped" : "");
} else {
return "you may put " + filter.getMessage() + " from your hand onto the battlefield";
return "you may put " + filter.getMessage() + " from your hand onto the battlefield" + (this.tapped ? " tapped" : "");
}
}
}

View file

@ -1,10 +1,6 @@
package mage.abilities.effects.common;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.OneShotEffect;
@ -12,13 +8,17 @@ import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.Target;
import mage.util.CardUtil;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
/**
* @author BetaSteward_at_googlemail.com
*/
@ -62,9 +62,15 @@ public class PutOnLibraryTargetEffect extends OneShotEffect {
}
break;
case GRAVEYARD:
Card card = game.getCard(targetId);
if (card != null && game.getState().getZone(targetId) == Zone.GRAVEYARD) {
cards.add(card);
Card graveyardCard = game.getCard(targetId);
if (graveyardCard != null) {
cards.add(graveyardCard);
}
break;
case STACK:
Card stackSpellCard = game.getSpell(targetId).getCard();
if (stackSpellCard != null) {
cards.add(stackSpellCard);
}
break;
}
@ -76,7 +82,7 @@ public class PutOnLibraryTargetEffect extends OneShotEffect {
if (card != null) {
Player owner = game.getPlayer(card.getOwnerId());
Cards cardsPlayer = new CardsImpl();
for (Iterator<Card> iterator = cards.iterator(); iterator.hasNext();) {
for (Iterator<Card> iterator = cards.iterator(); iterator.hasNext(); ) {
Card next = iterator.next();
if (next.isOwnedBy(owner.getId())) {
cardsPlayer.add(next);
@ -95,7 +101,7 @@ public class PutOnLibraryTargetEffect extends OneShotEffect {
if (permanent != null) {
Player owner = game.getPlayer(permanent.getOwnerId());
Cards cardsPlayer = new CardsImpl();
for (Iterator<Permanent> iterator = permanents.iterator(); iterator.hasNext();) {
for (Iterator<Permanent> iterator = permanents.iterator(); iterator.hasNext(); ) {
Permanent next = iterator.next();
if (next.isOwnedBy(owner.getId())) {
cardsPlayer.add(next);

Some files were not shown because too many files have changed in this diff Show more