Merge branch 'master' into refactor_promo_sets

This commit is contained in:
Oleg Agafonov 2020-08-07 02:48:40 +02:00 committed by GitHub
commit 9e6a348cb1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4098 changed files with 115584 additions and 60811 deletions

View file

@ -1,5 +1,9 @@
package mage;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import mage.abilities.Abilities;
import mage.abilities.Ability;
import mage.abilities.costs.mana.ManaCost;
@ -15,12 +19,6 @@ import mage.game.Game;
import mage.game.events.ZoneChangeEvent;
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 {
String getName();
@ -33,7 +31,7 @@ public interface MageObject extends MageItem, Serializable {
void setName(String name);
Set<CardType> getCardType();
ArrayList<CardType> getCardType();
SubTypeList getSubtype(Game game);
@ -41,9 +39,15 @@ public interface MageObject extends MageItem, Serializable {
Set<SuperType> getSuperType();
/**
* For cards: return basic abilities (without dynamic added) For permanents:
* return all abilities (dynamic ability inserts into permanent)
*
* @return
*/
Abilities<Ability> getAbilities();
boolean hasAbility(UUID abilityId, Game game);
boolean hasAbility(Ability ability, Game game);
ObjectColor getColor(Game game);
@ -180,9 +184,9 @@ public interface MageObject extends MageItem, Serializable {
}
if (this.isCreature() && otherCard.isCreature()) {
if (this.getAbilities().contains(ChangelingAbility.getInstance())
if (this.hasAbility(ChangelingAbility.getInstance(), game)
|| this.isAllCreatureTypes()
|| otherCard.getAbilities().contains(ChangelingAbility.getInstance())
|| otherCard.hasAbility(ChangelingAbility.getInstance(), game)
|| otherCard.isAllCreatureTypes()) {
return true;
}
@ -200,7 +204,7 @@ public interface MageObject extends MageItem, Serializable {
void setIsAllCreatureTypes(boolean value);
default void addCardTypes(Set<CardType> cardType) {
default void addCardTypes(ArrayList<CardType> cardType) {
getCardType().addAll(cardType);
}

View file

@ -1,5 +1,6 @@
package mage;
import java.util.*;
import mage.abilities.Abilities;
import mage.abilities.AbilitiesImpl;
import mage.abilities.Ability;
@ -21,8 +22,6 @@ import mage.game.permanent.Permanent;
import mage.util.GameLog;
import mage.util.SubTypeList;
import java.util.*;
public abstract class MageObjectImpl implements MageObject {
protected UUID objectId;
@ -32,7 +31,7 @@ public abstract class MageObjectImpl implements MageObject {
protected ObjectColor color;
protected ObjectColor frameColor;
protected FrameStyle frameStyle;
protected Set<CardType> cardType = EnumSet.noneOf(CardType.class);
protected ArrayList<CardType> cardType = new ArrayList<>();
protected SubTypeList subtype = new SubTypeList();
protected boolean isAllCreatureTypes;
protected Set<SuperType> supertype = EnumSet.noneOf(SuperType.class);
@ -112,7 +111,7 @@ public abstract class MageObjectImpl implements MageObject {
}
@Override
public Set<CardType> getCardType() {
public ArrayList<CardType> getCardType() {
return cardType;
}
@ -132,12 +131,12 @@ public abstract class MageObjectImpl implements MageObject {
}
@Override
public boolean hasAbility(UUID abilityId, Game game) {
if (this.getAbilities().containsKey(abilityId)) {
public boolean hasAbility(Ability ability, Game game) {
if (this.getAbilities().contains(ability)) {
return true;
}
Abilities<Ability> otherAbilities = game.getState().getAllOtherAbilities(getId());
return otherAbilities != null && otherAbilities.containsKey(abilityId);
return otherAbilities != null && otherAbilities.contains(ability);
}
@Override
@ -171,39 +170,22 @@ public abstract class MageObjectImpl implements MageObject {
// its frame colors.
if (this.isLand()) {
ObjectColor cl = frameColor.copy();
Set<ManaType> manaTypes = EnumSet.noneOf(ManaType.class);
for (Ability ab : getAbilities()) {
if (ab instanceof ActivatedManaAbilityImpl) {
ActivatedManaAbilityImpl mana = (ActivatedManaAbilityImpl) ab;
try {
List<Mana> manaAdded = mana.getNetMana(game);
for (Mana m : manaAdded) {
if (m.getAny() > 0) {
return new ObjectColor("WUBRG");
}
if (m.getWhite() > 0) {
cl.setWhite(true);
}
if (m.getBlue() > 0) {
cl.setBlue(true);
}
if (m.getBlack() > 0) {
cl.setBlack(true);
}
if (m.getRed() > 0) {
cl.setRed(true);
}
if (m.getGreen() > 0) {
cl.setGreen(true);
}
}
} catch (NullPointerException e) {
// Ability depends on game
// but no game passed
// All such abilities are 5-color ones
return new ObjectColor("WUBRG");
}
manaTypes.addAll(((ActivatedManaAbilityImpl) ab).getProducableManaTypes(game));
}
}
cl.setWhite(manaTypes.contains(ManaType.WHITE));
cl.setBlue(manaTypes.contains(ManaType.BLUE));
cl.setBlack(manaTypes.contains(ManaType.BLACK));
cl.setRed(manaTypes.contains(ManaType.RED));
cl.setGreen(manaTypes.contains(ManaType.GREEN));
// // Ability depends on game
// // but no game passed
// // All such abilities are 5-color ones
// return new ObjectColor("WUBRG");
return cl;
} else {
// For everything else, just return the frame colors
@ -329,7 +311,7 @@ public abstract class MageObjectImpl implements MageObject {
*/
@Override
public void removePTCDA() {
for (Iterator<Ability> iter = this.getAbilities().iterator(); iter.hasNext(); ) {
for (Iterator<Ability> iter = this.getAbilities().iterator(); iter.hasNext();) {
Ability ability = iter.next();
for (Effect effect : ability.getEffects()) {
if (effect instanceof ContinuousEffect && ((ContinuousEffect) effect).getSublayer() == SubLayer.CharacteristicDefining_7a) {

View file

@ -43,13 +43,13 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
* Creates a {@link Mana} object with the passed in values. Values can not
* be less than 0. Any values less than 0 will be logged and set to 0.
*
* @param red total Red mana to have.
* @param green total Green mana to have.
* @param blue total Blue mana to have.
* @param white total White mana to have.
* @param black total Black mana to have.
* @param generic total Generic mana to have.
* @param any total Any mana to have.
* @param red total Red mana to have.
* @param green total Green mana to have.
* @param blue total Blue mana to have.
* @param white total White mana to have.
* @param black total Black mana to have.
* @param generic total Generic mana to have.
* @param any total Any mana to have.
* @param colorless total Colorless mana to have.
*/
public Mana(final int red, final int green, final int blue, final int white, final int black, final int generic, final int any, final int colorless) {
@ -142,6 +142,35 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
}
}
public Mana(final ManaType manaType, int num) {
Objects.requireNonNull(manaType, "The passed in ManaType can not be null");
switch (manaType) {
case GREEN:
green = num;
break;
case RED:
red = num;
break;
case BLACK:
black = num;
break;
case BLUE:
blue = num;
break;
case WHITE:
white = num;
break;
case COLORLESS:
colorless = num;
break;
case GENERIC:
generic = num;
break;
default:
throw new IllegalArgumentException("Unknown manaType: " + manaType);
}
}
/**
* Creates a {@link Mana} object with the passed in {@code num} of Red mana.
* {@code num} can not be a negative value. Negative values will be logged
@ -161,7 +190,7 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
*
* @param num value of Green mana to create.
* @return a {@link Mana} object with the passed in {@code num} of Green
* mana.
* mana.
*/
public static Mana GreenMana(int num) {
return new Mana(0, notNegative(num, "Green"), 0, 0, 0, 0, 0, 0);
@ -174,7 +203,7 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
*
* @param num value of Blue mana to create.
* @return a {@link Mana} object with the passed in {@code num} of Blue
* mana.
* mana.
*/
public static Mana BlueMana(int num) {
return new Mana(0, 0, notNegative(num, "Blue"), 0, 0, 0, 0, 0);
@ -187,7 +216,7 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
*
* @param num value of White mana to create.
* @return a {@link Mana} object with the passed in {@code num} of White
* mana.
* mana.
*/
public static Mana WhiteMana(int num) {
return new Mana(0, 0, 0, notNegative(num, "White"), 0, 0, 0, 0);
@ -200,7 +229,7 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
*
* @param num value of Black mana to create.
* @return a {@link Mana} object with the passed in {@code num} of Black
* mana.
* mana.
*/
public static Mana BlackMana(int num) {
return new Mana(0, 0, 0, 0, notNegative(num, "Black"), 0, 0, 0);
@ -213,7 +242,7 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
*
* @param num value of Generic mana to create.
* @return a {@link Mana} object with the passed in {@code num} of Generic
* mana.
* mana.
*/
public static Mana GenericMana(int num) {
return new Mana(0, 0, 0, 0, 0, notNegative(num, "Generic"), 0, 0);
@ -226,7 +255,7 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
*
* @param num value of Colorless mana to create.
* @return a {@link Mana} object with the passed in {@code num} of Colorless
* mana.
* mana.
*/
public static Mana ColorlessMana(int num) {
return new Mana(0, 0, 0, 0, 0, 0, 0, notNegative(num, "Colorless"));
@ -444,7 +473,7 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
*
* @param filter the colors of mana to return the count for.
* @return the count of filtered mana provided by the passed in
* {@link FilterMana}.
* {@link FilterMana}.
*/
public int count(final FilterMana filter) {
if (filter == null) {
@ -898,10 +927,10 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
* Returns if this objects mana contains any coloured mana the same as the
* passed in {@link Mana}'s mana.
*
* @param mana the mana to check for
* @param mana the mana to check for
* @param includeColorless also check for colorless
* @return true if this contains any of the same type of coloured mana that
* this has
* this has
*/
public boolean containsAny(final Mana mana, boolean includeColorless) {
if (mana.black > 0 && this.black > 0) {
@ -929,7 +958,7 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
*
* @param color the color to return the count for.
* @return the total count of mana in this object as specified by the passed
* in {@link ColoredManaSymbol}.
* in {@link ColoredManaSymbol}.
*/
public int getColor(final ColoredManaSymbol color) {
if (color == ColoredManaSymbol.G) {
@ -956,7 +985,7 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
*
* @param manaType the type to return the count for.
* @return the total count of mana in this object as specified by the passed
* in {@link ManaType}.
* in {@link ManaType}.
*/
public int get(final ManaType manaType) {
switch (manaType) {
@ -981,7 +1010,7 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
* {@code amount} .
*
* @param manaType the color of the mana to set
* @param amount the value to set the mana too
* @param amount the value to set the mana too
*/
public void set(final ManaType manaType, final int amount) {
switch (manaType) {
@ -1056,7 +1085,7 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
*
* @param mana the mana to compare with
* @return if this object has more than or equal mana to the passed in
* {@link Mana}.
* {@link Mana}.
*/
public boolean includesMana(Mana mana) {
return this.green >= mana.green
@ -1123,10 +1152,7 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
}
}
if (lessMana.getColorless() > moreMana.getColorless()) {
anyDiff -= lessMana.getColorless() - moreMana.getColorless();
if (anyDiff < 0) {
return null;
}
return null; // Any (color) can't produce colorless mana
}
if (lessMana.getAny() > moreMana.getAny()) {
return null;
@ -1217,10 +1243,10 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
* is negative, it is logged and 0 is returned.
*
* @param value the value to check.
* @param name the name of the value to check. Used to make logging of the
* {@code value} easier
* @param name the name of the value to check. Used to make logging of the
* {@code value} easier
* @return the {@code value} passed in, unless it is minus, in which case 0
* is returned.
* is returned.
*/
private static int notNegative(int value, final String name) {
if (value < 0) {

View file

@ -2,9 +2,12 @@
package mage.abilities;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Predicate;
import mage.abilities.keyword.ProtectionAbility;
import mage.abilities.mana.ActivatedManaAbilityImpl;
import mage.constants.Zone;
@ -255,7 +258,8 @@ public interface Abilities<T extends Ability> extends List<T>, Serializable {
boolean containsAll(Abilities<T> abilities);
/**
* Searches this set of abilities for the existence of the give class
* Searches this set of abilities for the existence of the given class
* Warning, it doesn't work with inherited classes (e.g. it's not equal to instanceOf command)
*
* @param classObject
* @return True if the passed in class is also in this set of abilities.
@ -271,4 +275,13 @@ public interface Abilities<T extends Ability> extends List<T>, Serializable {
Abilities<T> copy();
String getValue();
@Deprecated // use permanent.removeAbility instead
boolean remove(Object o);
@Deprecated // use permanent.removeAbility instead
boolean removeAll(Collection<?> c);
@Deprecated // use permanent.removeAbility instead
boolean removeIf(Predicate<? super T> filter);
}

View file

@ -73,10 +73,14 @@ public class AbilitiesImpl<T extends Ability> extends ArrayList<T> implements Ab
StringBuilder sbRule = threadLocalBuilder.get();
for (Cost cost : ability.getCosts()) {
if (cost.getText() != null && !cost.getText().isEmpty()) {
String costText = cost.getText();
if (!cost.getText().startsWith("As an additional cost")) {
sbRule.append("As an additional cost to cast this spell, ");
if (!costText.isEmpty()) {
costText = Character.toLowerCase(costText.charAt(0)) + costText.substring(1);
}
}
sbRule.append(cost.getText()).append(".<br>");
sbRule.append(costText).append(".<br>");
}
}
rules.add(sbRule.toString());
@ -228,7 +232,8 @@ public class AbilitiesImpl<T extends Ability> extends ArrayList<T> implements Ab
if (ability.getId().equals(test.getId())) {
return true;
}
if (ability.getOriginalId().equals(test.getId())) {
if (ability.getOriginalId().equals(test.getOriginalId())) {
// on ability resolve: engine creates ability's copy and generates newId(), so you must use originalId to find that ability in card later
return true;
}
if (ability instanceof MageSingleton && test instanceof MageSingleton && ability.getRule().equals(test.getRule())) {
@ -239,7 +244,7 @@ public class AbilitiesImpl<T extends Ability> extends ArrayList<T> implements Ab
}
@Override
public boolean containsRule(T ability) {
public boolean containsRule(T ability) { // TODO: remove
return stream().anyMatch(rule -> rule.getRule().equals(ability.getRule()));
}
@ -258,7 +263,7 @@ public class AbilitiesImpl<T extends Ability> extends ArrayList<T> implements Ab
}
@Override
public boolean containsKey(UUID abilityId) {
public boolean containsKey(UUID abilityId) { // TODO: remove
return stream().anyMatch(ability -> abilityId.equals(ability.getId()));
}

View file

@ -23,6 +23,7 @@ import mage.watchers.Watcher;
import java.io.Serializable;
import java.util.List;
import java.util.UUID;
import mage.abilities.costs.common.TapSourceCost;
/**
* Practically everything in the game is started from an Ability. This interface
@ -190,13 +191,19 @@ public interface Ability extends Controllable, Serializable {
/**
* Retrieves all targets that must be satisfied before this ability is put
* onto the stack.
* onto the stack. Warning, return targets from first/current mode only.
*
* @return All {@link Targets} that must be satisfied before this ability is
* put onto the stack.
*/
Targets getTargets();
/**
* Retrieves all selected targets, read only. Multi-modes return different targets.
* Works on stack only (after real cast/activate)
*/
Targets getAllSelectedTargets();
/**
* Retrieves the {@link Target} located at the 0th index in the
* {@link Targets}. A call to the method is equivalent to
@ -360,6 +367,19 @@ public interface Ability extends Controllable, Serializable {
* @return
*/
boolean hasSourceObjectAbility(Game game, MageObject source, GameEvent event);
/**
* Returns true if the ability has a tap itself in their costs
* @return
*/
default boolean hasTapCost() {
for (Cost cost : this.getCosts()) {
if (cost instanceof TapSourceCost) {
return true;
}
}
return false;
}
/**
* Returns true if this ability has to be shown as topmost of all the rules
@ -447,7 +467,7 @@ public interface Ability extends Controllable, Serializable {
*
* @param abilityWord
*/
void setAbilityWord(AbilityWord abilityWord);
Ability setAbilityWord(AbilityWord abilityWord);
/**
* Creates the message about the ability casting/triggering/activating to
@ -522,4 +542,12 @@ public interface Ability extends Controllable, Serializable {
Ability addCustomOutcome(Outcome customOutcome);
Outcome getCustomOutcome();
/**
* For mtg's instances search, see rules example in 112.10b
*
* @param ability
* @return
*/
boolean isSameInstance(Ability ability);
}

View file

@ -8,6 +8,7 @@ import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect;
import mage.abilities.effects.Effects;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.ManaEffect;
import mage.abilities.hint.Hint;
import mage.abilities.mana.ActivatedManaAbilityImpl;
import mage.cards.Card;
@ -24,6 +25,7 @@ import mage.players.Player;
import mage.target.Target;
import mage.target.Targets;
import mage.target.targetadjustment.TargetAdjuster;
import mage.util.CardUtil;
import mage.util.GameLog;
import mage.util.ThreadLocalStringBuilder;
import mage.watchers.Watcher;
@ -33,6 +35,7 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import mage.abilities.costs.common.TapSourceCost;
/**
* @author BetaSteward_at_googlemail.com
@ -52,7 +55,7 @@ public abstract class AbilityImpl implements Ability {
protected ManaCosts<ManaCost> manaCostsToPay;
protected Costs<Cost> costs;
protected Costs<Cost> optionalCosts;
protected Modes modes; // access to it by GetModes only (it's can be override by some abilities)
protected Modes modes; // access to it by GetModes only (it can be overridden by some abilities)
protected Zone zone;
protected String name;
protected AbilityWord abilityWord;
@ -63,7 +66,7 @@ public abstract class AbilityImpl implements Ability {
protected boolean activated = false;
protected boolean worksFaceDown = false;
protected int sourceObjectZoneChangeCounter;
protected List<Watcher> watchers = new ArrayList<>(); // access to it by GetWatchers only (it's can be override by some abilities)
protected List<Watcher> watchers = new ArrayList<>(); // access to it by GetWatchers only (it can be overridden by some abilities)
protected List<Ability> subAbilities = null;
protected boolean canFizzle = true;
protected TargetAdjuster targetAdjuster = null;
@ -152,6 +155,9 @@ public abstract class AbilityImpl implements Ability {
boolean result = true;
//20100716 - 117.12
if (checkIfClause(game)) {
// Ability has started resolving. Fire event.
// Used for abilities counting the number of resolutions like Ashling the Pilgrim.
game.fireEvent(new GameEvent(GameEvent.EventType.RESOLVING_ABILITY, this.getOriginalId(), this.getSourceId(), this.getControllerId()));
if (this instanceof TriggeredAbility) {
for (UUID modeId : this.getModes().getSelectedModes()) {
this.getModes().setActiveMode(modeId);
@ -167,6 +173,9 @@ public abstract class AbilityImpl implements Ability {
private boolean resolveMode(Game game) {
boolean result = true;
for (Effect effect : getEffects()) {
if (game.inCheckPlayableState() && !(effect instanceof ManaEffect)) {
continue; // Ignored non mana effects - see GameEvent.TAPPED_FOR_MANA
}
if (effect instanceof OneShotEffect) {
boolean effectResult = effect.apply(game, this);
result &= effectResult;
@ -325,8 +334,9 @@ public abstract class AbilityImpl implements Ability {
}
if (!getTargets().isEmpty()) {
Outcome outcome = getEffects().getOutcome(this);
// only activated abilities can be canceled by user (not triggered)
if (!getTargets().chooseTargets(outcome, this.controllerId, this, noMana, game, this instanceof ActivatedAbility)) {
// only activated abilities can be canceled by human user (not triggered)
boolean canCancel = this instanceof ActivatedAbility && controller.isHuman();
if (!getTargets().chooseTargets(outcome, this.controllerId, this, noMana, game, canCancel)) {
// was canceled during targer selection
return false;
}
@ -349,8 +359,15 @@ public abstract class AbilityImpl implements Ability {
return false;
}
// fused spell contains 3 abilities (fused, left, right)
// fused cost added to fused ability, so no need cost modification for other parts
boolean needCostModification = true;
if (CardUtil.isFusedPartAbility(this, game)) {
needCostModification = false;
}
//20101001 - 601.2e
if (sourceObject != null) {
if (needCostModification && sourceObject != null) {
sourceObject.adjustCosts(this, game); // still needed
game.getContinuousEffects().costModification(this, game);
}
@ -414,36 +431,28 @@ public abstract class AbilityImpl implements Ability {
}
}
boolean alternativeCostisUsed = false;
boolean alternativeCostUsed = false;
if (sourceObject != null && !(sourceObject instanceof Permanent)) {
Abilities<Ability> abilities = null;
if (sourceObject instanceof Card) {
abilities = ((Card) sourceObject).getAbilities(game);
} else {
sourceObject.getAbilities();
}
if (abilities != null) {
for (Ability ability : abilities) {
// if cast for noMana no Alternative costs are allowed
if (canUseAlternativeCost && !noMana && ability instanceof AlternativeSourceCosts) {
AlternativeSourceCosts alternativeSpellCosts = (AlternativeSourceCosts) ability;
if (alternativeSpellCosts.isAvailable(this, game)) {
if (alternativeSpellCosts.askToActivateAlternativeCosts(this, game)) {
// only one alternative costs may be activated
alternativeCostisUsed = true;
break;
}
Abilities<Ability> abilities = CardUtil.getAbilities(sourceObject, game);
for (Ability ability : abilities) {
// if cast for noMana no Alternative costs are allowed
if (canUseAlternativeCost && !noMana && ability instanceof AlternativeSourceCosts) {
AlternativeSourceCosts alternativeSpellCosts = (AlternativeSourceCosts) ability;
if (alternativeSpellCosts.isAvailable(this, game)) {
if (alternativeSpellCosts.askToActivateAlternativeCosts(this, game)) {
// only one alternative costs may be activated
alternativeCostUsed = true;
break;
}
}
if (canUseAdditionalCost && ability instanceof OptionalAdditionalSourceCosts) {
((OptionalAdditionalSourceCosts) ability).addOptionalAdditionalCosts(this, game);
}
}
if (canUseAdditionalCost && ability instanceof OptionalAdditionalSourceCosts) {
((OptionalAdditionalSourceCosts) ability).addOptionalAdditionalCosts(this, game);
}
}
// controller specific alternate spell costs
if (canUseAlternativeCost && !noMana && !alternativeCostisUsed) {
if (canUseAlternativeCost && !noMana && !alternativeCostUsed) {
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
@ -452,7 +461,7 @@ public abstract class AbilityImpl implements Ability {
if (alternativeSourceCosts.isAvailable(this, game)) {
if (alternativeSourceCosts.askToActivateAlternativeCosts(this, game)) {
// only one alternative costs may be activated
alternativeCostisUsed = true;
alternativeCostUsed = true;
break;
}
}
@ -461,7 +470,7 @@ public abstract class AbilityImpl implements Ability {
}
}
return alternativeCostisUsed;
return alternativeCostUsed;
}
/**
@ -706,7 +715,6 @@ public abstract class AbilityImpl implements Ability {
@Override
public void addWatcher(Watcher watcher) {
watcher.setSourceId(this.sourceId);
watcher.setControllerId(this.controllerId);
getWatchers().add(watcher);
@ -766,7 +774,7 @@ public abstract class AbilityImpl implements Ability {
if (ruleStart.length() > 1) {
String end = ruleStart.substring(ruleStart.length() - 2).trim();
if (end.isEmpty() || end.equals(":") || end.equals(".")) {
rule = ruleStart + Character.toUpperCase(text.charAt(0)) + text.substring(1);
rule = ruleStart + CardUtil.getTextWithFirstCharUpperCase(text);
} else {
rule = ruleStart + text;
}
@ -844,6 +852,18 @@ public abstract class AbilityImpl implements Ability {
return new Targets();
}
@Override
public Targets getAllSelectedTargets() {
Targets res = new Targets();
for (UUID modeId : this.getModes().getSelectedModes()) {
Mode mode = this.getModes().get(modeId);
if (mode != null) {
res.addAll(mode.getTargets());
}
}
return res;
}
@Override
public UUID getFirstTarget() {
return getTargets().getFirstTarget();
@ -935,6 +955,10 @@ public abstract class AbilityImpl implements Ability {
@Override
public boolean hasSourceObjectAbility(Game game, MageObject source, GameEvent event) {
// if source object have this ability
// uses for ability.isInUseableZone
// replacement and other continues effects can be without source, but active (must return true)
MageObject object = source;
// for singleton abilities like Flying we can't rely on abilities' source because it's only once in continuous effects
// so will use the sourceId of the object itself that came as a parameter if it is not null
@ -946,16 +970,10 @@ public abstract class AbilityImpl implements Ability {
}
if (object != null) {
if (object instanceof Permanent) {
if (!((Permanent) object).getAbilities(game).contains(this)) {
return false;
}
return ((Permanent) object).isPhasedIn();
} else if (object instanceof Card) {
return ((Card) object).getAbilities(game).contains(this);
} else if (!object.getAbilities().contains(this)) { // not sure which object it can still be
// check if it's an ability that is temporary gained to a card
Abilities<Ability> otherAbilities = game.getState().getAllOtherAbilities(this.getSourceId());
return otherAbilities != null && otherAbilities.contains(this);
return object.hasAbility(this, game) && ((Permanent) object).isPhasedIn();
} else {
// cards and other objects
return object.hasAbility(this, game);
}
}
return true;
@ -1005,8 +1023,9 @@ public abstract class AbilityImpl implements Ability {
}
@Override
public void setAbilityWord(AbilityWord abilityWord) {
public Ability setAbilityWord(AbilityWord abilityWord) {
this.abilityWord = abilityWord;
return this;
}
@Override
@ -1115,6 +1134,7 @@ public abstract class AbilityImpl implements Ability {
String usedVerb = null;
for (Target target : targets) {
if (!target.getTargets().isEmpty()) {
String targetHintInfo = target.getChooseHint() == null ? "" : " (" + target.getChooseHint() + ")";
if (!target.isNotTarget()) {
if (usedVerb == null || usedVerb.equals(" choosing ")) {
usedVerb = " targeting ";
@ -1125,6 +1145,7 @@ public abstract class AbilityImpl implements Ability {
sb.append(usedVerb);
}
sb.append(target.getTargetedName(game));
sb.append(targetHintInfo);
}
}
}
@ -1223,6 +1244,17 @@ public abstract class AbilityImpl implements Ability {
}
}
/**
* Dynamic cost modification for ability.<br>
* Example: if it need stack related info (like real targets) then must
* check two states (game.inCheckPlayableState): <br>
* 1. In playable state it must check all possible use cases (e.g. allow to
* reduce on any available target and modes) <br>
* 2. In real cast state it must check current use case (e.g. real selected
* targets and modes)
*
* @param costAdjuster
*/
@Override
public void setCostAdjuster(CostAdjuster costAdjuster) {
this.costAdjuster = costAdjuster;
@ -1261,4 +1293,17 @@ public abstract class AbilityImpl implements Ability {
public Outcome getCustomOutcome() {
return this.customOutcome;
}
@Override
public boolean isSameInstance(Ability ability) {
// same instance (by mtg rules) = same object, ID or class+text (you can't check class only cause it can be different by params/text)
if (ability == null) {
return false;
}
return (this == ability)
|| (this.getId().equals(ability.getId()))
|| (this.getOriginalId().equals(ability.getOriginalId()))
|| (this.getClass() == ability.getClass() && this.getRule(true).equals(ability.getRule(true)));
}
}

View file

@ -1,18 +1,19 @@
package mage.abilities;
import java.util.UUID;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.mana.ManaOptions;
import mage.constants.TargetController;
import mage.game.Game;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public interface ActivatedAbility extends Ability {
final public class ActivationStatus {
final class ActivationStatus {
private final boolean canActivate;
private final MageObjectReference permittingObject;
@ -34,8 +35,13 @@ public interface ActivatedAbility extends Ability {
return new ActivationStatus(false, null);
}
public static ActivationStatus getTrue() {
return new ActivationStatus(true, null);
/**
* @param permittingObjectAbility card or permanent that allows to activate current ability
*/
public static ActivationStatus getTrue(Ability permittingObjectAbility, Game game) {
MageObject object = permittingObjectAbility == null ? null : permittingObjectAbility.getSourceObject(game);
MageObjectReference ref = object == null ? null : new MageObjectReference(object, game);
return new ActivationStatus(true, ref);
}
}

View file

@ -1,6 +1,5 @@
package mage.abilities;
import java.util.UUID;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.condition.Condition;
@ -12,11 +11,7 @@ import mage.abilities.effects.Effect;
import mage.abilities.effects.Effects;
import mage.abilities.mana.ManaOptions;
import mage.cards.Card;
import mage.constants.AbilityType;
import mage.constants.AsThoughEffectType;
import mage.constants.TargetController;
import mage.constants.TimingRule;
import mage.constants.Zone;
import mage.constants.*;
import mage.game.Game;
import mage.game.command.Commander;
import mage.game.command.Emblem;
@ -24,8 +19,9 @@ import mage.game.command.Plane;
import mage.game.permanent.Permanent;
import mage.util.CardUtil;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public abstract class ActivatedAbilityImpl extends AbilityImpl implements ActivatedAbility {
@ -148,6 +144,34 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
@Override
public abstract ActivatedAbilityImpl copy();
protected boolean checkTargetController(UUID playerId, Game game) {
switch (mayActivate) {
case ANY:
return true;
case ACTIVE:
return game.getActivePlayerId() == playerId;
case NOT_YOU:
return !controlsAbility(playerId, game);
case TEAM:
return !game.getPlayer(controllerId).hasOpponent(playerId, game);
case OPPONENT:
return game.getPlayer(controllerId).hasOpponent(playerId, game);
case OWNER:
Permanent permanent = game.getPermanent(getSourceId());
return permanent != null && permanent.isOwnedBy(playerId);
case YOU:
return controlsAbility(playerId, game);
case CONTROLLER_ATTACHED_TO:
Permanent enchantment = game.getPermanent(getSourceId());
if (enchantment == null || enchantment.getAttachedTo() == null) {
return false;
}
Permanent enchanted = game.getPermanent(enchantment.getAttachedTo());
return enchanted != null && enchanted.isControlledBy(playerId);
}
return true;
}
@Override
public ActivationStatus canActivate(UUID playerId, Game game) {
//20091005 - 602.2
@ -156,49 +180,8 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
|| condition.apply(game, this)))) {
return ActivationStatus.getFalse();
}
switch (mayActivate) {
case ANY:
break;
case ACTIVE:
if (game.getActivePlayerId() != playerId) {
return ActivationStatus.getFalse();
}
break;
case NOT_YOU:
if (controlsAbility(playerId, game)) {
return ActivationStatus.getFalse();
}
break;
case TEAM:
if (game.getPlayer(controllerId).hasOpponent(playerId, game)) {
return ActivationStatus.getFalse();
}
break;
case OPPONENT:
if (!game.getPlayer(controllerId).hasOpponent(playerId, game)) {
return ActivationStatus.getFalse();
}
break;
case OWNER:
Permanent permanent = game.getPermanent(getSourceId());
if (!permanent.isOwnedBy(playerId)) {
return ActivationStatus.getFalse();
}
break;
case YOU:
if (!controlsAbility(playerId, game)) {
return ActivationStatus.getFalse();
}
break;
case CONTROLLER_ATTACHED_TO:
Permanent enchantment = game.getPermanent(getSourceId());
if (enchantment != null && enchantment.getAttachedTo() != null) {
Permanent enchanted = game.getPermanent(enchantment.getAttachedTo());
if (enchanted != null && enchanted.isControlledBy(playerId)) {
break;
}
}
return ActivationStatus.getFalse();
if (!this.checkTargetController(playerId, game)) {
return ActivationStatus.getFalse();
}
//20091005 - 602.5d/602.5e
MageObjectReference permittingObject = game.getContinuousEffects()
@ -227,17 +210,16 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
protected boolean controlsAbility(UUID playerId, Game game) {
if (this.controllerId != null && this.controllerId.equals(playerId)) {
return true;
} else {
MageObject mageObject = game.getObject(this.sourceId);
if (mageObject instanceof Emblem) {
return ((Emblem) mageObject).isControlledBy(playerId);
} else if (mageObject instanceof Plane) {
return ((Plane) mageObject).isControlledBy(playerId);
} else if (mageObject instanceof Commander) {
return ((Commander) mageObject).isControlledBy(playerId);
} else if (game.getState().getZone(this.sourceId) != Zone.BATTLEFIELD) {
return ((Card) mageObject).isOwnedBy(playerId);
}
}
MageObject mageObject = game.getObject(this.sourceId);
if (mageObject instanceof Emblem) {
return ((Emblem) mageObject).isControlledBy(playerId);
} else if (mageObject instanceof Plane) {
return ((Plane) mageObject).isControlledBy(playerId);
} else if (mageObject instanceof Commander) {
return ((Commander) mageObject).isControlledBy(playerId);
} else if (game.getState().getZone(this.sourceId) != Zone.BATTLEFIELD) {
return ((Card) mageObject).isOwnedBy(playerId);
}
return false;
}
@ -271,22 +253,20 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
@Override
public boolean activate(Game game, boolean noMana) {
if (hasMoreActivationsThisTurn(game)) {
if (super.activate(game, noMana)) {
ActivationInfo activationInfo = getActivationInfo(game);
if (activationInfo == null) {
activationInfo = new ActivationInfo(game.getTurnNum(), 1);
} else if (activationInfo.turnNum != game.getTurnNum()) {
activationInfo.turnNum = game.getTurnNum();
activationInfo.activationCounter = 1;
} else {
activationInfo.activationCounter++;
}
setActivationInfo(activationInfo, game);
return true;
}
if (!hasMoreActivationsThisTurn(game) || !super.activate(game, noMana)) {
return false;
}
return false;
ActivationInfo activationInfo = getActivationInfo(game);
if (activationInfo == null) {
activationInfo = new ActivationInfo(game.getTurnNum(), 1);
} else if (activationInfo.turnNum != game.getTurnNum()) {
activationInfo.turnNum = game.getTurnNum();
activationInfo.activationCounter = 1;
} else {
activationInfo.activationCounter++;
}
setActivationInfo(activationInfo, game);
return true;
}
@Override

View file

@ -1,62 +1,62 @@
package mage.abilities;
import java.util.UUID;
import mage.game.Game;
/**
* The ActivationInfo class holds the information how often an ability of an
* object was activated during a turn. It handles the check, if the object is
* still the same, so for example if a permanent left battlefield and returns,
* the counting of activations happens for each object.
*
* @author LevelX2
*/
public class ActivationInfo {
protected int turnNum = 0;
protected int activationCounter = 0;
protected String key;
public static ActivationInfo getInstance(Game game, UUID sourceId) {
return ActivationInfo.getInstance(game, sourceId, game.getState().getZoneChangeCounter(sourceId));
}
public static ActivationInfo getInstance(Game game, UUID sourceId, int zoneChangeCounter) {
String key = "ActivationInfo" + sourceId.toString() + zoneChangeCounter;
Integer activations = (Integer) game.getState().getValue(key);
ActivationInfo activationInfo;
if (activations != null) {
Integer turnNum = (Integer) game.getState().getValue(key + 'T');
activationInfo = new ActivationInfo(game, turnNum, activations);
} else {
activationInfo = new ActivationInfo(game, game.getTurnNum(), 0);
}
activationInfo.setKey(key);
return activationInfo;
}
public void setKey(String key) {
this.key = key;
}
protected ActivationInfo(Game game, int turnNum, int activationCounter) {
this.turnNum = turnNum;
this.activationCounter = activationCounter;
}
public void addActivation(Game game) {
if (game.getTurnNum() != turnNum) {
activationCounter = 1;
turnNum = game.getTurnNum();
} else {
activationCounter++;
}
game.getState().setValue(key, activationCounter);
game.getState().setValue(key + 'T', turnNum);
}
public int getActivationCounter() {
return activationCounter;
}
}
package mage.abilities;
import java.util.UUID;
import mage.game.Game;
/**
* The ActivationInfo class holds the information how often an ability of an
* object was activated during a turn. It handles the check, if the object is
* still the same, so for example if a permanent left battlefield and returns,
* the counting of activations happens for each object.
*
* @author LevelX2
*/
public class ActivationInfo {
protected int turnNum = 0;
protected int activationCounter = 0;
protected String key;
public static ActivationInfo getInstance(Game game, UUID sourceId) {
return ActivationInfo.getInstance(game, sourceId, game.getState().getZoneChangeCounter(sourceId));
}
public static ActivationInfo getInstance(Game game, UUID sourceId, int zoneChangeCounter) {
String key = "ActivationInfo" + sourceId.toString() + zoneChangeCounter;
Integer activations = (Integer) game.getState().getValue(key);
ActivationInfo activationInfo;
if (activations != null) {
Integer turnNum = (Integer) game.getState().getValue(key + 'T');
activationInfo = new ActivationInfo(game, turnNum, activations);
} else {
activationInfo = new ActivationInfo(game, game.getTurnNum(), 0);
}
activationInfo.setKey(key);
return activationInfo;
}
public void setKey(String key) {
this.key = key;
}
protected ActivationInfo(Game game, int turnNum, int activationCounter) {
this.turnNum = turnNum;
this.activationCounter = activationCounter;
}
public void addActivation(Game game) {
if (game.getTurnNum() != turnNum) {
activationCounter = 1;
turnNum = game.getTurnNum();
} else {
activationCounter++;
}
game.getState().setValue(key, activationCounter);
game.getState().setValue(key + 'T', turnNum);
}
public int getActivationCounter() {
return activationCounter;
}
}

View file

@ -23,9 +23,9 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
public static final UUID CHOOSE_OPTION_CANCEL_ID = UUID.fromString("0125bd0c-5610-4eba-bc80-fc6d0a7b9de6");
private Mode currentMode; // the current mode of the selected modes
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 final List<UUID> selectedModes = new ArrayList<>(); // all selected modes (this + duplicate), use getSelectedModes all the time to keep modes order
private final Map<UUID, Mode> selectedDuplicateModes = new LinkedHashMap<>(); // for 2x selects: copy mode and put it to duplicate list
private final Map<UUID, UUID> selectedDuplicateToOriginalModeRefs = new LinkedHashMap<>(); // for 2x selects: stores ref from duplicate to original mode
private int minModes;
private int maxModes;
@ -52,10 +52,10 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
for (Map.Entry<UUID, Mode> entry : modes.entrySet()) {
this.put(entry.getKey(), entry.getValue().copy());
}
for (Map.Entry<UUID, Mode> entry : modes.duplicateModes.entrySet()) {
duplicateModes.put(entry.getKey(), entry.getValue().copy());
for (Map.Entry<UUID, Mode> entry : modes.selectedDuplicateModes.entrySet()) {
selectedDuplicateModes.put(entry.getKey(), entry.getValue().copy());
}
duplicateToOriginalModeRefs.putAll(modes.duplicateToOriginalModeRefs);
selectedDuplicateToOriginalModeRefs.putAll(modes.selectedDuplicateToOriginalModeRefs);
this.minModes = modes.minModes;
this.maxModes = modes.maxModes;
@ -72,7 +72,7 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
if (modes.getSelectedModes().isEmpty()) {
this.currentMode = values().iterator().next();
} else {
this.currentMode = get(modes.getMode().getId());
this.currentMode = get(modes.getMode().getId()); // need fix?
}
}
@ -84,7 +84,7 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
public Mode get(Object key) {
Mode modeToGet = super.get(key);
if (modeToGet == null && eachModeMoreThanOnce) {
modeToGet = duplicateModes.get(key);
modeToGet = selectedDuplicateModes.get(key);
}
return modeToGet;
}
@ -103,7 +103,7 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
public UUID getModeId(int index) {
int idx = 0;
if (eachModeMoreThanOnce) {
for (UUID modeId : this.selectedModes) {
for (UUID modeId : this.getSelectedModes()) {
idx++;
if (idx == index) {
return modeId;
@ -121,7 +121,25 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
}
public List<UUID> getSelectedModes() {
return selectedModes;
// sorted as original modes
List<UUID> res = new ArrayList<>();
for (Mode mode : this.values()) {
for (UUID selectedId : this.selectedModes) {
// selectedModes contains original mode and 2+ selected as duplicates (new modes)
UUID selectedOriginalId = this.selectedDuplicateToOriginalModeRefs.get(selectedId);
if (Objects.equals(mode.getId(), selectedId)
|| Objects.equals(mode.getId(), selectedOriginalId)) {
res.add(selectedId);
}
}
}
return res;
}
public void clearSelectedModes() {
this.selectedModes.clear();
this.selectedDuplicateModes.clear();
this.selectedDuplicateToOriginalModeRefs.clear();
}
public int getSelectedStats(UUID modeId) {
@ -133,14 +151,14 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
// multiple select (all 2x select generate new duplicate mode)
UUID originalId;
if (this.duplicateModes.containsKey(modeId)) {
if (this.selectedDuplicateModes.containsKey(modeId)) {
// modeId is duplicate
originalId = this.duplicateToOriginalModeRefs.get(modeId);
originalId = this.selectedDuplicateToOriginalModeRefs.get(modeId);
} else {
// modeId is original
originalId = modeId;
}
for (UUID id : this.duplicateToOriginalModeRefs.values()) {
for (UUID id : this.selectedDuplicateToOriginalModeRefs.values()) {
if (id.equals(originalId)) {
count++;
}
@ -183,9 +201,7 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
}
public void setActiveMode(Mode mode) {
if (selectedModes.contains(mode.getId())) {
this.currentMode = mode;
}
setActiveMode(mode.getId());
}
public void setActiveMode(UUID modeId) {
@ -200,9 +216,7 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
public boolean choose(Game game, Ability source) {
if (this.size() > 1) {
this.selectedModes.clear();
this.duplicateModes.clear();
this.duplicateToOriginalModeRefs.clear();
this.clearSelectedModes();
if (this.isRandom) {
List<Mode> modes = getAvailableModes(source, game);
this.addSelectedMode(modes.get(RandomUtil.nextInt(modes.size())).getId());
@ -230,7 +244,7 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
}
}
if (isEachModeOnlyOnce()) {
setAlreadySelectedModes(selectedModes, source, game);
setAlreadySelectedModes(source, game);
}
return !selectedModes.isEmpty();
}
@ -274,7 +288,7 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
Mode choice = player.chooseMode(this, source, game);
if (choice == null) {
if (isEachModeOnlyOnce()) {
setAlreadySelectedModes(selectedModes, source, game);
setAlreadySelectedModes(source, game);
}
return this.selectedModes.size() >= this.getMinModes();
}
@ -284,12 +298,13 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
}
}
if (isEachModeOnlyOnce()) {
setAlreadySelectedModes(selectedModes, source, game);
setAlreadySelectedModes(source, game);
}
return true;
} else { // only one mode
} else {
// only one mode available
if (currentMode == null) {
this.selectedModes.clear();
this.clearSelectedModes();
Mode mode = this.values().iterator().next();
this.addSelectedMode(mode.getId());
this.setActiveMode(mode);
@ -301,12 +316,11 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
/**
* Saves the already selected modes to the state value
*
* @param selectedModes
* @param source
* @param game
*/
private void setAlreadySelectedModes(List<UUID> selectedModes, Ability source, Game game) {
for (UUID modeId : selectedModes) {
private void setAlreadySelectedModes(Ability source, Game game) {
for (UUID modeId : getSelectedModes()) {
String key = getKey(source, game, modeId);
game.getState().setValue(key, true);
}
@ -318,19 +332,29 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
*
* @param modeId
*/
private void addSelectedMode(UUID modeId) {
public void addSelectedMode(UUID modeId) {
if (!this.containsKey(modeId)) {
throw new IllegalArgumentException("Unknown modeId to select");
}
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);
selectedDuplicateModes.put(modeId, duplicateMode);
selectedDuplicateToOriginalModeRefs.put(duplicateMode.getId(), originalId);
}
this.selectedModes.add(modeId);
}
public void removeSelectedMode(UUID modeId) {
this.selectedModes.remove(modeId);
this.selectedDuplicateModes.remove(modeId);
this.selectedDuplicateToOriginalModeRefs.remove(modeId);
}
// The already once selected modes for a modal card are stored as a state value
// That's important for modal abilities with modes that can only selected once while the object stays in its zone
@SuppressWarnings("unchecked")

View file

@ -1,18 +1,22 @@
package mage.abilities;
import mage.abilities.costs.mana.AlternateManaPaymentAbility;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.mana.ManaOptions;
import mage.constants.AbilityType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
* @author BetaSteward_at_googlemail.com, JayDi85
*/
public abstract class SpecialAction extends ActivatedAbilityImpl {
private boolean manaAction;
private final AlternateManaPaymentAbility manaAbility; // mana actions generates on every pay cycle, no need to copy it
protected ManaCost unpaidMana;
public SpecialAction() {
@ -20,22 +24,23 @@ public abstract class SpecialAction extends ActivatedAbilityImpl {
}
public SpecialAction(Zone zone) {
this(zone, false);
this(zone, null);
}
public SpecialAction(Zone zone, boolean manaAction) {
public SpecialAction(Zone zone, AlternateManaPaymentAbility manaAbility) {
super(AbilityType.SPECIAL_ACTION, zone);
this.usesStack = false;
this.manaAction = manaAction;
this.manaAbility = manaAbility;
}
public SpecialAction(final SpecialAction action) {
super(action);
this.manaAction = action.manaAction;
this.unpaidMana = action.unpaidMana;
this.manaAbility = action.manaAbility;
}
public boolean isManaAction() {
return manaAction;
return manaAbility != null;
}
public void setUnpaidMana(ManaCost manaCost) {
@ -45,4 +50,29 @@ public abstract class SpecialAction extends ActivatedAbilityImpl {
public ManaCost getUnpaidMana() {
return unpaidMana;
}
public ManaOptions getManaOptions(Ability source, Game game, ManaCost unpaid) {
if (manaAbility != null) {
return manaAbility.getManaOptions(source, game, unpaid);
}
return null;
}
@Override
public ActivationStatus canActivate(UUID playerId, Game game) {
if (isManaAction()) {
// limit play mana abilities by steps
int currentStepOrder = 0;
if (!game.getStack().isEmpty()) {
StackObject stackObject = game.getStack().getFirst();
if (stackObject instanceof Spell) {
currentStepOrder = ((Spell) stackObject).getCurrentActivatingManaAbilitiesStep().getStepOrder();
}
}
if (currentStepOrder > manaAbility.useOnActivationManaAbilityStep().getStepOrder()) {
return ActivationStatus.getFalse();
}
}
return super.canActivate(playerId, game);
}
}

View file

@ -1,7 +1,5 @@
package mage.abilities;
import java.util.Optional;
import java.util.UUID;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.costs.Cost;
@ -14,9 +12,12 @@ import mage.cards.SplitCard;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.Spell;
import mage.players.Player;
import java.util.Optional;
import java.util.UUID;
import javax.naming.directory.InvalidAttributesException;
/**
* @author BetaSteward_at_googlemail.com
*/
@ -70,7 +71,7 @@ public class SpellAbility extends ActivatedAbilityImpl {
}
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)
|| object.hasAbility(FlashAbility.getInstance(), game)
|| game.canPlaySorcery(playerId);
}
@ -104,12 +105,16 @@ public class SpellAbility extends ActivatedAbilityImpl {
return ActivationStatus.getFalse();
}
}
if (costs.canPay(this, sourceId, controllerId, game)) {
if (costs.canPay(this, sourceId, playerId, game)) {
if (getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED) {
SplitCard splitCard = (SplitCard) game.getCard(getSourceId());
if (splitCard != null) {
return new ActivationStatus(splitCard.getLeftHalfCard().getSpellAbility().canChooseTarget(game)
&& splitCard.getRightHalfCard().getSpellAbility().canChooseTarget(game), null);
// fused can be called from hand only, so not permitting object allows or other zones checks
// see https://www.mtgsalvation.com/forums/magic-fundamentals/magic-rulings/magic-rulings-archives/251926-snapcaster-mage-and-fuse
if (game.getState().getZone(splitCard.getId()) == Zone.HAND) {
return new ActivationStatus(splitCard.getLeftHalfCard().getSpellAbility().canChooseTarget(game)
&& splitCard.getRightHalfCard().getSpellAbility().canChooseTarget(game), null);
}
}
return ActivationStatus.getFalse();
@ -140,9 +145,13 @@ public class SpellAbility extends ActivatedAbilityImpl {
this.costs.clearPaid();
}
public String getName() {
return this.name;
}
@Override
public String toString() {
return this.name;
return getName();
}
@Override
@ -227,12 +236,25 @@ public class SpellAbility extends ActivatedAbilityImpl {
return this;
}
/**
* Returns a card object with the spell characteristics like calor, types,
* subtypes etc. E.g. if you cast a Bestow card as enchantment, the
* characteristics don't include the creature type.
*
* @param game
* @return card object with the spell characteristics
*/
public Card getCharacteristics(Game game) {
Spell spell = game.getSpell(this.getId());
if (spell != null) {
return spell;
Card spellCharacteristics = game.getSpell(this.getId());
if (spellCharacteristics == null) {
spellCharacteristics = game.getCard(this.getSourceId());
}
return game.getCard(this.getSourceId());
if (spellCharacteristics != null) {
if (getSpellAbilityCastMode() != SpellAbilityCastMode.NORMAL) {
spellCharacteristics = getSpellAbilityCastMode().getTypeModifiedCardObjectCopy(spellCharacteristics, game);
}
}
return spellCharacteristics;
}
public static SpellAbility getSpellAbilityFromEvent(GameEvent event, Game game) {

View file

@ -6,7 +6,6 @@ import mage.constants.AbilityType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.events.ZoneChangeEvent;
import mage.players.Player;
import mage.util.CardUtil;
@ -171,21 +170,24 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge
}
}
}
if (isLeavesTheBattlefieldTrigger()) {
source = zce.getTarget();
}
break;
case DESTROYED_PERMANENT:
if (isLeavesTheBattlefieldTrigger()) {
if (event.getType() == EventType.DESTROYED_PERMANENT) {
source = game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD);
} else if (((ZoneChangeEvent) event).getTarget() != null) {
source = ((ZoneChangeEvent) event).getTarget();
} else {
source = game.getLastKnownInformation(getSourceId(), event.getZone());
}
source = game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD);
}
break;
case PHASED_OUT:
case PHASED_IN:
if (isLeavesTheBattlefieldTrigger()) {
source = game.getLastKnownInformation(getSourceId(), event.getZone());
}
if (this.zone == Zone.ALL || game.getLastKnownInformation(getSourceId(), zone) != null) {
return this.hasSourceObjectAbility(game, source, event);
}
break;
}
return super.isInUseableZone(game, source, event);
}

View file

@ -53,7 +53,7 @@ public class ConstellationAbility extends TriggeredAbilityImpl {
return false;
}
Permanent permanent = game.getPermanent(event.getTargetId());
return permanent != null && permanent.isEnchantment();
return permanent != null && ((thisOr && permanent.getId().equals(getSourceId())) || permanent.isEnchantment());
}
@Override

View file

@ -1,5 +1,3 @@
package mage.abilities.abilityword;
import mage.abilities.Ability;
@ -16,14 +14,13 @@ import mage.target.Target;
import mage.util.ManaUtil;
/**
*
* @author LevelX2
*/
public class StriveAbility extends SimpleStaticAbility {
private final String striveCost;
public StriveAbility(String manaString) {
super(Zone.STACK, new StriveCostIncreasingEffect(new ManaCostsImpl(manaString)));
setRuleAtTheTop(true);
@ -63,7 +60,7 @@ class StriveCostIncreasingEffect extends CostModificationEffectImpl {
@Override
public boolean apply(Game game, Ability source, Ability abilityToModify) {
for (Target target : abilityToModify.getTargets()) {
if (target.getMaxNumberOfTargets() == Integer.MAX_VALUE) {
if (target.getMaxNumberOfTargets() == Integer.MAX_VALUE) { // strive works with "any number of target" only
int additionalTargets = target.getTargets().size() - 1;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < additionalTargets; i++) {

View file

@ -0,0 +1,61 @@
package mage.abilities.common;
import mage.abilities.LoyaltyAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.stack.StackAbility;
public class ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility extends TriggeredAbilityImpl {
private final SubType planeswalkerSubType;
public ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility(Effect effect, SubType planeswalkerSubType) {
super(Zone.BATTLEFIELD, effect, false);
this.planeswalkerSubType = planeswalkerSubType;
}
private ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility(final ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility ability) {
super(ability);
this.planeswalkerSubType = ability.planeswalkerSubType;
}
@Override
public ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility copy() {
return new ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ACTIVATED_ABILITY;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (!event.getPlayerId().equals(getControllerId())) {
return false;
}
StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(event.getSourceId());
if (stackAbility == null || !(stackAbility.getStackAbility() instanceof LoyaltyAbility)) {
return false;
}
Permanent permanent = stackAbility.getSourcePermanentOrLKI(game);
if (permanent == null || !permanent.isPlaneswalker()
|| !permanent.hasSubtype(planeswalkerSubType, game)) {
return false;
}
Effect effect = this.getEffects().get(0);
effect.setValue("stackAbility", stackAbility);
return true;
}
@Override
public String getRule() {
return "Whenever you activate a loyalty ability of a " + planeswalkerSubType.getDescription() + " planeswalker, " +
this.getEffects().get(0).getText(getModes().getMode()) + ".";
}
}

View file

@ -0,0 +1,48 @@
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.game.permanent.Permanent;
import static mage.constants.CardType.CREATURE;
/**
*
* @author htrajan
*/
public class AttachedToCreatureSourceTriggeredAbility extends TriggeredAbilityImpl {
public AttachedToCreatureSourceTriggeredAbility(Effect effect, boolean optional) {
super(Zone.BATTLEFIELD, effect, optional);
}
public AttachedToCreatureSourceTriggeredAbility(final AttachedToCreatureSourceTriggeredAbility ability) {
super(ability);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ATTACHED
&& event.getSourceId() != null
&& event.getSourceId().equals(this.getSourceId());
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Permanent attachedPermanent = game.getPermanent(event.getTargetId());
return attachedPermanent != null && attachedPermanent.getCardType().contains(CREATURE);
}
@Override
public String getRule() {
return "As {this} becomes attached to a creature, " + super.getRule();
}
@Override
public AttachedToCreatureSourceTriggeredAbility copy() {
return new AttachedToCreatureSourceTriggeredAbility(this);
}
}

View file

@ -77,7 +77,7 @@ public class AttacksAllTriggeredAbility extends TriggeredAbilityImpl {
switch (setTargetPointer) {
case PERMANENT:
for (Effect effect : getEffects()) {
effect.setTargetPointer(new FixedTarget(permanent.getId()));
effect.setTargetPointer(new FixedTarget(permanent, game));
}
break;
case PLAYER:

View file

@ -76,12 +76,12 @@ public class AttacksCreatureYouControlTriggeredAbility extends TriggeredAbilityI
public String getRule() {
String an;
String who = filter.getMessage();
if (who.startsWith("another")) {
if (who.startsWith("another") || who.startsWith("a ")) {
an = "";
} else if (who.startsWith("a")) {
an = "an";
} else if (who.length() > 0 && "aeiou".contains(who.charAt(0) + "")) {
an = "an ";
} else {
an = "a";
an = "a ";
}
return "When" + (once ? "" : "ever")

View file

@ -45,7 +45,9 @@ public class BecomesBlockedByCreatureTriggeredAbility extends TriggeredAbilityIm
if (!filter.match(blocker, game)) {
return false;
}
this.getEffects().setTargetPointer(new FixedTarget(blocker, game));
for (Effect effect : this.getEffects()) {
effect.setTargetPointer(new FixedTarget(event.getSourceId()));
}
return true;
}

View file

@ -0,0 +1,43 @@
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;
/**
*
* @author North
*/
public class BecomesBlockedSourceTriggeredAbility extends TriggeredAbilityImpl {
public BecomesBlockedSourceTriggeredAbility(Effect effect, boolean optional) {
super(Zone.BATTLEFIELD, effect, optional);
}
public BecomesBlockedSourceTriggeredAbility(final BecomesBlockedSourceTriggeredAbility ability) {
super(ability);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.CREATURE_BLOCKED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return event.getTargetId().equals(this.getSourceId());
}
@Override
public String getRule() {
return "Whenever {this} becomes blocked, " + super.getRule();
}
@Override
public BecomesBlockedSourceTriggeredAbility copy() {
return new BecomesBlockedSourceTriggeredAbility(this);
}
}

View file

@ -1,55 +1,55 @@
/*
* 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.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.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
/**
*
* @author Styxo
*/
public class BecomesMonstrousTriggeredAbility extends TriggeredAbilityImpl {
public BecomesMonstrousTriggeredAbility(Effect effect) {
super(Zone.BATTLEFIELD, effect, false);
}
public BecomesMonstrousTriggeredAbility(final BecomesMonstrousTriggeredAbility ability) {
super(ability);
}
@Override
public BecomesMonstrousTriggeredAbility copy() {
return new BecomesMonstrousTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.BECOMES_MONSTROUS;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Permanent permanent = game.getPermanent(event.getTargetId());
if (permanent != null && permanent.isCreature()
&& (permanent.isControlledBy(getControllerId()))) {
this.getEffects().setTargetPointer(new FixedTarget(permanent, game));
return true;
}
return false;
}
@Override
public String getRule() {
return "Whenever a creature you control becomes monstrous, " + super.getRule();
}
}
/*
* 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.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.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
/**
*
* @author Styxo
*/
public class BecomesMonstrousTriggeredAbility extends TriggeredAbilityImpl {
public BecomesMonstrousTriggeredAbility(Effect effect) {
super(Zone.BATTLEFIELD, effect, false);
}
public BecomesMonstrousTriggeredAbility(final BecomesMonstrousTriggeredAbility ability) {
super(ability);
}
@Override
public BecomesMonstrousTriggeredAbility copy() {
return new BecomesMonstrousTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.BECOMES_MONSTROUS;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Permanent permanent = game.getPermanent(event.getTargetId());
if (permanent != null && permanent.isCreature()
&& (permanent.isControlledBy(getControllerId()))) {
this.getEffects().setTargetPointer(new FixedTarget(permanent, game));
return true;
}
return false;
}
@Override
public String getRule() {
return "Whenever a creature you control becomes monstrous, " + super.getRule();
}
}

View file

@ -28,7 +28,11 @@ public class BecomesTargetTriggeredAbility extends TriggeredAbilityImpl {
}
public BecomesTargetTriggeredAbility(Effect effect, FilterStackObject filter, SetTargetPointer setTargetPointer) {
super(Zone.BATTLEFIELD, effect);
this(effect, filter, setTargetPointer, false);
}
public BecomesTargetTriggeredAbility(Effect effect, FilterStackObject filter, SetTargetPointer setTargetPointer, boolean optional) {
super(Zone.BATTLEFIELD, effect, optional);
this.filter = filter.copy();
this.setTargetPointer = setTargetPointer;
}

View file

@ -103,6 +103,7 @@ public class BeginningOfEndStepTriggeredAbility extends TriggeredAbilityImpl {
@Override
public String getRule() {
StringBuilder sb = new StringBuilder(getEffects().getText(modes.getMode()));
if (this.optional) {
if (sb.substring(0, 6).toLowerCase(Locale.ENGLISH).equals("target")) {
sb.insert(0, "you may have ");
@ -114,6 +115,7 @@ public class BeginningOfEndStepTriggeredAbility extends TriggeredAbilityImpl {
if (abilityWord != null) {
abilityWordRule = "<i>" + abilityWord.toString() + "</i> &mdash ";
}
switch (targetController) {
case YOU:
return sb.insert(0, generateConditionString()).insert(0, abilityWordRule + "At the beginning of your end step, ").toString();
@ -134,6 +136,13 @@ public class BeginningOfEndStepTriggeredAbility extends TriggeredAbilityImpl {
private String generateConditionString() {
if (interveningIfClauseCondition != null) {
if (interveningIfClauseCondition.toString().startsWith("if")) {
//Fixes punctuation on multiple sentence if-then construction
// see -- Colfenor's Urn
if (interveningIfClauseCondition.toString().endsWith(".")){
return interveningIfClauseCondition.toString() + " ";
}
return interveningIfClauseCondition.toString() + ", ";
} else {
return "if {this} is " + interveningIfClauseCondition.toString() + ", ";

View file

@ -1,4 +1,3 @@
package mage.abilities.common;
import mage.abilities.TriggeredAbilityImpl;
@ -137,7 +136,7 @@ public class BeginningOfUpkeepTriggeredAbility extends TriggeredAbilityImpl {
case OPPONENT:
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();
return sb.insert(0, generateZoneString()).insert(0, "At the beginning of each player's upkeep, ").toString();
case ACTIVE:
return sb.insert(0, generateZoneString()).insert(0, "At the beginning of each player's upkeep, ").toString();
case CONTROLLER_ATTACHED_TO:

View file

@ -15,6 +15,10 @@ public class BeginningOfYourEndStepTriggeredAbility extends TriggeredAbilityImpl
super(Zone.BATTLEFIELD, effect, optional);
}
public BeginningOfYourEndStepTriggeredAbility(Zone zone, Effect effect, boolean optional) {
super(zone, effect, optional);
}
public BeginningOfYourEndStepTriggeredAbility(final BeginningOfYourEndStepTriggeredAbility ability) {
super(ability);
}

View file

@ -0,0 +1,91 @@
package mage.abilities.common;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
/**
*
* @author North, Loki
*/
public class BlocksOrBecomesBlockedSourceTriggeredAbility extends TriggeredAbilityImpl {
protected FilterPermanent filter;
protected String rule;
protected boolean setTargetPointer;
public BlocksOrBecomesBlockedSourceTriggeredAbility(Effect effect, boolean optional) {
this(effect, StaticFilters.FILTER_PERMANENT_CREATURE, optional, null, true);
}
public BlocksOrBecomesBlockedSourceTriggeredAbility(Effect effect, FilterPermanent filter, boolean optional) {
this(effect, filter, optional, null, true);
}
public BlocksOrBecomesBlockedSourceTriggeredAbility(Effect effect, FilterPermanent filter, boolean optional, String rule) {
this(effect, filter, optional, rule, true);
}
public BlocksOrBecomesBlockedSourceTriggeredAbility(Effect effect, FilterPermanent filter, boolean optional, String rule, boolean setTargetPointer) {
super(Zone.BATTLEFIELD, effect, optional);
this.filter = filter;
this.rule = rule;
this.setTargetPointer = setTargetPointer;
}
public BlocksOrBecomesBlockedSourceTriggeredAbility(final BlocksOrBecomesBlockedSourceTriggeredAbility ability) {
super(ability);
this.filter = ability.filter;
this.rule = ability.rule;
this.setTargetPointer = ability.setTargetPointer;
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.BLOCKER_DECLARED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (event.getSourceId().equals(this.getSourceId())) {
Permanent blocked = game.getPermanent(event.getTargetId());
if (blocked != null && filter.match(blocked, game)) {
if (setTargetPointer) {
this.getEffects().setTargetPointer(new FixedTarget(blocked, game));
}
return true;
}
}
if (event.getTargetId().equals(this.getSourceId())) {
Permanent blocker = game.getPermanent(event.getSourceId());
if (blocker != null && filter.match(blocker, game)) {
if (setTargetPointer) {
this.getEffects().setTargetPointer(new FixedTarget(blocker, game));
}
return true;
}
}
return false;
}
@Override
public String getRule() {
if (rule != null) {
return rule;
}
return "Whenever {this} blocks or becomes blocked" + (setTargetPointer ? " by a " + filter.getMessage() : "") + ", " + super.getRule();
}
@Override
public BlocksOrBecomesBlockedSourceTriggeredAbility copy() {
return new BlocksOrBecomesBlockedSourceTriggeredAbility(this);
}
}

View file

@ -0,0 +1,32 @@
package mage.abilities.common;
import mage.abilities.StaticAbility;
import mage.abilities.effects.common.CantBeCounteredSourceEffect;
import mage.constants.Zone;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class CantBeCounteredSourceAbility extends StaticAbility {
public CantBeCounteredSourceAbility() {
super(Zone.STACK, new CantBeCounteredSourceEffect());
}
public CantBeCounteredSourceAbility(CantBeCounteredSourceAbility ability) {
super(ability);
}
@Override
public String getRule() {
return "{this} can't be countered.";
}
@Override
public CantBeCounteredSourceAbility copy() {
return new CantBeCounteredSourceAbility(this);
}
}

View file

@ -1,104 +1,104 @@
package mage.abilities.common;
import mage.abilities.Ability;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.permanent.Permanent;
import mage.util.CardUtil;
/**
*
* @author emerald000
*/
public class CantHaveMoreThanAmountCountersSourceAbility extends SimpleStaticAbility {
private final CounterType counterType;
private final int amount;
public CantHaveMoreThanAmountCountersSourceAbility(CounterType counterType, int amount) {
super(Zone.BATTLEFIELD, new CantHaveMoreThanAmountCountersSourceEffect(counterType, amount));
this.counterType = counterType;
this.amount = amount;
}
private CantHaveMoreThanAmountCountersSourceAbility(CantHaveMoreThanAmountCountersSourceAbility ability) {
super(ability);
this.counterType = ability.counterType;
this.amount = ability.amount;
}
@Override
public String getRule() {
return "Rasputin can't have more than " + CardUtil.numberToText(this.amount) + ' ' + this.counterType.getName() + " counters on it.";
}
@Override
public CantHaveMoreThanAmountCountersSourceAbility copy() {
return new CantHaveMoreThanAmountCountersSourceAbility(this);
}
public CounterType getCounterType() {
return this.counterType;
}
public int getAmount() {
return this.amount;
}
}
class CantHaveMoreThanAmountCountersSourceEffect extends ReplacementEffectImpl {
private final CounterType counterType;
private final int amount;
CantHaveMoreThanAmountCountersSourceEffect(CounterType counterType, int amount) {
super(Duration.WhileOnBattlefield, Outcome.Detriment, false);
this.counterType = counterType;
this.amount = amount;
}
CantHaveMoreThanAmountCountersSourceEffect(final CantHaveMoreThanAmountCountersSourceEffect effect) {
super(effect);
this.counterType = effect.counterType;
this.amount = effect.amount;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
return true;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == EventType.ADD_COUNTER;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
Permanent permanent = game.getPermanent(event.getTargetId());
if (permanent == null) {
permanent = game.getPermanentEntering(event.getTargetId());
}
return permanent != null
&& permanent.getId().equals(source.getSourceId())
&& event.getData().equals(this.counterType.getName())
&& permanent.getCounters(game).getCount(this.counterType) == this.amount;
}
@Override
public boolean apply(Game game, Ability source) {
return true;
}
@Override
public CantHaveMoreThanAmountCountersSourceEffect copy() {
return new CantHaveMoreThanAmountCountersSourceEffect(this);
}
}
package mage.abilities.common;
import mage.abilities.Ability;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.permanent.Permanent;
import mage.util.CardUtil;
/**
*
* @author emerald000
*/
public class CantHaveMoreThanAmountCountersSourceAbility extends SimpleStaticAbility {
private final CounterType counterType;
private final int amount;
public CantHaveMoreThanAmountCountersSourceAbility(CounterType counterType, int amount) {
super(Zone.BATTLEFIELD, new CantHaveMoreThanAmountCountersSourceEffect(counterType, amount));
this.counterType = counterType;
this.amount = amount;
}
private CantHaveMoreThanAmountCountersSourceAbility(CantHaveMoreThanAmountCountersSourceAbility ability) {
super(ability);
this.counterType = ability.counterType;
this.amount = ability.amount;
}
@Override
public String getRule() {
return "Rasputin can't have more than " + CardUtil.numberToText(this.amount) + ' ' + this.counterType.getName() + " counters on it.";
}
@Override
public CantHaveMoreThanAmountCountersSourceAbility copy() {
return new CantHaveMoreThanAmountCountersSourceAbility(this);
}
public CounterType getCounterType() {
return this.counterType;
}
public int getAmount() {
return this.amount;
}
}
class CantHaveMoreThanAmountCountersSourceEffect extends ReplacementEffectImpl {
private final CounterType counterType;
private final int amount;
CantHaveMoreThanAmountCountersSourceEffect(CounterType counterType, int amount) {
super(Duration.WhileOnBattlefield, Outcome.Detriment, false);
this.counterType = counterType;
this.amount = amount;
}
CantHaveMoreThanAmountCountersSourceEffect(final CantHaveMoreThanAmountCountersSourceEffect effect) {
super(effect);
this.counterType = effect.counterType;
this.amount = effect.amount;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
return true;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == EventType.ADD_COUNTER;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
Permanent permanent = game.getPermanent(event.getTargetId());
if (permanent == null) {
permanent = game.getPermanentEntering(event.getTargetId());
}
return permanent != null
&& permanent.getId().equals(source.getSourceId())
&& event.getData().equals(this.counterType.getName())
&& permanent.getCounters(game).getCount(this.counterType) == this.amount;
}
@Override
public boolean apply(Game game, Ability source) {
return true;
}
@Override
public CantHaveMoreThanAmountCountersSourceEffect copy() {
return new CantHaveMoreThanAmountCountersSourceEffect(this);
}
}

View file

@ -9,7 +9,7 @@ import mage.constants.Zone;
*/
public class CastCommanderAbility extends SpellAbility {
private String ruleText;
private final String ruleText;
public CastCommanderAbility(Card card, SpellAbility spellTemplate) {
super(spellTemplate);

View file

@ -1,49 +1,49 @@
/*
* 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.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.game.events.GameEvent.EventType;
import mage.game.permanent.Permanent;
/**
* @author jeffwadsworth
*/
public class ControllerPlaysLandTriggeredAbility extends TriggeredAbilityImpl {
public ControllerPlaysLandTriggeredAbility(Zone zone, Effect effect, Boolean optional) {
super(zone, effect, optional);
}
public ControllerPlaysLandTriggeredAbility(ControllerPlaysLandTriggeredAbility ability) {
super(ability);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == EventType.LAND_PLAYED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Permanent land = game.getPermanent(event.getTargetId());
return land != null && land.getControllerId().equals(controllerId);
}
@Override
public ControllerPlaysLandTriggeredAbility copy() {
return new ControllerPlaysLandTriggeredAbility(this);
}
@Override
public String getRule() {
return "Whenever you play a land, ";
}
}
/*
* 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.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.game.events.GameEvent.EventType;
import mage.game.permanent.Permanent;
/**
* @author jeffwadsworth
*/
public class ControllerPlaysLandTriggeredAbility extends TriggeredAbilityImpl {
public ControllerPlaysLandTriggeredAbility(Zone zone, Effect effect, Boolean optional) {
super(zone, effect, optional);
}
public ControllerPlaysLandTriggeredAbility(ControllerPlaysLandTriggeredAbility ability) {
super(ability);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == EventType.LAND_PLAYED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Permanent land = game.getPermanent(event.getTargetId());
return land != null && land.getControllerId().equals(controllerId);
}
@Override
public ControllerPlaysLandTriggeredAbility copy() {
return new ControllerPlaysLandTriggeredAbility(this);
}
@Override
public String getRule() {
return "Whenever you play a land, ";
}
}

View file

@ -0,0 +1,63 @@
package mage.abilities.common;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.abilities.keyword.CyclingAbility;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.StackAbility;
import mage.game.stack.StackObject;
/**
* @author TheElk801
*/
public class CycleControllerTriggeredAbility extends TriggeredAbilityImpl {
private final boolean excludeSource;
public CycleControllerTriggeredAbility(Effect effect) {
this(effect, false);
}
public CycleControllerTriggeredAbility(Effect effect, boolean optional) {
this(effect, optional, false);
}
public CycleControllerTriggeredAbility(Effect effect, boolean optional, boolean excludeSource) {
super(Zone.BATTLEFIELD, effect, optional);
this.excludeSource = excludeSource;
}
private CycleControllerTriggeredAbility(final CycleControllerTriggeredAbility ability) {
super(ability);
this.excludeSource = ability.excludeSource;
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ACTIVATED_ABILITY;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (game.getState().getStack().isEmpty()
|| !event.getPlayerId().equals(this.getControllerId())
|| (event.getSourceId().equals(this.getSourceId()) && excludeSource)) {
return false;
}
StackObject item = game.getState().getStack().getFirst();
return item instanceof StackAbility
&& item.getStackAbility() instanceof CyclingAbility;
}
@Override
public String getRule() {
return "Whenever you cycle " + (excludeSource ? "another" : "a") + " card, " + super.getRule();
}
@Override
public CycleControllerTriggeredAbility copy() {
return new CycleControllerTriggeredAbility(this);
}
}

View file

@ -1,5 +1,3 @@
package mage.abilities.common;
import mage.abilities.effects.Effect;
@ -10,7 +8,6 @@ import mage.game.events.GameEvent;
import mage.game.stack.StackObject;
/**
*
* @author Plopman
*/
public class CycleTriggeredAbility extends ZoneChangeTriggeredAbility {
@ -26,7 +23,7 @@ public class CycleTriggeredAbility extends ZoneChangeTriggeredAbility {
public CycleTriggeredAbility(CycleTriggeredAbility ability) {
super(ability);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ACTIVATED_ABILITY;
@ -34,10 +31,11 @@ public class CycleTriggeredAbility extends ZoneChangeTriggeredAbility {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if(event.getSourceId().equals(this.getSourceId())) {
if (event.getSourceId().equals(this.getSourceId())) {
StackObject object = game.getStack().getStackObject(event.getSourceId());
if(object != null && object.getStackAbility() instanceof CyclingAbility){
return true;
if (object != null && object.getStackAbility() instanceof CyclingAbility) {
this.getEffects().setValue("cycleCosts", object.getStackAbility().getCosts());
return true;
}
}
return false;

View file

@ -1,104 +1,104 @@
/*
* 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.common;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.SetTargetPointer;
import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.game.Game;
import mage.game.events.DamagedCreatureEvent;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
/**
*
* @author LevelX2
*/
public class DealsDamageToACreatureAllTriggeredAbility extends TriggeredAbilityImpl {
private final boolean combatDamageOnly;
private final FilterPermanent filterPermanent;
private final SetTargetPointer setTargetPointer;
/**
* This ability works only for permanents doing damage.
*
* @param effect
* @param optional
* @param filterPermanent The filter that restricts which permanets have to
* trigger
* @param setTargetPointer The target to be set to target pointer of the
* effect.<br>
* - PLAYER = player controlling the damage source.<br>
* - PERMANENT = source permanent.<br>
* - PERMANENT_TARGET = damaged creature.
* @param combatDamageOnly The flag to determine if only combat damage has
* to trigger
*/
public DealsDamageToACreatureAllTriggeredAbility(Effect effect, boolean optional, FilterPermanent filterPermanent, SetTargetPointer setTargetPointer, boolean combatDamageOnly) {
super(Zone.BATTLEFIELD, effect, optional);
this.combatDamageOnly = combatDamageOnly;
this.setTargetPointer = setTargetPointer;
this.filterPermanent = filterPermanent;
}
public DealsDamageToACreatureAllTriggeredAbility(final DealsDamageToACreatureAllTriggeredAbility ability) {
super(ability);
this.combatDamageOnly = ability.combatDamageOnly;
this.filterPermanent = ability.filterPermanent;
this.setTargetPointer = ability.setTargetPointer;
}
@Override
public DealsDamageToACreatureAllTriggeredAbility copy() {
return new DealsDamageToACreatureAllTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == EventType.DAMAGED_CREATURE;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (!combatDamageOnly || ((DamagedCreatureEvent) event).isCombatDamage()) {
Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId());
if (permanent != null && filterPermanent.match(permanent, getSourceId(), getControllerId(), game)) {
for (Effect effect : this.getEffects()) {
effect.setValue("damage", event.getAmount());
effect.setValue("sourceId", event.getSourceId());
switch (setTargetPointer) {
case PLAYER:
effect.setTargetPointer(new FixedTarget(permanent.getControllerId()));
break;
case PERMANENT:
effect.setTargetPointer(new FixedTarget(permanent, game));
break;
case PERMANENT_TARGET:
Permanent permanent_target = game.getPermanentOrLKIBattlefield(event.getTargetId());
if (permanent_target != null) {
effect.setTargetPointer(new FixedTarget(permanent_target, game));
}
break;
}
}
return true;
}
}
return false;
}
@Override
public String getRule() {
return "Whenever " + filterPermanent.getMessage() + " deals "
+ (combatDamageOnly ? "combat " : "") + "damage to a creature, " + super.getRule();
}
}
/*
* 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.common;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.SetTargetPointer;
import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.game.Game;
import mage.game.events.DamagedCreatureEvent;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
/**
*
* @author LevelX2
*/
public class DealsDamageToACreatureAllTriggeredAbility extends TriggeredAbilityImpl {
private final boolean combatDamageOnly;
private final FilterPermanent filterPermanent;
private final SetTargetPointer setTargetPointer;
/**
* This ability works only for permanents doing damage.
*
* @param effect
* @param optional
* @param filterPermanent The filter that restricts which permanets have to
* trigger
* @param setTargetPointer The target to be set to target pointer of the
* effect.<br>
* - PLAYER = player controlling the damage source.<br>
* - PERMANENT = source permanent.<br>
* - PERMANENT_TARGET = damaged creature.
* @param combatDamageOnly The flag to determine if only combat damage has
* to trigger
*/
public DealsDamageToACreatureAllTriggeredAbility(Effect effect, boolean optional, FilterPermanent filterPermanent, SetTargetPointer setTargetPointer, boolean combatDamageOnly) {
super(Zone.BATTLEFIELD, effect, optional);
this.combatDamageOnly = combatDamageOnly;
this.setTargetPointer = setTargetPointer;
this.filterPermanent = filterPermanent;
}
public DealsDamageToACreatureAllTriggeredAbility(final DealsDamageToACreatureAllTriggeredAbility ability) {
super(ability);
this.combatDamageOnly = ability.combatDamageOnly;
this.filterPermanent = ability.filterPermanent;
this.setTargetPointer = ability.setTargetPointer;
}
@Override
public DealsDamageToACreatureAllTriggeredAbility copy() {
return new DealsDamageToACreatureAllTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == EventType.DAMAGED_CREATURE;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (!combatDamageOnly || ((DamagedCreatureEvent) event).isCombatDamage()) {
Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId());
if (permanent != null && filterPermanent.match(permanent, getSourceId(), getControllerId(), game)) {
for (Effect effect : this.getEffects()) {
effect.setValue("damage", event.getAmount());
effect.setValue("sourceId", event.getSourceId());
switch (setTargetPointer) {
case PLAYER:
effect.setTargetPointer(new FixedTarget(permanent.getControllerId()));
break;
case PERMANENT:
effect.setTargetPointer(new FixedTarget(permanent, game));
break;
case PERMANENT_TARGET:
Permanent permanent_target = game.getPermanentOrLKIBattlefield(event.getTargetId());
if (permanent_target != null) {
effect.setTargetPointer(new FixedTarget(permanent_target, game));
}
break;
}
}
return true;
}
}
return false;
}
@Override
public String getRule() {
return "Whenever " + filterPermanent.getMessage() + " deals "
+ (combatDamageOnly ? "combat " : "") + "damage to a creature, " + super.getRule();
}
}

View file

@ -0,0 +1,62 @@
package mage.abilities.common;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.DestroyTargetEffect;
import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
public class DestroyPlaneswalkerWhenDamagedTriggeredAbility extends TriggeredAbilityImpl {
private final FilterPermanent filter;
public DestroyPlaneswalkerWhenDamagedTriggeredAbility() {
this((FilterPermanent) null);
}
public DestroyPlaneswalkerWhenDamagedTriggeredAbility(FilterPermanent filter) {
super(Zone.BATTLEFIELD, null);
this.filter = filter;
}
private DestroyPlaneswalkerWhenDamagedTriggeredAbility(final DestroyPlaneswalkerWhenDamagedTriggeredAbility effect) {
super(effect);
this.filter = effect.filter;
}
@Override
public DestroyPlaneswalkerWhenDamagedTriggeredAbility copy() {
return new DestroyPlaneswalkerWhenDamagedTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DAMAGED_PLANESWALKER;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Permanent permanent = getSourcePermanentIfItStillExists(game);
if (permanent != null) {
boolean applies = filter != null ?
filter.match(permanent, game) : event.getSourceId().equals(getSourceId());
if (applies) {
Effect effect = new DestroyTargetEffect();
effect.setTargetPointer(new FixedTarget(event.getTargetId(), game));
this.getEffects().clear();
this.addEffect(effect);
return true;
}
}
return false;
}
@Override
public String getRule() {
return "Whenever " + (filter != null ? filter.getMessage() : "this creature") + " deals damage to a planeswalker, destroy that planeswalker.";
}
}

View file

@ -69,7 +69,7 @@ public class DiesCreatureTriggeredAbility extends TriggeredAbilityImpl {
public boolean checkTrigger(GameEvent event, Game game) {
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
if (zEvent.isDiesEvent()) {
if (filter.match(zEvent.getTarget(), sourceId, controllerId, game) && zEvent.getTarget().isCreature()) {
if (filter.match(zEvent.getTarget(), sourceId, controllerId, game)) {
if (setTargetPointer) {
for (Effect effect : this.getEffects()) {
effect.setTargetPointer(new FixedTarget(event.getTargetId(), game));

View file

@ -0,0 +1,79 @@
package mage.abilities.common;
import mage.MageObject;
import mage.abilities.effects.Effect;
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.game.permanent.PermanentToken;
/**
* @author BetaSteward_at_googlemail.com
*/
public class DiesSourceTriggeredAbility extends ZoneChangeTriggeredAbility {
public DiesSourceTriggeredAbility(Effect effect, boolean optional) {
super(Zone.BATTLEFIELD, Zone.GRAVEYARD, effect, "When {this} dies, ", optional);
}
public DiesSourceTriggeredAbility(Effect effect) {
this(effect, false);
}
public DiesSourceTriggeredAbility(DiesSourceTriggeredAbility ability) {
super(ability);
}
@Override
public boolean isInUseableZone(Game game, MageObject source, GameEvent event) {
// check it was previously on battlefield
Permanent before = ((ZoneChangeEvent) event).getTarget();
if (before == null) {
return false;
}
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 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 {
// Already moved to another zone, so guess it's ok
return true;
}
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
if (super.checkEventType(event, game)) {
return ((ZoneChangeEvent) event).getFromZone() == Zone.BATTLEFIELD && ((ZoneChangeEvent) event).getToZone() == Zone.GRAVEYARD;
}
return false;
}
@Override
public DiesSourceTriggeredAbility copy() {
return new DiesSourceTriggeredAbility(this);
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (super.checkTrigger(event, game)) {
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
if (zEvent.getTarget().isTransformable()) {
if (!zEvent.getTarget().getAbilities().contains(this)) {
return false;
}
}
for (Effect effect : getEffects()) {
effect.setValue("permanentLeftBattlefield", zEvent.getTarget());
}
return true;
}
return false;
}
}

View file

@ -1,4 +1,3 @@
package mage.abilities.common;
import mage.MageObject;
@ -17,6 +16,7 @@ import mage.game.permanent.Permanent;
public class DiesThisOrAnotherCreatureTriggeredAbility extends TriggeredAbilityImpl {
protected FilterCreaturePermanent filter;
private boolean applyFilterOnSource = false;
public DiesThisOrAnotherCreatureTriggeredAbility(Effect effect, boolean optional) {
this(effect, optional, new FilterCreaturePermanent());
@ -30,6 +30,12 @@ public class DiesThisOrAnotherCreatureTriggeredAbility extends TriggeredAbilityI
public DiesThisOrAnotherCreatureTriggeredAbility(DiesThisOrAnotherCreatureTriggeredAbility ability) {
super(ability);
this.filter = ability.filter;
this.applyFilterOnSource = ability.applyFilterOnSource;
}
public DiesThisOrAnotherCreatureTriggeredAbility setApplyFilterOnSource(boolean applyFilterOnSource) {
this.applyFilterOnSource = applyFilterOnSource;
return this;
}
@Override
@ -68,7 +74,7 @@ public class DiesThisOrAnotherCreatureTriggeredAbility extends TriggeredAbilityI
if (zEvent.isDiesEvent()) {
if (zEvent.getTarget() != null) {
if (zEvent.getTarget().getId().equals(this.getSourceId())) {
if (!applyFilterOnSource && zEvent.getTarget().getId().equals(this.getSourceId())) {
return true;
} else {
if (filter.match(zEvent.getTarget(), getSourceId(), getControllerId(), game)) {

View file

@ -1,54 +1,54 @@
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.game.events.GameEvent.EventType;
import mage.game.stack.StackObject;
/**
*
* @author Styxo
*/
public class DiscardedByOpponentTriggeredAbility extends TriggeredAbilityImpl {
public DiscardedByOpponentTriggeredAbility(Effect effect) {
this(effect, false);
}
public DiscardedByOpponentTriggeredAbility(Effect effect, boolean optional) {
super(Zone.GRAVEYARD, effect, optional);
}
public DiscardedByOpponentTriggeredAbility(final DiscardedByOpponentTriggeredAbility ability) {
super(ability);
}
@Override
public DiscardedByOpponentTriggeredAbility copy() {
return new DiscardedByOpponentTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == EventType.DISCARDED_CARD;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (getSourceId().equals(event.getTargetId())) {
StackObject stackObject = game.getStack().getStackObject(event.getSourceId());
if (stackObject != null) {
return game.getOpponents(this.getControllerId()).contains(stackObject.getControllerId());
}
}
return false;
}
@Override
public String getRule() {
return "When a spell or ability an opponent controls causes you to discard this card, " + super.getRule();
}
}
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.game.events.GameEvent.EventType;
import mage.game.stack.StackObject;
/**
*
* @author Styxo
*/
public class DiscardedByOpponentTriggeredAbility extends TriggeredAbilityImpl {
public DiscardedByOpponentTriggeredAbility(Effect effect) {
this(effect, false);
}
public DiscardedByOpponentTriggeredAbility(Effect effect, boolean optional) {
super(Zone.GRAVEYARD, effect, optional);
}
public DiscardedByOpponentTriggeredAbility(final DiscardedByOpponentTriggeredAbility ability) {
super(ability);
}
@Override
public DiscardedByOpponentTriggeredAbility copy() {
return new DiscardedByOpponentTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == EventType.DISCARDED_CARD;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (getSourceId().equals(event.getTargetId())) {
StackObject stackObject = game.getStack().getStackObject(event.getSourceId());
if (stackObject != null) {
return game.getOpponents(this.getControllerId()).contains(stackObject.getControllerId());
}
}
return false;
}
@Override
public String getRule() {
return "When a spell or ability an opponent controls causes you to discard this card, " + super.getRule();
}
}

View file

@ -1,4 +1,3 @@
package mage.abilities.common;
import mage.abilities.TriggeredAbilityImpl;
@ -43,7 +42,7 @@ public class EnchantedCreatureBlockedTriggeredAbility extends TriggeredAbilityIm
@Override
public String getRule() {
return "Whenever enchanted creature becomes blocked by a creature, " + super.getRule();
return "Whenever enchanted creature becomes blocked, " + super.getRule();
}
@Override

View file

@ -1,7 +1,6 @@
package mage.abilities.common;
import java.util.UUID;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.SetTargetPointer;
@ -12,8 +11,9 @@ import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public class EntersBattlefieldAllTriggeredAbility extends TriggeredAbilityImpl {
@ -22,6 +22,7 @@ public class EntersBattlefieldAllTriggeredAbility extends TriggeredAbilityImpl {
protected String rule;
protected boolean controlledText;
protected SetTargetPointer setTargetPointer;
protected final boolean thisOrAnother;
/**
* zone = BATTLEFIELD optional = false
@ -54,11 +55,16 @@ public class EntersBattlefieldAllTriggeredAbility extends TriggeredAbilityImpl {
}
public EntersBattlefieldAllTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter, boolean optional, SetTargetPointer setTargetPointer, String rule, boolean controlledText) {
this(zone, effect, filter, optional, setTargetPointer, rule, controlledText, false);
}
protected EntersBattlefieldAllTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter, boolean optional, SetTargetPointer setTargetPointer, String rule, boolean controlledText, boolean thisOrAnother) {
super(zone, effect, optional);
this.filter = filter;
this.rule = rule;
this.controlledText = controlledText;
this.setTargetPointer = setTargetPointer;
this.thisOrAnother = thisOrAnother;
}
public EntersBattlefieldAllTriggeredAbility(final EntersBattlefieldAllTriggeredAbility ability) {
@ -67,6 +73,7 @@ public class EntersBattlefieldAllTriggeredAbility extends TriggeredAbilityImpl {
this.rule = ability.rule;
this.controlledText = ability.controlledText;
this.setTargetPointer = ability.setTargetPointer;
this.thisOrAnother = ability.thisOrAnother;
}
@Override
@ -105,7 +112,11 @@ public class EntersBattlefieldAllTriggeredAbility extends TriggeredAbilityImpl {
if (rule != null && !rule.isEmpty()) {
return rule;
}
StringBuilder sb = new StringBuilder("Whenever ").append(filter.getMessage());
StringBuilder sb = new StringBuilder("Whenever ");
if (thisOrAnother) {
sb.append("{this} or another ");
}
sb.append(filter.getMessage());
sb.append(" enters the battlefield");
if (controlledText) {
sb.append(" under your control, ");

View file

@ -0,0 +1,62 @@
package mage.abilities.common;
import mage.abilities.effects.Effect;
import mage.constants.SetTargetPointer;
import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
/**
* @author TheElk801
*/
public class EntersBattlefieldThisOrAnotherTriggeredAbility extends EntersBattlefieldAllTriggeredAbility {
private final boolean onlyControlled;
public EntersBattlefieldThisOrAnotherTriggeredAbility(Effect effect, FilterPermanent filter) {
this(effect, filter, false, false);
}
public EntersBattlefieldThisOrAnotherTriggeredAbility(Effect effect, FilterPermanent filter, boolean optional, boolean onlyControlled) {
this(effect, filter, optional, SetTargetPointer.NONE, onlyControlled);
}
public EntersBattlefieldThisOrAnotherTriggeredAbility(Effect effect, FilterPermanent filter, boolean optional, SetTargetPointer setTargetPointer, boolean onlyControlled) {
this(Zone.BATTLEFIELD, effect, filter, optional, setTargetPointer, onlyControlled);
}
public EntersBattlefieldThisOrAnotherTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter, boolean optional, SetTargetPointer setTargetPointer, boolean onlyControlled) {
super(zone, effect, filter, optional, setTargetPointer, null, onlyControlled, true);
this.onlyControlled = onlyControlled;
}
private EntersBattlefieldThisOrAnotherTriggeredAbility(final EntersBattlefieldThisOrAnotherTriggeredAbility ability) {
super(ability);
this.onlyControlled = ability.onlyControlled;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (!super.checkTrigger(event, game)) {
return false;
}
Permanent permanent = game.getPermanent(event.getTargetId());
if (permanent == null) {
return false;
}
if (permanent.getId().equals(getSourceId())) {
return true;
}
if (onlyControlled && !permanent.isControlledBy(this.getControllerId())) {
return false;
}
return filter.match(permanent, getSourceId(), getControllerId(), game);
}
@Override
public EntersBattlefieldThisOrAnotherTriggeredAbility copy() {
return new EntersBattlefieldThisOrAnotherTriggeredAbility(this);
}
}

View file

@ -0,0 +1,72 @@
package mage.abilities.common;
import mage.abilities.Ability;
import mage.abilities.StaticAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.RestrictionEffect;
import mage.abilities.effects.common.combat.AttacksIfAbleAttachedEffect;
import mage.constants.AttachmentType;
import mage.constants.Duration;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public class GoadAttachedAbility extends StaticAbility {
public GoadAttachedAbility(Effect... effects) {
super(Zone.BATTLEFIELD, null);
for (Effect effect : effects) {
this.addEffect(effect);
}
this.addEffect(new AttacksIfAbleAttachedEffect(
Duration.WhileOnBattlefield, AttachmentType.AURA
).setText(", and is goaded. "));
this.addEffect(new GoadAttackEffect());
}
private GoadAttachedAbility(final GoadAttachedAbility ability) {
super(ability);
}
@Override
public GoadAttachedAbility copy() {
return new GoadAttachedAbility(this);
}
}
class GoadAttackEffect extends RestrictionEffect {
GoadAttackEffect() {
super(Duration.WhileOnBattlefield);
staticText = "<i>(It attacks each combat if able and attacks a player other than you if able.)</i>";
}
private GoadAttackEffect(final GoadAttackEffect effect) {
super(effect);
}
@Override
public GoadAttackEffect copy() {
return new GoadAttackEffect(this);
}
@Override
public boolean applies(Permanent permanent, Ability source, Game game) {
Permanent attachment = game.getPermanent(source.getSourceId());
return attachment != null && attachment.getAttachedTo() != null
&& permanent.getId().equals(attachment.getAttachedTo());
}
@Override
public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game, boolean canUseChooseDialogs) {
if (defenderId == null) {
return true;
}
return !defenderId.equals(source.getControllerId());
}
}

View file

@ -1,88 +1,88 @@
package mage.abilities.common;
import java.util.UUID;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.SetTargetPointer;
import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
/**
*
* @author Styxo
*/
public class LeavesBattlefieldAllTriggeredAbility extends TriggeredAbilityImpl {
protected FilterPermanent filter;
protected SetTargetPointer setTargetPointer;
public LeavesBattlefieldAllTriggeredAbility(Effect effect, FilterPermanent filter) {
this(effect, filter, false);
}
public LeavesBattlefieldAllTriggeredAbility(Effect effect, FilterPermanent filter, boolean optional) {
this(Zone.BATTLEFIELD, effect, filter, optional);
}
public LeavesBattlefieldAllTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter, boolean optional) {
this(zone, effect, filter, optional, SetTargetPointer.NONE);
}
public LeavesBattlefieldAllTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter, boolean optional, SetTargetPointer setTargetPointer) {
super(zone, effect, optional);
this.filter = filter;
this.setTargetPointer = setTargetPointer;
}
private LeavesBattlefieldAllTriggeredAbility(final LeavesBattlefieldAllTriggeredAbility ability) {
super(ability);
filter = ability.filter;
setTargetPointer = ability.setTargetPointer;
}
@Override
public LeavesBattlefieldAllTriggeredAbility copy() {
return new LeavesBattlefieldAllTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ZONE_CHANGE;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
if (zEvent.getFromZone() == Zone.BATTLEFIELD) {
UUID targetId = event.getTargetId();
Permanent permanent = game.getPermanentOrLKIBattlefield(targetId);
if (permanent != null && filter.match(permanent, getSourceId(), getControllerId(), game)) {
if (setTargetPointer != SetTargetPointer.NONE) {
for (Effect effect : this.getEffects()) {
switch (setTargetPointer) {
case PERMANENT:
effect.setTargetPointer(new FixedTarget(permanent.getId()));
break;
case PLAYER:
effect.setTargetPointer(new FixedTarget(permanent.getControllerId()));
break;
}
}
}
return true;
}
}
return false;
}
@Override
public String getRule() {
return "Whenever " + filter.getMessage() + " leaves the battlefield, " + super.getRule();
}
}
package mage.abilities.common;
import java.util.UUID;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.SetTargetPointer;
import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
/**
*
* @author Styxo
*/
public class LeavesBattlefieldAllTriggeredAbility extends TriggeredAbilityImpl {
protected FilterPermanent filter;
protected SetTargetPointer setTargetPointer;
public LeavesBattlefieldAllTriggeredAbility(Effect effect, FilterPermanent filter) {
this(effect, filter, false);
}
public LeavesBattlefieldAllTriggeredAbility(Effect effect, FilterPermanent filter, boolean optional) {
this(Zone.BATTLEFIELD, effect, filter, optional);
}
public LeavesBattlefieldAllTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter, boolean optional) {
this(zone, effect, filter, optional, SetTargetPointer.NONE);
}
public LeavesBattlefieldAllTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter, boolean optional, SetTargetPointer setTargetPointer) {
super(zone, effect, optional);
this.filter = filter;
this.setTargetPointer = setTargetPointer;
}
protected LeavesBattlefieldAllTriggeredAbility(final LeavesBattlefieldAllTriggeredAbility ability) {
super(ability);
filter = ability.filter;
setTargetPointer = ability.setTargetPointer;
}
@Override
public LeavesBattlefieldAllTriggeredAbility copy() {
return new LeavesBattlefieldAllTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ZONE_CHANGE;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
if (zEvent.getFromZone() == Zone.BATTLEFIELD) {
UUID targetId = event.getTargetId();
Permanent permanent = game.getPermanentOrLKIBattlefield(targetId);
if (permanent != null && filter.match(permanent, getSourceId(), getControllerId(), game)) {
if (setTargetPointer != SetTargetPointer.NONE) {
for (Effect effect : this.getEffects()) {
switch (setTargetPointer) {
case PERMANENT:
effect.setTargetPointer(new FixedTarget(permanent, game));
break;
case PLAYER:
effect.setTargetPointer(new FixedTarget(permanent.getControllerId()));
break;
}
}
}
return true;
}
}
return false;
}
@Override
public String getRule() {
return "Whenever " + filter.getMessage() + " leaves the battlefield, " + super.getRule();
}
}

View file

@ -1,4 +1,3 @@
package mage.abilities.common;
import java.util.ArrayList;
@ -78,7 +77,7 @@ class LicidEffect extends OneShotEffect {
if (licid != null) {
UUID messageId = UUID.randomUUID();
LicidContinuousEffect effect = new LicidContinuousEffect(messageId);
effect.setTargetPointer(new FixedTarget(licid.getId()));
effect.setTargetPointer(new FixedTarget(licid, game));
game.addEffect(effect, source);
new AttachEffect(Outcome.Neutral).apply(game, source);
SpecialAction specialAction = new LicidSpecialAction(this.specialActionCost, messageId, licid.getIdName());
@ -130,7 +129,8 @@ class LicidContinuousEffect extends ContinuousEffectImpl {
}
}
}
licid.getAbilities(game).removeAll(toRemove);
licid.removeAbilities(toRemove, source.getSourceId(), game);
Ability ability = new EnchantAbility("creature");
ability.setRuleAtTheTop(true);
licid.addAbility(ability, source.getSourceId(), game);

View file

@ -0,0 +1,47 @@
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;
/**
* @author TheElk801
*/
public class MutatesSourceTriggeredAbility extends TriggeredAbilityImpl {
public MutatesSourceTriggeredAbility(Effect effect) {
this(effect, false);
}
public MutatesSourceTriggeredAbility(Effect effect, boolean optional) {
super(Zone.BATTLEFIELD, effect, optional);
}
private MutatesSourceTriggeredAbility(final MutatesSourceTriggeredAbility ability) {
super(ability);
}
@Override
public MutatesSourceTriggeredAbility copy() {
return new MutatesSourceTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
// TODO: implement this
return false;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
// TODO: implement this
return false;
}
@Override
public String getRule() {
return "Whenever this creature mutates, " + super.getRule();
}
}

View file

@ -1,48 +1,48 @@
/*
* 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.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.game.permanent.Permanent;
/**
* @author jeffwadsworth
*/
public class OpponentPlaysLandTriggeredAbility extends TriggeredAbilityImpl {
public OpponentPlaysLandTriggeredAbility(Zone zone, Effect effect, Boolean optional) {
super(zone, effect, optional);
}
public OpponentPlaysLandTriggeredAbility(OpponentPlaysLandTriggeredAbility ability) {
super(ability);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.LAND_PLAYED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Permanent land = game.getPermanent(event.getTargetId());
return land != null && game.getOpponents(controllerId).contains(land.getControllerId());
}
@Override
public OpponentPlaysLandTriggeredAbility copy() {
return new OpponentPlaysLandTriggeredAbility(this);
}
@Override
public String getRule() {
return "Whenever an opponent plays a land, ";
}
}
/*
* 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.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.game.permanent.Permanent;
/**
* @author jeffwadsworth
*/
public class OpponentPlaysLandTriggeredAbility extends TriggeredAbilityImpl {
public OpponentPlaysLandTriggeredAbility(Zone zone, Effect effect, Boolean optional) {
super(zone, effect, optional);
}
public OpponentPlaysLandTriggeredAbility(OpponentPlaysLandTriggeredAbility ability) {
super(ability);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.LAND_PLAYED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Permanent land = game.getPermanent(event.getTargetId());
return land != null && game.getOpponents(controllerId).contains(land.getControllerId());
}
@Override
public OpponentPlaysLandTriggeredAbility copy() {
return new OpponentPlaysLandTriggeredAbility(this);
}
@Override
public String getRule() {
return "Whenever an opponent plays a land, ";
}
}

View file

@ -1,14 +1,13 @@
package mage.abilities.common;
import java.util.UUID;
import mage.abilities.ActivatedAbilityImpl;
import mage.abilities.effects.common.PassEffect;
import mage.constants.Zone;
import mage.game.Game;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class PassAbility extends ActivatedAbilityImpl {
@ -29,7 +28,7 @@ public class PassAbility extends ActivatedAbilityImpl {
@Override
public ActivationStatus canActivate(UUID playerId, Game game) {
return ActivationStatus.getTrue();
return ActivationStatus.getTrue(this, game);
}
@Override

View file

@ -85,7 +85,7 @@ public class PutCardIntoGraveFromAnywhereAllTriggeredAbility extends TriggeredAb
switch (setTargetPointer) {
case CARD:
for (Effect effect : getEffects()) {
effect.setTargetPointer(new FixedTarget(card.getId(), card.getZoneChangeCounter(game)));
effect.setTargetPointer(new FixedTarget(card, game));
}
break;
case PLAYER:

View file

@ -41,6 +41,9 @@ public class TapForManaAllTriggeredAbility extends TriggeredAbilityImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (game.inCheckPlayableState()) { // Ignored - see GameEvent.TAPPED_FOR_MANA
return false;
}
Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId());
if (permanent != null && filter.match(permanent, getSourceId(), getControllerId(), game)) {
ManaEvent mEvent = (ManaEvent) event;
@ -49,7 +52,7 @@ public class TapForManaAllTriggeredAbility extends TriggeredAbilityImpl {
}
switch(setTargetPointer) {
case PERMANENT:
getEffects().get(0).setTargetPointer(new FixedTarget(permanent.getId()));
getEffects().get(0).setTargetPointer(new FixedTarget(permanent, game));
break;
case PLAYER:
getEffects().get(0).setTargetPointer(new FixedTarget(permanent.getControllerId()));

View file

@ -49,7 +49,7 @@ public class TapForManaAllTriggeredManaAbility extends TriggeredManaAbility {
effect.setValue("mana", mEvent.getMana());
switch(setTargetPointer) {
case PERMANENT:
effect.setTargetPointer(new FixedTarget(permanent.getId()));
effect.setTargetPointer(new FixedTarget(permanent, game));
break;
case PLAYER:
effect.setTargetPointer(new FixedTarget(permanent.getControllerId()));

View file

@ -34,6 +34,9 @@ public class TapLandForManaAllTriggeredAbility extends TriggeredAbilityImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (game.inCheckPlayableState()) { // Ignored - see GameEvent.TAPPED_FOR_MANA
return false;
}
Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId());
if (permanent != null && permanent.isLand()) {
if (setTargetPointer) {

View file

@ -38,7 +38,7 @@ public class TapLandForManaAllTriggeredManaAbility extends TriggeredManaAbility
Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId());
if (permanent != null && permanent.isLand()) {
if (setTargetPointer) {
getEffects().get(0).setTargetPointer(new FixedTarget(permanent.getId()));
getEffects().get(0).setTargetPointer(new FixedTarget(permanent, game));
}
return true;
}

View file

@ -40,6 +40,7 @@ public class TurnFaceUpAbility extends SpecialAction {
this.usesStack = false;
this.abilityType = AbilityType.SPECIAL_ACTION;
this.setRuleVisible(false); // will be made visible only to controller in CardView
this.setWorksFaceDown(true);
}
public TurnFaceUpAbility(final TurnFaceUpAbility ability) {

View file

@ -31,7 +31,7 @@ public class UnattachedTriggeredAbility extends TriggeredAbilityImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (event.getSourceId().equals(this.getSourceId()) ) {
getEffects().get(0).setTargetPointer(new FixedTarget(event.getTargetId()));
getEffects().get(0).setTargetPointer(new FixedTarget(event.getTargetId(), game));
return true;
}
return false;

View file

@ -27,6 +27,9 @@ public class ZoneChangeAllTriggeredAbility extends TriggeredAbilityImpl {
public ZoneChangeAllTriggeredAbility(Zone zone, Zone fromZone, Zone toZone, Effect effect, FilterPermanent filter, String rule, boolean optional) {
super(zone, effect, optional);
if (fromZone == Zone.BATTLEFIELD) {
setLeavesTheBattlefieldTrigger(true);
}
this.fromZone = fromZone;
this.toZone = toZone;
this.rule = rule;

View file

@ -1,4 +1,3 @@
package mage.abilities.common.delayed;
import mage.constants.Outcome;
@ -19,7 +18,6 @@ public class PactDelayedTriggeredAbility extends DelayedTriggeredAbility {
super(new PactEffect(cost));
}
public PactDelayedTriggeredAbility(PactDelayedTriggeredAbility ability) {
super(ability);
}
@ -39,8 +37,6 @@ public class PactDelayedTriggeredAbility extends DelayedTriggeredAbility {
return game.isActivePlayer(this.getControllerId());
}
@Override
public String getRule() {
return "At the beginning of your next upkeep " + modes.getText();
@ -49,8 +45,7 @@ public class PactDelayedTriggeredAbility extends DelayedTriggeredAbility {
class PactEffect extends OneShotEffect {
private ManaCosts cost;
private final ManaCosts cost;
public PactEffect(ManaCosts cost) {
super(Outcome.Neutral);
@ -60,7 +55,7 @@ class PactEffect extends OneShotEffect {
public PactEffect(final PactEffect effect) {
super(effect);
this.cost = effect.cost;
this.cost = effect.cost.copy();
}
@Override
@ -71,10 +66,10 @@ class PactEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player != null) {
if (player.chooseUse(Outcome.Benefit, "Pay " + cost.getText() + '?', source, game)) {
if (player != null) {
if (player.chooseUse(Outcome.Benefit, "Pay " + cost.getText() + '?', source, game)) {
cost.clearPaid();
if (cost.pay(source, game, source.getSourceId(), source.getControllerId(), false, null)){
if (cost.pay(source, game, source.getSourceId(), source.getControllerId(), false, null)) {
return true;
}
}
@ -83,7 +78,4 @@ class PactEffect extends OneShotEffect {
}
return false;
}
}

View file

@ -0,0 +1,52 @@
package mage.abilities.common.delayed;
import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.effects.Effect;
import mage.constants.Duration;
import mage.game.Game;
import mage.game.events.GameEvent;
import java.util.Locale;
/**
* @author TheElk801
*/
public class ReflexiveTriggeredAbility extends DelayedTriggeredAbility {
private final String text;
public ReflexiveTriggeredAbility(Effect effect, boolean optional, String text) {
super(effect, Duration.EndOfTurn, true, optional);
this.text = text;
}
protected ReflexiveTriggeredAbility(final ReflexiveTriggeredAbility ability) {
super(ability);
this.text = ability.text;
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.OPTION_USED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return event.getPlayerId().equals(this.getControllerId())
&& event.getSourceId().equals(this.getSourceId());
}
@Override
public String getRule() {
return text.substring(0, 1).toUpperCase(Locale.ENGLISH) + text.substring(1) + '.';
}
public String getText() {
return text;
}
@Override
public ReflexiveTriggeredAbility copy() {
return new ReflexiveTriggeredAbility(this);
}
}

View file

@ -1,38 +1,38 @@
package mage.abilities.condition;
import java.util.ArrayList;
import java.util.Arrays;
import mage.abilities.Ability;
import mage.game.Game;
/**
* Combines conditions to one compound conditon, one condition must be
* true to return true for the compound condtion.
*
* @author emerald000
*/
public class OrCondition implements Condition {
private final ArrayList<Condition> conditions = new ArrayList<>();
private final String text;
public OrCondition(Condition... conditions) {
this("", conditions);
}
public OrCondition(String text, Condition... conditions) {
this.conditions.addAll(Arrays.asList(conditions));
this.text = text;
}
@Override
public boolean apply(Game game, Ability source) {
return conditions.stream().anyMatch(condition -> condition.apply(game, source));
}
@Override
public String toString() {
return text;
}
}
package mage.abilities.condition;
import java.util.ArrayList;
import java.util.Arrays;
import mage.abilities.Ability;
import mage.game.Game;
/**
* Combines conditions to one compound conditon, one condition must be
* true to return true for the compound condtion.
*
* @author emerald000
*/
public class OrCondition implements Condition {
private final ArrayList<Condition> conditions = new ArrayList<>();
private final String text;
public OrCondition(Condition... conditions) {
this("", conditions);
}
public OrCondition(String text, Condition... conditions) {
this.conditions.addAll(Arrays.asList(conditions));
this.text = text;
}
@Override
public boolean apply(Game game, Ability source) {
return conditions.stream().anyMatch(condition -> condition.apply(game, source));
}
@Override
public String toString() {
return text;
}
}

View file

@ -0,0 +1,33 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.util.UUID;
/**
* Source must be attached to permanent
*
* @author JayDi85
*/
public class AttachedToPermanentCondition implements Condition {
final UUID permanentId;
public AttachedToPermanentCondition(UUID permanentId) {
this.permanentId = permanentId;
}
@Override
public boolean apply(Game game, Ability source) {
Permanent attachment = game.getPermanent(source.getSourceId());
Permanent permanent = game.getPermanent(this.permanentId);
if (attachment != null && permanent != null) {
return permanent.getAttachments().contains(attachment.getId());
}
return false;
}
}

View file

@ -1,24 +1,24 @@
package mage.abilities.condition.common;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.watchers.common.AttackedThisTurnWatcher;
/**
*
* @author LevelX2
*/
public enum AttackedThisTurnSourceCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId());
AttackedThisTurnWatcher watcher = game.getState().getWatcher(AttackedThisTurnWatcher.class);
return sourcePermanent != null && watcher.getAttackedThisTurnCreatures().contains(new MageObjectReference(sourcePermanent, game));
}
}
package mage.abilities.condition.common;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.watchers.common.AttackedThisTurnWatcher;
/**
*
* @author LevelX2
*/
public enum AttackedThisTurnSourceCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId());
AttackedThisTurnWatcher watcher = game.getState().getWatcher(AttackedThisTurnWatcher.class);
return sourcePermanent != null && watcher.getAttackedThisTurnCreatures().contains(new MageObjectReference(sourcePermanent, game));
}
}

View file

@ -17,7 +17,7 @@ public enum BuybackCondition implements Condition {
public boolean apply(Game game, Ability source) {
Card card = game.getCard(source.getSourceId());
if (card != null) {
return card.getAbilities().stream()
return card.getAbilities(game).stream()
.filter(a -> a instanceof BuybackAbility)
.anyMatch(a -> ((BuybackAbility) a).isBuybackActivated(game));
}

View file

@ -13,7 +13,7 @@ import mage.watchers.common.CastFromHandWatcher;
*
* @author Loki
*/
public enum CastFromHandSourceCondition implements Condition {
public enum CastFromHandSourcePermanentCondition implements Condition {
instance;

View file

@ -1,26 +1,26 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.designations.DesignationType;
import mage.game.Game;
/**
*
* @author LvelX2
*/
public enum CitysBlessingCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
return game.getPlayer(source.getControllerId()).hasDesignation(DesignationType.CITYS_BLESSING);
}
@Override
public String toString() {
return "you have the city's blessing";
}
}
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.designations.DesignationType;
import mage.game.Game;
/**
*
* @author LvelX2
*/
public enum CitysBlessingCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
return game.getPlayer(source.getControllerId()).hasDesignation(DesignationType.CITYS_BLESSING);
}
@Override
public String toString() {
return "you have the city's blessing";
}
}

View file

@ -0,0 +1,36 @@
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 java.util.Collection;
import java.util.Objects;
/**
* @author TheElk801
*/
public enum ControlACommanderCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
return game.getPlayerList()
.stream()
.map(game::getPlayer)
.filter(Objects::nonNull)
.map(player -> game.getCommandersIds(player, CommanderCardType.COMMANDER_OR_OATHBREAKER))
.flatMap(Collection::stream)
.map(game::getPermanent)
.filter(Objects::nonNull)
.map(Permanent::getControllerId)
.anyMatch(source.getControllerId()::equals);
}
@Override
public String toString() {
return "If you control a commander";
}
}

View file

@ -1,88 +1,88 @@
/*
* 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.condition.common;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.constants.TargetController;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game;
/**
*
* @author Styxo
*/
public class CreatureCountCondition implements Condition {
private FilterCreaturePermanent filter;
private int creatureCount;
private TargetController targetController;
public CreatureCountCondition(FilterCreaturePermanent filter, int creatureCount, TargetController targetController) {
this.filter = filter;
this.creatureCount = creatureCount;
this.targetController = targetController;
}
public CreatureCountCondition(int creatureCount, TargetController targetController) {
this.filter = new FilterCreaturePermanent();
this.creatureCount = creatureCount;
this.targetController = targetController;
}
@Override
public boolean apply(Game game, Ability source) {
switch (targetController) {
case YOU:
return game.getBattlefield().countAll(filter, source.getControllerId(), game) == creatureCount;
case OPPONENT:
for (UUID opponent : game.getOpponents(source.getControllerId())) {
if (game.getBattlefield().countAll(filter, opponent, game) != creatureCount) {
return false;
}
}
return true;
case ANY:
return game.getBattlefield().count(filter, source.getSourceId(), source.getControllerId(), game) == creatureCount;
default:
throw new UnsupportedOperationException("Value for targetController not supported: " + targetController.toString());
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
switch (targetController) {
case YOU:
sb.append("you");
break;
case OPPONENT:
sb.append("your opponents");
break;
case ANY:
sb.append("if ");
sb.append(creatureCount);
sb.append(' ');
sb.append(filter.getMessage());
sb.append(" are on the battlefield");
return sb.toString();
}
sb.append(" control");
if (creatureCount == 0) {
sb.append(" no ");
} else {
sb.append(" exactly ");
sb.append(creatureCount);
sb.append(' ');
}
sb.append(filter.getMessage());
sb.append(creatureCount != 1 ? "s" : "");
return sb.toString();
}
}
/*
* 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.condition.common;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.constants.TargetController;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game;
/**
*
* @author Styxo
*/
public class CreatureCountCondition implements Condition {
private FilterCreaturePermanent filter;
private int creatureCount;
private TargetController targetController;
public CreatureCountCondition(FilterCreaturePermanent filter, int creatureCount, TargetController targetController) {
this.filter = filter;
this.creatureCount = creatureCount;
this.targetController = targetController;
}
public CreatureCountCondition(int creatureCount, TargetController targetController) {
this.filter = new FilterCreaturePermanent();
this.creatureCount = creatureCount;
this.targetController = targetController;
}
@Override
public boolean apply(Game game, Ability source) {
switch (targetController) {
case YOU:
return game.getBattlefield().countAll(filter, source.getControllerId(), game) == creatureCount;
case OPPONENT:
for (UUID opponent : game.getOpponents(source.getControllerId())) {
if (game.getBattlefield().countAll(filter, opponent, game) != creatureCount) {
return false;
}
}
return true;
case ANY:
return game.getBattlefield().count(filter, source.getSourceId(), source.getControllerId(), game) == creatureCount;
default:
throw new UnsupportedOperationException("Value for targetController not supported: " + targetController.toString());
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
switch (targetController) {
case YOU:
sb.append("you");
break;
case OPPONENT:
sb.append("your opponents");
break;
case ANY:
sb.append("if ");
sb.append(creatureCount);
sb.append(' ');
sb.append(filter.getMessage());
sb.append(" are on the battlefield");
return sb.toString();
}
sb.append(" control");
if (creatureCount == 0) {
sb.append(" no ");
} else {
sb.append(" exactly ");
sb.append(creatureCount);
sb.append(' ');
}
sb.append(filter.getMessage());
sb.append(creatureCount != 1 ? "s" : "");
return sb.toString();
}
}

View file

@ -19,9 +19,9 @@ public enum DashedCondition implements Condition {
public boolean apply(Game game, Ability source) {
Card card = game.getCard(source.getSourceId());
if (card != null) {
return card.getAbilities().stream()
return card.getAbilities(game).stream()
.filter(a -> a instanceof DashAbility)
.anyMatch(d -> ((DashAbility)d).isActivated(source, game));
.anyMatch(d -> ((DashAbility) d).isActivated(source, game));
}
return false;

View file

@ -22,7 +22,7 @@ public enum EvokedCondition implements Condition {
public boolean apply(Game game, Ability source) {
Card card = game.getCard(source.getSourceId());
if (card != null) {
return card.getAbilities().stream()
return card.getAbilities(game).stream()
.filter(ab -> ab instanceof EvokeAbility)
.anyMatch(evoke -> ((EvokeAbility) evoke).isActivated(source, game));
}

View file

@ -1,29 +1,29 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.game.Game;
import mage.watchers.common.LifeLossOtherFromCombatWatcher;
/**
* Describes condition when an opponent has been dealt any amount of non-combat
* damage
*
* @author Styxo
*/
public enum HateCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
LifeLossOtherFromCombatWatcher watcher = game.getState().getWatcher(LifeLossOtherFromCombatWatcher.class);
return watcher != null && watcher.opponentLostLifeOtherFromCombat(source.getControllerId(), game);
}
@Override
public String toString() {
return "if an opponent lost life from source other than combat damage this turn";
}
}
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.game.Game;
import mage.watchers.common.LifeLossOtherFromCombatWatcher;
/**
* Describes condition when an opponent has been dealt any amount of non-combat
* damage
*
* @author Styxo
*/
public enum HateCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
LifeLossOtherFromCombatWatcher watcher = game.getState().getWatcher(LifeLossOtherFromCombatWatcher.class);
return watcher != null && watcher.opponentLostLifeOtherFromCombat(source.getControllerId(), game);
}
@Override
public String toString() {
return "if an opponent lost life from source other than combat damage this turn";
}
}

View file

@ -1,28 +1,28 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.game.Game;
import mage.watchers.WatcherUtils;
import mage.watchers.common.PlayerLostLifeWatcher;
/**
*
* @author LevelX
*/
public enum LiveLostLastTurnCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
PlayerLostLifeWatcher watcher = game.getState().getWatcher(PlayerLostLifeWatcher.class);
if (watcher != null) {
return watcher.getLifeLostLastTurn(source.getControllerId()) > 0;
} else {
WatcherUtils.logMissingWatcher(game, source, PlayerLostLifeWatcher.class, this.getClass());
}
return false;
}
}
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.game.Game;
import mage.watchers.WatcherUtils;
import mage.watchers.common.PlayerLostLifeWatcher;
/**
*
* @author LevelX
*/
public enum LiveLostLastTurnCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
PlayerLostLifeWatcher watcher = game.getState().getWatcher(PlayerLostLifeWatcher.class);
if (watcher != null) {
return watcher.getLifeLostLastTurn(source.getControllerId()) > 0;
} else {
WatcherUtils.logMissingWatcher(game, source, PlayerLostLifeWatcher.class, this.getClass());
}
return false;
}
}

View file

@ -22,9 +22,9 @@ import java.util.UUID;
*/
public class OathbreakerOnBattlefieldCondition implements Condition {
private UUID playerId;
private FilterControlledPermanent filter;
private String compatibleNames;
private final UUID playerId;
private final FilterControlledPermanent filter;
private final String compatibleNames;
public OathbreakerOnBattlefieldCondition(Game game, UUID playerId, UUID signatureSpellId, Set<UUID> oathbreakersToSearch) {
this.playerId = playerId;
@ -35,17 +35,17 @@ public class OathbreakerOnBattlefieldCondition implements Condition {
// spell can be casted by any compatible oathbreakers
List<PermanentIdPredicate> compatibleList = new ArrayList<>();
List<String> compatibleNames = new ArrayList<>();
List<String> compatibleNamesList = 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());
compatibleNamesList.add(commander.getName());
}
}
}
this.compatibleNames = String.join("; ", compatibleNames);
this.compatibleNames = String.join("; ", compatibleNamesList);
if (compatibleList.isEmpty()) {
// random id to disable condition

View file

@ -0,0 +1,36 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.game.Game;
import mage.players.Player;
import java.util.UUID;
/**
* @author JayDi85
*/
public enum OpponentHasNoCardsInHandCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player != null) {
for (UUID playerId : game.getOpponents(source.getControllerId())) {
Player opponent = game.getPlayer(playerId);
if (opponent != null && opponent.getHand().isEmpty()) {
return true;
}
}
}
return false;
}
@Override
public String toString() {
return "an opponent has no cards in hand";
}
}

View file

@ -1,16 +1,16 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.abilities.keyword.ProwlAbility;
import mage.cards.Card;
import mage.constants.SubType;
import mage.game.Game;
import mage.watchers.common.ProwlWatcher;
/**
* Checks if a the spell was cast with the alternate prowl costs
* Is it able to activate prowl cost (damage was made)
*
* @author LevelX2
* @author JayDi85
*/
public enum ProwlCondition implements Condition {
@ -18,22 +18,15 @@ public enum ProwlCondition implements Condition {
@Override
public boolean apply(Game game, Ability source) {
ProwlWatcher watcher = game.getState().getWatcher(ProwlWatcher.class);
Card card = game.getCard(source.getSourceId());
if (card != null) {
for (Ability ability : card.getAbilities()) {
if (ability instanceof ProwlAbility) {
if (((ProwlAbility) ability).isActivated(source, game)) {
return true;
}
if (watcher != null && card != null) {
for (SubType subtype : card.getSubtype(game)) {
if (watcher.hasSubtypeMadeCombatDamage(source.getControllerId(), subtype)) {
return true;
}
}
}
return false;
}
@Override
public String toString() {
return "{source}'s prowl cost was paid";
}
}

View file

@ -0,0 +1,38 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.abilities.keyword.ProwlAbility;
import mage.cards.Card;
import mage.game.Game;
/**
* Checks if a the spell was cast with the alternate prowl costs
*
* @author LevelX2
*/
public enum ProwlCostWasPaidCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
Card card = game.getCard(source.getSourceId());
if (card != null) {
for (Ability ability : card.getAbilities()) {
if (ability instanceof ProwlAbility) {
if (((ProwlAbility) ability).isActivated(source, game)) {
return true;
}
}
}
}
return false;
}
@Override
public String toString() {
return "{source}'s prowl cost was paid";
}
}

View file

@ -21,6 +21,6 @@ public enum RaidCondition implements Condition {
@Override
public String toString() {
return "if you attacked with a creature this turn";
return "if you attacked this turn";
}
}

View file

@ -1,31 +1,43 @@
package mage.abilities.condition.common;
import java.util.Objects;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.watchers.common.LostControlWatcher;
/**
* This condition remembers controller on the first apply.
* As long as this controller keeps unchanged and the source is
* on the battlefield, the condition is true.
* This condition checks if ever since first call of the apply method the
* controller of the source has changed
*
* Monitoring the LOST_CONTROL event has the advantage that also all layered
* effects can correctly check for controller change because comparing old and
* new controller during their apply time does not take into account layered
* change control effects that will be applied later.
*
* This condition needs the LostControlWatcher, so be sure to add it to the card
* that uses the condition.
*
* @author LevelX2
*/
public class SourceOnBattlefieldControlUnchangedCondition implements Condition {
private UUID controllerId;
private Long checkingSince;
private int startingZoneChangeCounter;
@Override
public boolean apply(Game game, Ability source) {
if (controllerId == null) {
controllerId = source.getControllerId();
if (checkingSince == null) {
checkingSince = System.currentTimeMillis() - 1;
startingZoneChangeCounter = game.getState().getZoneChangeCounter(source.getSourceId());
}
Permanent permanent = game.getBattlefield().getPermanent(source.getSourceId());
return (permanent != null && Objects.equals(controllerId, source.getControllerId()));
if (game.getState().getZoneChangeCounter(source.getSourceId()) > startingZoneChangeCounter) {
return false;
}
LostControlWatcher watcher = game.getState().getWatcher(LostControlWatcher.class);
if (watcher != null) {
return checkingSince > watcher.getOrderOfLastLostControl(source.getSourceId());
}
throw new UnsupportedOperationException("LostControlWatcher not found!");
}
}

View file

@ -0,0 +1,40 @@
/*
* 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.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.constants.Zone;
import mage.game.Game;
/**
*
* @author LevelX2
*/
public class SourceRemainsInZoneCondition implements Condition {
private final Zone zone;
private int timesChangedZones = -1;
public SourceRemainsInZoneCondition(Zone zone) {
this.zone = zone;
this.timesChangedZones = -1;
}
@Override
public boolean apply(Game game, Ability source) {
if (timesChangedZones == -1) { // Only changed on first execution
timesChangedZones = game.getState().getZoneChangeCounter(source.getSourceId());
}
return (timesChangedZones == game.getState().getZoneChangeCounter(source.getSourceId())
&& zone.equals(game.getState().getZone(source.getSourceId())));
}
@Override
public String toString() {
return "for as long as {this} remains on the " + zone.toString();
}
}

View file

@ -27,10 +27,10 @@ public enum SuspendedCondition implements Condition {
public boolean apply(Game game, Ability source) {
Card card = game.getCard(source.getSourceId());
if (card != null) {
boolean found = card.getAbilities().stream().anyMatch(ability -> ability instanceof SuspendAbility);
boolean found = card.getAbilities(game).containsClass(SuspendAbility.class);
if (!found) {
found = game.getState().getAllOtherAbilities(source.getSourceId()).stream().anyMatch(ability -> ability instanceof SuspendAbility);
found = game.getState().getAllOtherAbilities(source.getSourceId()).containsClass(SuspendAbility.class);
}
if (found) {

View file

@ -1,72 +1,72 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.cards.Card;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.permanent.Permanent;
/**
*
* @author Styxo
*/
public class TargetHasCounterCondition implements Condition {
private final CounterType counterType;
private int amount = 1;
private int from = -1;
private int to;
public TargetHasCounterCondition(CounterType type) {
this.counterType = type;
}
public TargetHasCounterCondition(CounterType type, int amount) {
this.counterType = type;
this.amount = amount;
}
public TargetHasCounterCondition(CounterType type, int from, int to) {
this.counterType = type;
this.from = from;
this.to = to;
}
@Override
@SuppressWarnings("null")
public boolean apply(Game game, Ability source) {
Card card = null;
Permanent permanent = game.getPermanentOrLKIBattlefield(source.getFirstTarget());
if (permanent == null) {
card = game.getCard(source.getFirstTarget());
if (card == null) {
return false;
}
}
if (from != -1) { //range compare
int count;
if (card != null) {
count = card.getCounters(game).getCount(counterType);
} else {
count = permanent.getCounters(game).getCount(counterType);
}
if (to == Integer.MAX_VALUE) {
return count >= from;
}
return count >= from && count <= to;
} else { // single compare (lte)
if (card != null) {
return card.getCounters(game).getCount(counterType) >= amount;
} else {
return permanent.getCounters(game).getCount(counterType) >= amount;
}
}
}
@Override
public String toString() {
return "if it has a " + counterType.getName() + " on it";
}
}
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.cards.Card;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.permanent.Permanent;
/**
*
* @author Styxo
*/
public class TargetHasCounterCondition implements Condition {
private final CounterType counterType;
private int amount = 1;
private int from = -1;
private int to;
public TargetHasCounterCondition(CounterType type) {
this.counterType = type;
}
public TargetHasCounterCondition(CounterType type, int amount) {
this.counterType = type;
this.amount = amount;
}
public TargetHasCounterCondition(CounterType type, int from, int to) {
this.counterType = type;
this.from = from;
this.to = to;
}
@Override
@SuppressWarnings("null")
public boolean apply(Game game, Ability source) {
Card card = null;
Permanent permanent = game.getPermanentOrLKIBattlefield(source.getFirstTarget());
if (permanent == null) {
card = game.getCard(source.getFirstTarget());
if (card == null) {
return false;
}
}
if (from != -1) { //range compare
int count;
if (card != null) {
count = card.getCounters(game).getCount(counterType);
} else {
count = permanent.getCounters(game).getCount(counterType);
}
if (to == Integer.MAX_VALUE) {
return count >= from;
}
return count >= from && count <= to;
} else { // single compare (lte)
if (card != null) {
return card.getCounters(game).getCount(counterType) >= amount;
} else {
return permanent.getCounters(game).getCount(counterType) >= amount;
}
}
}
@Override
public String toString() {
return "if it has a " + counterType.getName() + " on it";
}
}

View file

@ -1,8 +1,8 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.constants.ComparisonType;
import mage.abilities.condition.IntCompareCondition;
import mage.constants.ComparisonType;
import mage.game.Game;
import mage.watchers.common.PlayerGainedLifeWatcher;
@ -27,6 +27,6 @@ public class YouGainedLifeCondition extends IntCompareCondition {
@Override
public String toString() {
return String.format("if you gained %s or more life this turn ", value + 1);
return String.format("if you gained %s or more life this turn", value + 1);
}
}

View file

@ -151,11 +151,11 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
for (AlternativeCost2 alternateCost : alternativeCostsToCheck) {
alternateCost.activate();
for (Iterator it = ((Costs) alternateCost).iterator(); it.hasNext(); ) {
Cost costDeailed = (Cost) it.next();
if (costDeailed instanceof ManaCost) {
ability.getManaCostsToPay().add((ManaCost) costDeailed.copy());
} else {
ability.getCosts().add(costDeailed.copy());
Cost costDetailed = (Cost) it.next();
if (costDetailed instanceof ManaCost) {
ability.getManaCostsToPay().add((ManaCost) costDetailed.copy());
} else if (costDetailed != null) {
ability.getCosts().add(costDetailed.copy());
}
}
}
@ -222,7 +222,7 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
sb.append("pay ");
}
String text = alternativeCost.getText(true);
sb.append(Character.toLowerCase(text.charAt(0)) + text.substring(1));
sb.append(Character.toLowerCase(text.charAt(0))).append(text.substring(1));
}
++numberCosts;
}

View file

@ -8,5 +8,14 @@ import mage.game.Game;
*/
public interface CostAdjuster {
/**
* Must check playable and real cast states.
* Example: if it need stack related info (like real targets) then must check two states (game.inCheckPlayableState):
* 1. In playable state it must check all possible use cases (e.g. allow to reduce on any available target and modes)
* 2. In real cast state it must check current use case (e.g. real selected targets and modes)
*
* @param ability
* @param game
*/
void adjustCosts(Ability ability, Game game);
}

View file

@ -1,58 +1,63 @@
/**
*
* @author jeffwadsworth
*/
package mage.abilities.costs.common;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
import mage.cards.Card;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.Player;
public class CyclingDiscardCost extends CostImpl {
public CyclingDiscardCost() {
}
public CyclingDiscardCost(CyclingDiscardCost cost) {
super(cost);
}
@Override
public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) {
return game.getPlayer(controllerId).getHand().contains(sourceId);
}
@Override
public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana, Cost costToPay) {
Player player = game.getPlayer(controllerId);
if (player != null) {
Card card = player.getHand().get(sourceId, game);
if (card != null) {
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.CYCLE_CARD, card.getId(), card.getId(), card.getOwnerId()));
paid = player.discard(card, null, game);
if (paid) {
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.CYCLED_CARD, card.getId(), card.getId(), card.getOwnerId()));
}
}
}
return paid;
}
@Override
public String getText() {
return "Discard this card";
}
@Override
public CyclingDiscardCost copy() {
return new CyclingDiscardCost(this);
}
}
package mage.abilities.costs.common;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
import mage.cards.Card;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.Player;
import java.util.UUID;
/**
* @author jeffwadsworth
*/
public class CyclingDiscardCost extends CostImpl {
private MageObjectReference cycledCard = null;
public CyclingDiscardCost() {
}
private CyclingDiscardCost(CyclingDiscardCost cost) {
super(cost);
}
@Override
public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) {
return game.getPlayer(controllerId).getHand().contains(sourceId);
}
@Override
public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana, Cost costToPay) {
Player player = game.getPlayer(controllerId);
if (player != null) {
Card card = player.getHand().get(sourceId, game);
if (card != null) {
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.CYCLE_CARD, card.getId(), card.getId(), card.getOwnerId()));
paid = player.discard(card, null, game);
if (paid) {
cycledCard = new MageObjectReference(card, game);
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.CYCLED_CARD, card.getId(), card.getId(), card.getOwnerId()));
}
}
}
return paid;
}
@Override
public String getText() {
return "Discard this card";
}
@Override
public CyclingDiscardCost copy() {
return new CyclingDiscardCost(this);
}
public MageObjectReference getCycledCard() {
return cycledCard;
}
}

View file

@ -1,11 +1,9 @@
package mage.abilities.costs.common;
import mage.filter.FilterCard;
import mage.target.common.TargetCardInHand;
/**
*
* @author magenoxx_at_googlemail.com
*/
public class DiscardCardCost extends DiscardTargetCost {
@ -15,7 +13,7 @@ public class DiscardCardCost extends DiscardTargetCost {
}
public DiscardCardCost(boolean randomDiscard) {
this(new FilterCard(randomDiscard ?"a card at random":"a card"), randomDiscard);
this(new FilterCard(randomDiscard ? "a card at random" : "a card"), randomDiscard);
}
public DiscardCardCost(FilterCard filter) {
@ -23,7 +21,7 @@ public class DiscardCardCost extends DiscardTargetCost {
}
public DiscardCardCost(FilterCard filter, boolean randomDiscard) {
super(new TargetCardInHand(filter), randomDiscard);
super(new TargetCardInHand(filter).withChooseHint("discard cost"), randomDiscard);
}
public DiscardCardCost(final DiscardCardCost cost) {

View file

@ -1,27 +1,23 @@
package mage.abilities.costs.common;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
import mage.cards.Card;
import mage.game.Game;
import mage.players.Player;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public class DiscardHandCost extends CostImpl {
public DiscardHandCost() {
}
public DiscardHandCost(final DiscardHandCost cost) {
private DiscardHandCost(final DiscardHandCost cost) {
super(cost);
}
@ -38,12 +34,11 @@ public class DiscardHandCost extends CostImpl {
@Override
public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana, Cost costToPay) {
Player player = game.getPlayer(controllerId);
if (player != null) {
for (Card card : player.getHand().getCards(game)) {
player.discard(card, ability, game);
}
paid = true;
if (player == null) {
return paid;
}
player.discard(player.getHand(), ability, game);
paid = true;
return paid;
}

View file

@ -1,20 +1,21 @@
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;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.constants.Outcome;
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 BetaSteward_at_googlemail.com
*/
public class DiscardTargetCost extends CostImpl {
@ -50,13 +51,11 @@ public class DiscardTargetCost extends CostImpl {
if (randomDiscard) {
this.cards.addAll(player.discard(amount, true, ability, game).getCards(game));
} else if (targets.choose(Outcome.Discard, controllerId, sourceId, game)) {
for (UUID targetId : targets.get(0).getTargets()) {
Card card = player.getHand().get(targetId, game);
if (card == null) {
return false;
}
player.discard(card, ability, game);
this.cards.add(card);
Cards toDiscard = new CardsImpl();
toDiscard.addAll(targets.get(0).getTargets());
Cards discarded = player.discard(toDiscard, ability, game);
if (!discarded.isEmpty()) {
cards.addAll(discarded.getCards(game));
}
}
paid = cards.size() >= amount;

View file

@ -1,4 +1,3 @@
package mage.abilities.costs.common;
import mage.abilities.Ability;
@ -10,7 +9,6 @@ import mage.players.Player;
import mage.target.common.TargetCardInHand;
/**
*
* @author LevelX2
*/
public class DiscardXTargetCost extends VariableCostImpl {

View file

@ -1,54 +1,54 @@
package mage.abilities.costs.common;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.common.DontUntapInControllersNextUntapStepTargetEffect;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
/**
*
* @author emerald000
*/
public class ExertSourceCost extends CostImpl {
public ExertSourceCost() {
this.text = "Exert {this}";
}
public ExertSourceCost(ExertSourceCost cost) {
super(cost);
}
@Override
public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) {
return true;
}
@Override
public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana, Cost costToPay) {
Player player = game.getPlayer(controllerId);
Permanent permanent = game.getPermanent(sourceId);
if (player != null && permanent != null) {
game.fireEvent(GameEvent.getEvent(EventType.BECOMES_EXERTED, permanent.getId(), permanent.getId(), permanent.getControllerId()));
ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect("", permanent.getControllerId());
effect.setTargetPointer(new FixedTarget(permanent, game));
game.addEffect(effect, ability);
paid = true;
}
return paid;
}
@Override
public ExertSourceCost copy() {
return new ExertSourceCost(this);
}
}
package mage.abilities.costs.common;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.common.DontUntapInControllersNextUntapStepTargetEffect;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
/**
*
* @author emerald000
*/
public class ExertSourceCost extends CostImpl {
public ExertSourceCost() {
this.text = "Exert {this}";
}
public ExertSourceCost(ExertSourceCost cost) {
super(cost);
}
@Override
public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) {
return true;
}
@Override
public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana, Cost costToPay) {
Player player = game.getPlayer(controllerId);
Permanent permanent = game.getPermanent(sourceId);
if (player != null && permanent != null) {
game.fireEvent(GameEvent.getEvent(EventType.BECOMES_EXERTED, permanent.getId(), permanent.getId(), permanent.getControllerId()));
ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect("", permanent.getControllerId());
effect.setTargetPointer(new FixedTarget(permanent, game));
game.addEffect(effect, ability);
paid = true;
}
return paid;
}
@Override
public ExertSourceCost copy() {
return new ExertSourceCost(this);
}
}

View file

@ -36,7 +36,9 @@ public class ExileFromGraveCost extends CostImpl {
+ CardUtil.numberToText(target.getMaxNumberOfTargets()))
+ ' ' + target.getTargetName();
} else {
this.text = "Exile " + target.getTargetName();
this.text = "Exile "
+ (target.getTargetName().startsWith("card ") ? "a ":"")
+ target.getTargetName();
}
if (!this.text.endsWith(" from your graveyard")) {
this.text = this.text + " from your graveyard";

View file

@ -17,7 +17,7 @@ import mage.players.Player;
public class ExileSourceFromGraveCost extends CostImpl {
public ExileSourceFromGraveCost() {
this.text = "Exile this card from your graveyard";
this.text = "Exile {this} from your graveyard";
}
public ExileSourceFromGraveCost(ExileSourceFromGraveCost cost) {

View file

@ -40,7 +40,7 @@ public class PayLifeCost extends CostImpl {
//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()) {
if (lifeToPayAmount > 0 && !game.getPlayer(controllerId).canPayLifeCost(ability)) {
return false;
}
return game.getPlayer(controllerId).getLife() >= lifeToPayAmount || lifeToPayAmount == 0;

View file

@ -42,7 +42,7 @@ public class PayVariableLifeCost extends VariableCostImpl {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
// Paying 0 life is not considered paying any life, so paying 0 is still allowed
if (game.getPlayer(source.getControllerId()).canPayLifeCost()) {
if (game.getPlayer(source.getControllerId()).canPayLifeCost(source)) {
maxValue = controller.getLife();
}
}

View file

@ -1,60 +1,60 @@
/*
* 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.costs.common;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
import mage.cards.Card;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetCardInHand;
import java.util.UUID;
/**
* @author jeffwadsworth
*/
public class PutCardFromHandOnTopOfLibraryCost extends CostImpl {
public PutCardFromHandOnTopOfLibraryCost() {
this.text = "Put a card from your hand on top of your library";
}
public PutCardFromHandOnTopOfLibraryCost(PutCardFromHandOnTopOfLibraryCost cost) {
super(cost);
}
@Override
public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana, Cost costToPay) {
Player controller = game.getPlayer(controllerId);
TargetCardInHand targetCardInHand = new TargetCardInHand();
targetCardInHand.setRequired(false);
Card card;
if (targetCardInHand.canChoose(controllerId, game)
&& controller.choose(Outcome.PreventDamage, targetCardInHand, sourceId, game)) {
card = game.getCard(targetCardInHand.getFirstTarget());
paid = card != null && controller.moveCardToLibraryWithInfo(card, sourceId, game, Zone.HAND, true, true);
}
return paid;
}
@Override
public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) {
Player controller = game.getPlayer(controllerId);
return (controller != null
&& !controller.getHand().isEmpty());
}
@Override
public PutCardFromHandOnTopOfLibraryCost copy() {
return new PutCardFromHandOnTopOfLibraryCost(this);
}
}
/*
* 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.costs.common;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
import mage.cards.Card;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetCardInHand;
import java.util.UUID;
/**
* @author jeffwadsworth
*/
public class PutCardFromHandOnTopOfLibraryCost extends CostImpl {
public PutCardFromHandOnTopOfLibraryCost() {
this.text = "Put a card from your hand on top of your library";
}
public PutCardFromHandOnTopOfLibraryCost(PutCardFromHandOnTopOfLibraryCost cost) {
super(cost);
}
@Override
public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana, Cost costToPay) {
Player controller = game.getPlayer(controllerId);
TargetCardInHand targetCardInHand = new TargetCardInHand();
targetCardInHand.setRequired(false);
Card card;
if (targetCardInHand.canChoose(controllerId, game)
&& controller.choose(Outcome.PreventDamage, targetCardInHand, sourceId, game)) {
card = game.getCard(targetCardInHand.getFirstTarget());
paid = card != null && controller.moveCardToLibraryWithInfo(card, sourceId, game, Zone.HAND, true, true);
}
return paid;
}
@Override
public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) {
Player controller = game.getPlayer(controllerId);
return (controller != null
&& !controller.getHand().isEmpty());
}
@Override
public PutCardFromHandOnTopOfLibraryCost copy() {
return new PutCardFromHandOnTopOfLibraryCost(this);
}
}

View file

@ -1,18 +1,17 @@
package mage.abilities.costs.common;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
import mage.cards.Card;
import mage.constants.Zone;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.UUID;
public class PutTopCardOfYourLibraryToGraveyardCost extends CostImpl {
private final int numberOfCards;
@ -27,7 +26,7 @@ public class PutTopCardOfYourLibraryToGraveyardCost extends CostImpl {
this.text = setText();
}
public PutTopCardOfYourLibraryToGraveyardCost(PutTopCardOfYourLibraryToGraveyardCost cost) {
private PutTopCardOfYourLibraryToGraveyardCost(final PutTopCardOfYourLibraryToGraveyardCost cost) {
super(cost);
this.numberOfCards = cost.numberOfCards;
this.cardsMovedToGraveyard.addAll(cost.getCardsMovedToGraveyard());
@ -38,8 +37,7 @@ public class PutTopCardOfYourLibraryToGraveyardCost extends CostImpl {
Player player = game.getPlayer(controllerId);
if (player != null && player.getLibrary().size() >= numberOfCards) {
paid = true;
this.cardsMovedToGraveyard.addAll(player.getLibrary().getTopCards(game, numberOfCards));
player.moveCards(player.getLibrary().getTopCards(game, numberOfCards), Zone.GRAVEYARD, ability, game);
this.cardsMovedToGraveyard.addAll(player.millCards(numberOfCards, ability, game).getCards(game));
}
return paid;
}
@ -60,13 +58,6 @@ public class PutTopCardOfYourLibraryToGraveyardCost extends CostImpl {
}
private String setText() {
StringBuilder sb = new StringBuilder("Put the top ");
if (numberOfCards == 1) {
sb.append("card");
} else {
sb.append(CardUtil.numberToText(numberOfCards)).append(" cards");
}
sb.append(" of your library into your graveyard");
return sb.toString();
return "mill " + (numberOfCards == 1 ? "a card" : CardUtil.numberToText(numberOfCards) + " cards");
}
}

View file

@ -23,10 +23,10 @@ import java.util.UUID;
*/
public class RemoveCounterCost extends CostImpl {
private TargetPermanent target;
protected TargetPermanent target;
private String name;
private CounterType counterTypeToRemove;
private int countersToRemove;
protected int countersToRemove;
public RemoveCounterCost(TargetPermanent target) {
this(target, null);

View file

@ -26,7 +26,7 @@ public class ReturnToHandChosenControlledPermanentCost extends CostImpl {
target.setNotTarget(true);
this.addTarget(target);
if (target.getMaxNumberOfTargets() > 1 && target.getMaxNumberOfTargets() == target.getNumberOfTargets()) {
this.text = "return " + CardUtil.numberToText(target.getMaxNumberOfTargets()) + ' '
this.text = "Return " + CardUtil.numberToText(target.getMaxNumberOfTargets()) + ' '
+ target.getTargetName()
+ (target.getTargetName().endsWith(" you control") ? "" : " you control")
+ " to their owner's hand";

View file

@ -0,0 +1,25 @@
package mage.abilities.costs.mana;
/**
* Some special AlternateManaPaymentAbility must be restricted to pay before or after mana abilities.
* Game logic: if you use special mana ability then normal mana abilities must be restricted and vice versa,
* see Convoke for more info and rules
*
* @author JayDi85
*/
public enum ActivationManaAbilityStep {
BEFORE(0), // assist
NORMAL(1), // all activated mana abilities
AFTER(2); // convoke, delve, improvise
private final int stepOrder;
ActivationManaAbilityStep(int stepOrder) {
this.stepOrder = stepOrder;
}
public int getStepOrder() {
return stepOrder;
}
}

View file

@ -1,18 +1,17 @@
package mage.abilities.costs.mana;
import mage.abilities.Ability;
import mage.abilities.mana.ManaOptions;
import mage.game.Game;
/**
* Interface for abilities that allow the player to pay mana costs of a spell in alternate ways.
* For the payment SpecialActions are used.
*
* <p>
* Example of such an alternate payment ability: {@link mage.abilities.keyword.DelveAbility}
*
* @author LevelX2
* @author LevelX2, JayDi85
*/
@FunctionalInterface
public interface AlternateManaPaymentAbility {
/**
* Adds the special action to the state, that allows the user to do the alternate mana payment
@ -22,4 +21,21 @@ public interface AlternateManaPaymentAbility {
* @param unpaid unapaid mana costs of the spell
*/
void addSpecialAction(Ability source, Game game, ManaCost unpaid);
/**
* All possible mana payments that can make that ability (uses to find playable abilities)
*
* @param source
* @param game
* @param unpaid
* @return
*/
ManaOptions getManaOptions(Ability source, Game game, ManaCost unpaid);
/**
* Mana payment step where you can use it
*
* @return
*/
ActivationManaAbilityStep useOnActivationManaAbilityStep();
}

View file

@ -120,7 +120,7 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
}
Player player = game.getPlayer(controllerId);
handleKrrikPhyrexianManaCosts(controllerId, ability, game);
handleLikePhyrexianManaCosts(controllerId, ability, game); // e.g. K'rrik, Son of Yawgmoth
if (!player.getManaPool().isForcedToPay()) {
assignPayment(game, ability, player.getManaPool(), this);
}
@ -150,13 +150,16 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
*/
@Override
public boolean payOrRollback(Ability ability, Game game, UUID sourceId, UUID payingPlayerId) {
int bookmark = game.bookmarkState();
handlePhyrexianManaCosts(payingPlayerId, ability, game);
if (pay(ability, game, sourceId, payingPlayerId, false, null)) {
game.removeBookmark(bookmark);
return true;
Player player = game.getPlayer(payingPlayerId);
if (player != null) {
int bookmark = game.bookmarkState();
handlePhyrexianManaCosts(payingPlayerId, ability, game);
if (pay(ability, game, sourceId, payingPlayerId, false, null)) {
game.removeBookmark(bookmark);
return true;
}
player.restoreState(bookmark, ability.getRule(), game);
}
game.restoreState(bookmark, ability.getRule());
return false;
}
@ -170,11 +173,6 @@ 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);
@ -189,7 +187,7 @@ 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) {
private void handleLikePhyrexianManaCosts(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

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