Merge branch 'master' into fix-tapped-for-mana-event

This commit is contained in:
Oleg Agafonov 2019-12-31 12:12:25 +01:00 committed by GitHub
commit 60781604f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
1600 changed files with 42766 additions and 10461 deletions

View file

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

View file

@ -1,5 +1,9 @@
package mage.abilities;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import mage.MageObject;
import mage.Mana;
import mage.abilities.costs.*;
@ -34,11 +38,6 @@ import mage.util.ThreadLocalStringBuilder;
import mage.watchers.Watcher;
import org.apache.log4j.Logger;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
/**
* @author BetaSteward_at_googlemail.com
*/
@ -57,7 +56,7 @@ public abstract class AbilityImpl implements Ability {
protected ManaCosts<ManaCost> manaCostsToPay;
protected Costs<Cost> costs;
protected Costs<Cost> optionalCosts;
protected Modes modes;
protected Modes modes; // access to it by GetModes only (it's can be override by some abilities)
protected Zone zone;
protected String name;
protected AbilityWord abilityWord;
@ -65,11 +64,10 @@ public abstract class AbilityImpl implements Ability {
protected boolean ruleAtTheTop = false;
protected boolean ruleVisible = true;
protected boolean ruleAdditionalCostsVisible = true;
protected boolean costModificationActive = true;
protected boolean activated = false;
protected boolean worksFaceDown = false;
protected int sourceObjectZoneChangeCounter;
protected List<Watcher> watchers = new ArrayList<>();
protected List<Watcher> watchers = new ArrayList<>(); // access to it by GetWatchers only (it's can be override by some abilities)
protected List<Ability> subAbilities = null;
protected boolean canFizzle = true;
protected TargetAdjuster targetAdjuster = null;
@ -101,7 +99,7 @@ public abstract class AbilityImpl implements Ability {
this.manaCostsToPay = ability.manaCostsToPay.copy();
this.costs = ability.costs.copy();
this.optionalCosts = ability.optionalCosts.copy();
for (Watcher watcher : ability.watchers) {
for (Watcher watcher : ability.getWatchers()) {
watchers.add(watcher.copy());
}
@ -115,7 +113,6 @@ public abstract class AbilityImpl implements Ability {
this.ruleAtTheTop = ability.ruleAtTheTop;
this.ruleVisible = ability.ruleVisible;
this.ruleAdditionalCostsVisible = ability.ruleAdditionalCostsVisible;
this.costModificationActive = ability.costModificationActive;
this.worksFaceDown = ability.worksFaceDown;
this.abilityWord = ability.abilityWord;
this.sourceObjectZoneChangeCounter = ability.sourceObjectZoneChangeCounter;
@ -262,8 +259,9 @@ public abstract class AbilityImpl implements Ability {
this.getManaCostsToPay().clear();
}
}
if (modes.getAdditionalCost() != null) {
modes.getAdditionalCost().addOptionalAdditionalModeCosts(this, game);
if (getModes().getAdditionalCost() != null) {
getModes().getAdditionalCost().addOptionalAdditionalModeCosts(this, game);
}
// 20130201 - 601.2b
// If the spell has alternative or additional costs that will be paid as it's being cast such
@ -312,12 +310,12 @@ public abstract class AbilityImpl implements Ability {
// each target the spell requires. A spell may require some targets only if an alternative or
// additional cost (such as a buyback or kicker cost), or a particular mode, was chosen for it;
// otherwise, the spell is cast as though it did not require those targets. If the spell has a
// variable number of targets, the player announces how many targets he or she will choose before
// he or she announces those targets. The same target can't be chosen multiple times for any one
// variable number of targets, the player announces how many targets they will choose before
// they announce those targets. The same target can't be chosen multiple times for any one
// instance of the word "target" on the spell. However, if the spell uses the word "target" in
// multiple places, the same object, player, or zone can be chosen once for each instance of the
// word "target" (as long as it fits the targeting criteria). If any effects say that an object
// or player must be chosen as a target, the player chooses targets so that he or she obeys the
// or player must be chosen as a target, the player chooses targets so that they obey the
// maximum possible number of such effects without violating any rules or effects that say that
// an object or player can't be chosen as a target. The chosen players, objects, and/or zones
// each become a target of that spell. (Any abilities that trigger when those players, objects,
@ -354,30 +352,9 @@ public abstract class AbilityImpl implements Ability {
}
//20101001 - 601.2e
if (costModificationActive) {
// TODO: replace all AdjustingSourceCosts abilities to continuus effect, see Affinity example
//20100716 - 601.2e
if (sourceObject != null) {
sourceObject.adjustCosts(this, game);
if (sourceObject instanceof Card) {
for (Ability ability : ((Card) sourceObject).getAbilities(game)) {
if (ability instanceof AdjustingSourceCosts) {
((AdjustingSourceCosts) ability).adjustCosts(this, game);
}
}
} else {
for (Ability ability : sourceObject.getAbilities()) {
if (ability instanceof AdjustingSourceCosts) {
((AdjustingSourceCosts) ability).adjustCosts(this, game);
}
}
}
}
if (sourceObject != null) {
sourceObject.adjustCosts(this, game); // still needed
game.getContinuousEffects().costModification(this, game);
} else {
costModificationActive = true;
}
UUID activatorId = controllerId;
@ -519,14 +496,14 @@ public abstract class AbilityImpl implements Ability {
/**
* 601.2b If a cost that will be paid as the spell is being cast includes
* Phyrexian mana symbols, the player announces whether he or she intends to
* pay 2 life or the corresponding colored mana cost for each of those
* symbols.
* Phyrexian mana symbols, the player announces whether they intend to pay 2
* life or the corresponding colored mana cost for each of those symbols.
*/
private void handlePhyrexianManaCosts(Game game, UUID sourceId, Player controller) {
Iterator<ManaCost> costIterator = manaCostsToPay.iterator();
while (costIterator.hasNext()) {
ManaCost cost = costIterator.next();
if (cost instanceof PhyrexianManaCost) {
PhyrexianManaCost phyrexianManaCost = (PhyrexianManaCost) cost;
PayLifeCost payLifeCost = new PayLifeCost(2);
@ -633,7 +610,7 @@ public abstract class AbilityImpl implements Ability {
@Override
public void setControllerId(UUID controllerId) {
this.controllerId = controllerId;
for (Watcher watcher : watchers) {
for (Watcher watcher : getWatchers()) {
watcher.setControllerId(controllerId);
}
@ -661,7 +638,7 @@ public abstract class AbilityImpl implements Ability {
subAbility.setSourceId(sourceId);
}
}
for (Watcher watcher : watchers) {
for (Watcher watcher : getWatchers()) {
watcher.setSourceId(sourceId);
}
@ -734,7 +711,7 @@ public abstract class AbilityImpl implements Ability {
watcher.setSourceId(this.sourceId);
watcher.setControllerId(this.controllerId);
watchers.add(watcher);
getWatchers().add(watcher);
}
@Override
@ -866,7 +843,7 @@ public abstract class AbilityImpl implements Ability {
if (getModes().getMode() != null) {
return getModes().getMode().getTargets();
}
return null;
return new Targets();
}
@Override
@ -1167,11 +1144,6 @@ public abstract class AbilityImpl implements Ability {
return sb.toString();
}
@Override
public void setCostModificationActive(boolean active) {
this.costModificationActive = active;
}
@Override
public boolean getWorksFaceDown() {
return worksFaceDown;

View file

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

View file

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

View file

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

View file

@ -1,5 +1,7 @@
package mage.abilities;
import java.util.Optional;
import java.util.UUID;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.costs.Cost;
@ -15,9 +17,6 @@ import mage.game.events.GameEvent;
import mage.game.stack.Spell;
import mage.players.Player;
import java.util.Optional;
import java.util.UUID;
/**
* @author BetaSteward_at_googlemail.com
*/
@ -63,6 +62,12 @@ public class SpellAbility extends ActivatedAbilityImpl {
*/
public boolean spellCanBeActivatedRegularlyNow(UUID playerId, Game game) {
MageObject object = game.getObject(sourceId);
if (object == null) {
return false;
}
if (game.getState().getValue("PlayFromNotOwnHandZone" + object.getId()) != null) {
return (Boolean) game.getState().getValue("PlayFromNotOwnHandZone" + object.getId()); // card like Chandra, Torch of Defiance +1 loyal ability)
}
return null != game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.CAST_AS_INSTANT, this, playerId, game) // check this first to allow Offering in main phase
|| timing == TimingRule.INSTANT
|| object.hasAbility(FlashAbility.getInstance().getId(), game)
@ -72,7 +77,8 @@ public class SpellAbility extends ActivatedAbilityImpl {
@Override
public ActivationStatus canActivate(UUID playerId, Game game) {
if (this.spellCanBeActivatedRegularlyNow(playerId, game)) {
if (spellAbilityType == SpellAbilityType.SPLIT || spellAbilityType == SpellAbilityType.SPLIT_AFTERMATH) {
if (spellAbilityType == SpellAbilityType.SPLIT
|| spellAbilityType == SpellAbilityType.SPLIT_AFTERMATH) {
return ActivationStatus.getFalse();
}
// fix for Gitaxian Probe and casting opponent's spells
@ -84,7 +90,7 @@ public class SpellAbility extends ActivatedAbilityImpl {
}
}
// Check if rule modifying events prevent to cast the spell in check playable mode
if (this.isCheckPlayableMode()) {
if (game.inCheckPlayableState()) {
if (game.getContinuousEffects().preventedByRuleModification(
GameEvent.getEvent(GameEvent.EventType.CAST_SPELL, this.getId(), this.getSourceId(), playerId), this, game, true)) {
return ActivationStatus.getFalse();
@ -93,7 +99,8 @@ public class SpellAbility extends ActivatedAbilityImpl {
// Alternate spell abilities (Flashback, Overload) can't be cast with no mana to pay option
if (getSpellAbilityType() == SpellAbilityType.BASE_ALTERNATE) {
Player player = game.getPlayer(playerId);
if (player != null && getSourceId().equals(player.getCastSourceIdWithAlternateMana())) {
if (player != null
&& player.getCastSourceIdWithAlternateMana().contains(getSourceId())) {
return ActivationStatus.getFalse();
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,22 +0,0 @@
package mage.abilities.costs;
import mage.abilities.Ability;
import mage.game.Game;
/**
* Interface for abilities that adjust source and only source costs. For the
* cases when some permanent adjusts costs of other spells use
* {@link mage.abilities.effects.CostModificationEffect}.
* <p>
* Example of such source costs adjusting:
* {@link mage.abilities.keyword.AffinityForArtifactsAbility}
*
* @author nantuko
*/
@Deprecated
// replace all AdjustingSourceCosts with "extends CostModificationEffectImpl with zone.ALL" (see Affinity example)
@FunctionalInterface
public interface AdjustingSourceCosts {
void adjustCosts(Ability ability, Game game);
}

View file

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

View file

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

View file

@ -12,6 +12,7 @@ import mage.constants.ColoredManaSymbol;
import mage.constants.ManaType;
import mage.constants.Outcome;
import mage.filter.Filter;
import mage.filter.FilterMana;
import mage.game.Game;
import mage.players.ManaPool;
import mage.players.Player;
@ -117,6 +118,7 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
}
Player player = game.getPlayer(controllerId);
handleKrrikPhyrexianManaCosts(controllerId, ability, game);
if (!player.getManaPool().isForcedToPay()) {
assignPayment(game, ability, player.getManaPool(), this);
}
@ -166,6 +168,11 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
while (manaCostIterator.hasNext()) {
ManaCost manaCost = manaCostIterator.next();
PhyrexianManaCost tempPhyrexianCost = null;
Mana mana = manaCost.getMana();
FilterMana phyrexianColors = player.getPhyrexianColors();
if (manaCost instanceof PhyrexianManaCost) {
PhyrexianManaCost phyrexianManaCost = (PhyrexianManaCost) manaCost;
PayLifeCost payLifeCost = new PayLifeCost(2);
@ -179,6 +186,56 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
tempCosts.pay(source, game, source.getSourceId(), player.getId(), false, null);
}
private void handleKrrikPhyrexianManaCosts(UUID payingPlayerId, Ability source, Game game) {
Player player = game.getPlayer(payingPlayerId);
if (this == null || player == null) {
return; // nothing to be done without any mana costs. prevents NRE from occurring here
}
Iterator<T> manaCostIterator = this.iterator();
Costs<PayLifeCost> tempCosts = new CostsImpl<>();
while (manaCostIterator.hasNext()) {
ManaCost manaCost = manaCostIterator.next();
Mana mana = manaCost.getMana();
PhyrexianManaCost tempPhyrexianCost = null;
FilterMana phyrexianColors = player.getPhyrexianColors();
/* K'rrik, Son of Yawgmoth ability check */
if (phyrexianColors != null) {
int phyrexianEnabledPips = mana.count(phyrexianColors);
if (phyrexianEnabledPips > 0) {
/* find which color mana is in the cost and set it in the temp Phyrexian cost */
if (phyrexianColors.isWhite() && mana.getWhite() > 0) {
tempPhyrexianCost = new PhyrexianManaCost(ColoredManaSymbol.W);
}
else if (phyrexianColors.isBlue() && mana.getBlue() > 0) {
tempPhyrexianCost = new PhyrexianManaCost(ColoredManaSymbol.U);
}
else if (phyrexianColors.isBlack() && mana.getBlack() > 0) {
tempPhyrexianCost = new PhyrexianManaCost(ColoredManaSymbol.B);
}
else if (phyrexianColors.isRed() && mana.getRed() > 0) {
tempPhyrexianCost = new PhyrexianManaCost(ColoredManaSymbol.R);
}
else if (phyrexianColors.isGreen() && mana.getGreen() > 0) {
tempPhyrexianCost = new PhyrexianManaCost(ColoredManaSymbol.G);
}
if (tempPhyrexianCost != null) {
PayLifeCost payLifeCost = new PayLifeCost(2);
if (payLifeCost.canPay(source, source.getSourceId(), player.getId(), game)
&& player.chooseUse(Outcome.LoseLife, "Pay 2 life (using an active ability) instead of " + tempPhyrexianCost.getBaseText() + '?', source, game)) {
manaCostIterator.remove();
tempCosts.add(payLifeCost);
}
}
}
}
}
tempCosts.pay(source, game, source.getSourceId(), player.getId(), false, null);
}
@Override
public ManaCosts<T> getUnpaid() {

View file

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

View file

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

View file

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

View file

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

View file

@ -1,18 +1,18 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package mage.abilities.dynamicvalue.common;
import java.util.ArrayList;
import java.util.Arrays;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.abilities.hint.Hint;
import mage.abilities.hint.ValueHint;
import mage.constants.ColoredManaSymbol;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
/**
* Each colored mana symbol (e.g. {U}) in the mana costs of permanents you
@ -20,37 +20,49 @@ import mage.game.permanent.Permanent;
*
* @author LevelX2
*/
public class DevotionCount implements DynamicValue {
public enum DevotionCount implements DynamicValue {
W(ColoredManaSymbol.W),
U(ColoredManaSymbol.U),
B(ColoredManaSymbol.B),
R(ColoredManaSymbol.R),
G(ColoredManaSymbol.G),
WU(ColoredManaSymbol.W, ColoredManaSymbol.U),
WB(ColoredManaSymbol.W, ColoredManaSymbol.B),
UB(ColoredManaSymbol.U, ColoredManaSymbol.B),
UR(ColoredManaSymbol.U, ColoredManaSymbol.R),
BR(ColoredManaSymbol.B, ColoredManaSymbol.R),
BG(ColoredManaSymbol.B, ColoredManaSymbol.G),
RG(ColoredManaSymbol.R, ColoredManaSymbol.G),
RW(ColoredManaSymbol.R, ColoredManaSymbol.W),
GW(ColoredManaSymbol.G, ColoredManaSymbol.W),
GU(ColoredManaSymbol.G, ColoredManaSymbol.U);
private ArrayList<ColoredManaSymbol> devotionColors = new ArrayList<>();
private final ArrayList<ColoredManaSymbol> devotionColors = new ArrayList<>();
private final Hint hint;
public DevotionCount(ColoredManaSymbol... devotionColor) {
DevotionCount(ColoredManaSymbol... devotionColor) {
this.devotionColors.addAll(Arrays.asList(devotionColor));
}
public DevotionCount(final DevotionCount dynamicValue) {
this.devotionColors = dynamicValue.devotionColors;
this.hint = new ValueHint(this.getMessage().replace("your d", "D"), this);
}
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
int devotion = 0;
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(sourceAbility.getControllerId())) {
for (ManaCost manaCost : permanent.getManaCost()) {
for (ColoredManaSymbol coloredManaSymbol : devotionColors) {
if (manaCost.containsColor(coloredManaSymbol)) {
devotion++;
break; // count each manaCost maximum of one time (Hybrid don't count for multiple colors of devotion)
}
}
}
}
return devotion;
return game.getBattlefield()
.getAllActivePermanents(sourceAbility.getControllerId())
.stream()
.map(MageObject::getManaCost)
.flatMap(Collection::stream)
.mapToInt(this::checkCost)
.sum();
}
private int checkCost(ManaCost manaCost) {
return devotionColors.stream().anyMatch(manaCost::containsColor) ? 1 : 0;
}
@Override
public DevotionCount copy() {
return new DevotionCount(this);
return this;
}
@Override
@ -71,4 +83,8 @@ public class DevotionCount implements DynamicValue {
}
return sb.toString();
}
public Hint getHint() {
return hint;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,14 +1,15 @@
package mage.abilities.effects;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.*;
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect;
import mage.abilities.effects.common.continuous.CommanderReplacementEffect;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.cards.SplitCardHalf;
import mage.cards.*;
import mage.constants.*;
import mage.filter.FilterCard;
import mage.filter.predicate.Predicate;
@ -26,11 +27,6 @@ import mage.players.Player;
import mage.target.common.TargetCardInHand;
import org.apache.log4j.Logger;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
/**
* @author BetaSteward_at_googlemail.com
*/
@ -233,7 +229,9 @@ public class ContinuousEffects implements Serializable {
}
private List<ContinuousEffect> filterLayeredEffects(List<ContinuousEffect> effects, Layer layer) {
return effects.stream().filter(effect -> effect.hasLayer(layer)).collect(Collectors.toList());
return effects.stream()
.filter(effect -> effect.hasLayer(layer))
.collect(Collectors.toList());
}
public Map<RequirementEffect, Set<Ability>> getApplicableRequirementEffects(Permanent permanent, boolean playerRealted, Game game) {
@ -336,7 +334,7 @@ public class ContinuousEffects implements Serializable {
}
// boolean checkLKI = event.getType().equals(EventType.ZONE_CHANGE) || event.getType().equals(EventType.DESTROYED_PERMANENT);
//get all applicable transient Replacement effects
for (Iterator<ReplacementEffect> iterator = replacementEffects.iterator(); iterator.hasNext(); ) {
for (Iterator<ReplacementEffect> iterator = replacementEffects.iterator(); iterator.hasNext();) {
ReplacementEffect effect = iterator.next();
if (!effect.checksEventType(event, game)) {
continue;
@ -369,7 +367,7 @@ public class ContinuousEffects implements Serializable {
}
}
for (Iterator<PreventionEffect> iterator = preventionEffects.iterator(); iterator.hasNext(); ) {
for (Iterator<PreventionEffect> iterator = preventionEffects.iterator(); iterator.hasNext();) {
PreventionEffect effect = iterator.next();
if (!effect.checksEventType(event, game)) {
continue;
@ -508,9 +506,20 @@ public class ContinuousEffects implements Serializable {
UUID idToCheck;
if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof SplitCardHalf) {
idToCheck = ((SplitCardHalf) affectedAbility.getSourceObject(game)).getParentCard().getId();
} else if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof AdventureCardSpell
&& type != AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE
&& type != AsThoughEffectType.CAST_AS_INSTANT) {
// adventure spell uses alternative characteristics for spell/stack
idToCheck = ((AdventureCardSpell) affectedAbility.getSourceObject(game)).getParentCard().getId();
} else {
if (game.getObject(objectId) instanceof SplitCardHalf) {
idToCheck = ((SplitCardHalf) game.getObject(objectId)).getParentCard().getId();
Card card = game.getCard(objectId);
if (card instanceof SplitCardHalf) {
idToCheck = ((SplitCardHalf) card).getParentCard().getId();
} else if (card instanceof AdventureCardSpell
&& type != AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE
&& type != AsThoughEffectType.CAST_AS_INSTANT) {
// adventure spell uses alternative characteristics for spell/stack
idToCheck = ((AdventureCardSpell) card).getParentCard().getId();
} else {
idToCheck = objectId;
}
@ -712,10 +721,10 @@ public class ContinuousEffects implements Serializable {
* Checks if an event won't happen because of an rule modifying effect
*
* @param event
* @param targetAbility ability the event is attached to. can be null.
* @param targetAbility ability the event is attached to. can be null.
* @param game
* @param checkPlayableMode true if the event does not really happen but
* it's checked if the event would be replaced
* it's checked if the event would be replaced
* @return
*/
public boolean preventedByRuleModification(GameEvent event, Ability targetAbility, Game game, boolean checkPlayableMode) {
@ -729,10 +738,7 @@ public class ContinuousEffects implements Serializable {
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
effect.setValue("targetAbility", targetAbility);
if (effect.applies(event, sourceAbility, game)) {
if (targetAbility instanceof ActivatedAbility && ((ActivatedAbility) targetAbility).isCheckPlayableMode()) {
checkPlayableMode = true;
}
if (!checkPlayableMode) {
if (!game.inCheckPlayableState()) {
String message = effect.getInfoMessage(sourceAbility, event, game);
if (message != null && !message.isEmpty()) {
if (effect.sendMessageToUser()) {
@ -762,7 +768,7 @@ public class ContinuousEffects implements Serializable {
do {
Map<ReplacementEffect, Set<Ability>> rEffects = getApplicableReplacementEffects(event, game);
// Remove all consumed effects (ability dependant)
for (Iterator<ReplacementEffect> it1 = rEffects.keySet().iterator(); it1.hasNext(); ) {
for (Iterator<ReplacementEffect> it1 = rEffects.keySet().iterator(); it1.hasNext();) {
ReplacementEffect entry = it1.next();
if (consumed.containsKey(entry.getId()) /*&& !(entry instanceof CommanderReplacementEffect) */) { // 903.9.
Set<UUID> consumedAbilitiesIds = consumed.get(entry.getId());
@ -953,7 +959,7 @@ public class ContinuousEffects implements Serializable {
if (!waitingEffects.isEmpty()) {
// check if waiting effects can be applied now
for (Iterator<Map.Entry<ContinuousEffect, Set<UUID>>> iterator = waitingEffects.entrySet().iterator(); iterator.hasNext(); ) {
for (Iterator<Map.Entry<ContinuousEffect, Set<UUID>>> iterator = waitingEffects.entrySet().iterator(); iterator.hasNext();) {
Map.Entry<ContinuousEffect, Set<UUID>> entry = iterator.next();
if (appliedEffects.containsAll(entry.getValue())) { // all dependent to effects are applied now so apply the effect itself
appliedAbilities = appliedEffectAbilities.get(entry.getKey());
@ -1072,11 +1078,13 @@ public class ContinuousEffects implements Serializable {
private boolean isAbilityStillExists(final Game game, final Ability ability, ContinuousEffect effect) {
final Card card = game.getPermanentOrLKIBattlefield(ability.getSourceId());
if (!(effect instanceof BecomesFaceDownCreatureEffect)) {
if (!(effect instanceof BecomesFaceDownCreatureEffect)
&& (effect != null && !effect.getDuration().equals(Duration.Custom))) { // Custom effects do not depend on the creating permanent
if (card != null) {
return card.getAbilities(game).contains(ability);
}
}
return true;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,7 +7,6 @@ import mage.abilities.Mode;
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
import mage.abilities.common.delayed.AtTheEndOfCombatDelayedTriggeredAbility;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.HasteAbility;
@ -136,6 +135,8 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect {
} else {
permanent = game.getPermanentOrLKIBattlefield(targetId);
}
// can target card or permanent
Card copyFrom;
ApplyToPermanent applier = new EmptyApplyToPermanent();
if (permanent != null) {

View file

@ -50,7 +50,7 @@ public class DrawDiscardTargetEffect extends OneShotEffect {
Player player = game.getPlayer(getTargetPointer().getFirst(game, source));
if (player != null) {
player.drawCards(cardsToDraw, game);
player.discard(cardsToDiscard, source, game);
player.discard(cardsToDiscard, false, source, game);
return true;
}
return false;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,3 @@
package mage.abilities.effects.common;
import mage.constants.Outcome;
@ -34,8 +32,9 @@ public class ReturnToHandAttachedEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) {
Object object = getValue("attachedTo");
if (object instanceof Permanent) {
Card card = game.getCard(((Permanent)object).getId());
if (card != null) {
Card card = game.getCard(((Permanent) object).getId());
if (card != null
&& getValue("zcc").equals(game.getState().getZoneChangeCounter(card.getId()))) { // Necrogenesis, etc.
if (card.moveToZone(Zone.HAND, source.getSourceId(), game, false)) {
return true;
}

View file

@ -65,7 +65,7 @@ public class ReturnToHandChosenPermanentEffect extends OneShotEffect {
sb.append(CardUtil.numberToText(number, "a"));
}
sb.append(' ').append(filter.getMessage());
sb.append(" he or she controls");
sb.append(" they control");
if (number > 1) {
sb.append(" to their owner's hand");
} else {

View file

@ -1,4 +1,3 @@
package mage.abilities.effects.common;
import java.util.ArrayList;
@ -68,9 +67,10 @@ public class ReturnToHandTargetEffect extends OneShotEffect {
for (UUID targetId : targetPointer.getTargets(game, source)) {
MageObject mageObject = game.getObject(targetId);
if (mageObject != null) {
if (mageObject instanceof Spell && mageObject.isCopy()) {
if (mageObject instanceof Spell
&& mageObject.isCopy()) {
copyIds.add(targetId);
} else {
} else if (mageObject instanceof Card) {
cards.add((Card) mageObject);
}
}
@ -83,7 +83,8 @@ public class ReturnToHandTargetEffect extends OneShotEffect {
}
@Override
public String getText(Mode mode) {
public String getText(Mode mode
) {
if (staticText != null && !staticText.isEmpty()) {
return staticText;
}
@ -93,7 +94,8 @@ public class ReturnToHandTargetEffect extends OneShotEffect {
Target target = mode.getTargets().get(0);
StringBuilder sb = new StringBuilder("return ");
if (target.getNumberOfTargets() == 0 && target.getMaxNumberOfTargets() > 0) {
sb.append("up to ").append(CardUtil.numberToText(target.getMaxNumberOfTargets())).append(" target ").append(target.getTargetName()).append(" to their owners' hand");
sb.append("up to ").append(CardUtil.numberToText(target.getMaxNumberOfTargets())).append(" target ")
.append(target.getTargetName()).append(" to their owners' hand");
return sb.toString();
} else {
if (target.getNumberOfTargets() > 1) {

View file

@ -1,4 +1,3 @@
package mage.abilities.effects.common;
import java.util.Set;
@ -73,7 +72,7 @@ public class RevealLibraryPutIntoHandEffect extends OneShotEffect {
Set<Card> cardsList = cards.getCards(game);
Cards cardsToHand = new CardsImpl();
for (Card card : cardsList) {
if (filter.match(card, game)) {
if (filter.match(card, source.getSourceId(), controller.getId(), game)) {
cardsToHand.add(card);
cards.remove(card);
}

View file

@ -1,9 +1,6 @@
package mage.abilities.effects.common;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.StaticValue;
@ -16,8 +13,11 @@ import mage.players.Player;
import mage.target.common.TargetControlledPermanent;
import mage.util.CardUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class SacrificeAllEffect extends OneShotEffect {
@ -86,10 +86,10 @@ public class SacrificeAllEffect extends OneShotEffect {
sb.append("each player sacrifices ");
if (amount.toString().equals("X")) {
sb.append(amount.toString());
} else {
sb.append(CardUtil.numberToText(amount.toString(), "a"));
sb.append(' ');
} else if (!filter.getMessage().startsWith("a ")) {
sb.append(CardUtil.numberToText(amount.toString(), "a "));
}
sb.append(' ');
sb.append(filter.getMessage());
staticText = sb.toString();
}

View file

@ -171,7 +171,7 @@ public class SacrificeOpponentsUnlessPayEffect extends OneShotEffect {
sb.append(' ');
sb.append(filter.getMessage());
sb.append(" unless he or she pays ");
sb.append(" unless they pay ");
if (cost != null) {
sb.append(cost.getText());

View file

@ -1,8 +1,5 @@
package mage.abilities.effects.common;
import java.util.List;
import java.util.Set;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
@ -16,8 +13,10 @@ import mage.game.Game;
import mage.players.Player;
import mage.target.TargetCard;
import java.util.List;
import java.util.Set;
/**
*
* @author Styxo
*/
public class WishEffect extends OneShotEffect {
@ -73,7 +72,7 @@ public class WishEffect extends OneShotEffect {
if (controller.chooseUse(Outcome.Benefit, choiceText, source, game)) {
Cards cards = controller.getSideboard();
List<Card> exile = game.getExile().getAllCards(game);
boolean noTargets = cards.isEmpty() && (alsoFromExile ? exile.isEmpty() : true);
boolean noTargets = cards.isEmpty() && (!alsoFromExile || exile.isEmpty());
if (noTargets) {
game.informPlayer(controller, "You have no cards outside the game" + (alsoFromExile ? " or in exile" : "") + '.');
return true;
@ -96,7 +95,7 @@ public class WishEffect extends OneShotEffect {
return true;
}
TargetCard target = new TargetCard(Zone.OUTSIDE, filter);
TargetCard target = new TargetCard(Zone.ALL, filter);
target.setNotTarget(true);
if (controller.choose(Outcome.Benefit, filteredCards, target, game)) {
Card card = controller.getSideboard().get(target.getFirstTarget(), game);

View file

@ -34,7 +34,7 @@ public class CantBlockUnlessPayManaAllEffect extends PayCostToAttackBlockEffectI
+ " can't block "
+ "unless their controller pays "
+ (manaCosts == null ? "" : manaCosts.getText())
+ " for each blocking creature he or she controls";
+ " for each blocking creature they control";
}
public CantBlockUnlessPayManaAllEffect(CantBlockUnlessPayManaAllEffect effect) {

View file

@ -83,6 +83,7 @@ public class BecomesCreatureTargetEffect extends ContinuousEffectImpl {
if (sublayer == SubLayer.NA) {
if (loseAllAbilities) {
permanent.getSubtype(game).retainAll(SubType.getLandTypes());
permanent.getCardType().clear(); // remove all CardTypes
permanent.getSubtype(game).addAll(token.getSubtype(game));
} else {
for (SubType t : token.getSubtype(game)) {

View file

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

View file

@ -10,7 +10,8 @@ import mage.abilities.effects.ContinuousEffectImpl;
import mage.cards.Card;
import mage.cards.SplitCardHalf;
import mage.constants.*;
import mage.filter.common.FilterNonlandCard;
import mage.filter.FilterCard;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.stack.Spell;
import mage.players.Player;
@ -19,13 +20,26 @@ import java.util.UUID;
public class CastFromHandWithoutPayingManaCostEffect extends ContinuousEffectImpl {
private final FilterCard filter;
private final boolean fromHand;
public CastFromHandWithoutPayingManaCostEffect() {
super(Duration.WhileOnBattlefield, Outcome.Detriment);
staticText = "You may cast nonland cards from your hand without paying their mana costs";
this(StaticFilters.FILTER_CARDS_NON_LAND, true);
}
public CastFromHandWithoutPayingManaCostEffect(final CastFromHandWithoutPayingManaCostEffect effect) {
public CastFromHandWithoutPayingManaCostEffect(FilterCard filter, boolean fromHand) {
super(Duration.WhileOnBattlefield, Outcome.Detriment);
this.filter = filter;
this.fromHand = fromHand;
staticText = "You may cast " + filter.getMessage()
+ (fromHand ? " from your hand" : "")
+ " without paying their mana costs";
}
private CastFromHandWithoutPayingManaCostEffect(final CastFromHandWithoutPayingManaCostEffect effect) {
super(effect);
this.filter = effect.filter;
this.fromHand = effect.fromHand;
}
@Override
@ -36,12 +50,19 @@ public class CastFromHandWithoutPayingManaCostEffect extends ContinuousEffectImp
@Override
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
controller.getAlternativeSourceCosts().add(new AlternativeCostSourceAbility(
null, new CompoundCondition(SourceIsSpellCondition.instance, new IsBeingCastFromHandCondition()), null, new FilterNonlandCard(), true));
return true;
if (controller == null) {
return false;
}
return false;
Condition condition;
if (fromHand) {
condition = new CompoundCondition(SourceIsSpellCondition.instance, IsBeingCastFromHandCondition.instance);
} else {
condition = SourceIsSpellCondition.instance;
}
controller.getAlternativeSourceCosts().add(new AlternativeCostSourceAbility(
null, condition, null, filter, true
));
return true;
}
@Override
@ -55,7 +76,8 @@ public class CastFromHandWithoutPayingManaCostEffect extends ContinuousEffectImp
}
}
class IsBeingCastFromHandCondition implements Condition {
enum IsBeingCastFromHandCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {

View file

@ -2,6 +2,7 @@ package mage.abilities.effects.common.continuous;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
import mage.abilities.Mode;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.constants.Duration;
@ -23,6 +24,7 @@ public class GainControlTargetEffect extends ContinuousEffectImpl {
protected UUID controllingPlayerId;
private boolean fixedControl;
private boolean firstControlChange = true;
public GainControlTargetEffect(Duration duration) {
this(duration, false, null);
@ -77,31 +79,44 @@ public class GainControlTargetEffect extends ContinuousEffectImpl {
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
boolean targetStillExists = false;
boolean oneTargetStillExists = false;
for (UUID permanentId : getTargetPointer().getTargets(game, source)) {
Permanent permanent = game.getPermanent(permanentId);
if (permanent != null) {
targetStillExists = true;
oneTargetStillExists = true;
if (!permanent.isControlledBy(controllingPlayerId)) {
GameEvent loseControlEvent = GameEvent.getEvent(GameEvent.EventType.LOSE_CONTROL, permanentId, source.getId(), permanent.getControllerId());
if (game.replaceEvent(loseControlEvent)) {
return false;
}
boolean controlChanged = false;
if (controllingPlayerId != null) {
permanent.changeControllerId(controllingPlayerId, game);
permanent.getAbilities().setControllerId(controllingPlayerId);
if (permanent.changeControllerId(controllingPlayerId, game)) {
permanent.getAbilities().setControllerId(controllingPlayerId);
controlChanged = true;
}
} else {
permanent.changeControllerId(source.getControllerId(), game);
permanent.getAbilities().setControllerId(source.getControllerId());
if (permanent.changeControllerId(source.getControllerId(), game)) {
permanent.getAbilities().setControllerId(source.getControllerId());
controlChanged = true;
}
}
if (source instanceof ActivatedAbility
&& firstControlChange && !controlChanged) {
// If it was not possible to get control of target permanent by the activated ability the first time it took place
// the effect failed (e.g. because of Guardian Beast) and must be discarded
// This does not handle correctly multiple targets at once
discard();
}
}
}
}
// no valid target exists and the controller is no longer in the game, effect can be discarded
if (!targetStillExists
if (!oneTargetStillExists
|| !controller.isInGame()) {
discard();
}
firstControlChange = false;
return true;
}
discard(); // controller no longer exists

View file

@ -1,7 +1,5 @@
package mage.abilities.effects.common.continuous;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.cards.Card;
@ -11,6 +9,9 @@ import mage.constants.Outcome;
import mage.filter.FilterCard;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
import java.util.UUID;
/**
* @author nantuko
@ -47,16 +48,23 @@ public class PlayTheTopCardEffect extends AsThoughEffectImpl {
@Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
Card cardOnTop = game.getCard(objectId);
if (cardOnTop != null
&& affectedControllerId.equals(source.getControllerId())
&& cardOnTop.isOwnedBy(source.getControllerId())
&& (!cardOnTop.getManaCost().isEmpty() || cardOnTop.isLand())
&& filter.match(cardOnTop, game)) {
Player player = game.getPlayer(cardOnTop.getOwnerId());
if (player != null && cardOnTop.equals(player.getLibrary().getFromTop(game))) {
return true;
}
return applies(objectId, null, source, game, affectedControllerId);
}
@Override
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
Card cardToCheck = game.getCard(objectId);
objectId = CardUtil.getMainCardId(game, objectId); // for split cards
if (cardToCheck != null
&& playerId.equals(source.getControllerId())
&& cardToCheck.isOwnedBy(source.getControllerId())
&& (!cardToCheck.getManaCost().isEmpty() || cardToCheck.isLand())
&& filter.match(cardToCheck, game)) {
Player player = game.getPlayer(cardToCheck.getOwnerId());
UUID needCardID = player.getLibrary().getFromTop(game) == null ? null : player.getLibrary().getFromTop(game).getId();
return objectId.equals(needCardID);
}
return false;
}

View file

@ -1,4 +1,3 @@
package mage.abilities.effects.common.cost;
import mage.abilities.Ability;
@ -7,12 +6,12 @@ import mage.constants.CostModificationType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.filter.FilterCard;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
/**
*
* @author Styxo
*/
public class SourceCostReductionForEachCardInGraveyardEffect extends CostModificationEffectImpl {
@ -20,7 +19,7 @@ public class SourceCostReductionForEachCardInGraveyardEffect extends CostModific
private FilterCard filter;
public SourceCostReductionForEachCardInGraveyardEffect() {
this(new FilterCard());
this(StaticFilters.FILTER_CARD);
}
public SourceCostReductionForEachCardInGraveyardEffect(FilterCard filter) {
@ -29,7 +28,7 @@ public class SourceCostReductionForEachCardInGraveyardEffect extends CostModific
staticText = "{this} costs {1} less to cast for each " + filter.getMessage() + " in your graveyard";
}
SourceCostReductionForEachCardInGraveyardEffect(SourceCostReductionForEachCardInGraveyardEffect effect) {
private SourceCostReductionForEachCardInGraveyardEffect(SourceCostReductionForEachCardInGraveyardEffect effect) {
super(effect);
this.filter = effect.filter.copy();
}

View file

@ -1,4 +1,3 @@
package mage.abilities.effects.common.cost;
import java.util.LinkedHashSet;
@ -6,7 +5,6 @@ import java.util.Set;
import java.util.UUID;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
import mage.abilities.SpellAbility;
import mage.cards.Card;
import mage.choices.ChoiceImpl;
@ -70,11 +68,9 @@ public class SpellsCostReductionAllEffect extends CostModificationEffectImpl {
@Override
public boolean apply(Game game, Ability source, Ability abilityToModify) {
if (upTo) {
if (abilityToModify instanceof ActivatedAbility) {
if (((ActivatedAbility) abilityToModify).isCheckPlayableMode()) {
CardUtil.reduceCost(abilityToModify, this.amount);
return true;
}
if (game.inCheckPlayableState()) {
CardUtil.reduceCost(abilityToModify, this.amount);
return true;
}
Mana mana = abilityToModify.getManaCostsToPay().getMana();
int reduceMax = mana.getGeneric();

View file

@ -1,4 +1,3 @@
package mage.abilities.effects.common.cost;
import java.util.LinkedHashSet;
@ -6,7 +5,6 @@ import java.util.Set;
import mage.MageObject;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
import mage.abilities.SpellAbility;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCosts;
@ -85,7 +83,7 @@ public class SpellsCostReductionControllerEffect extends CostModificationEffectI
return false;
}
int reduce = reduceMax;
if (!(abilityToModify instanceof ActivatedAbility) || !((ActivatedAbility) abilityToModify).isCheckPlayableMode()) {
if (!game.inCheckPlayableState()) {
ChoiceImpl choice = new ChoiceImpl(false);
Set<String> set = new LinkedHashSet<>();
for (int i = 0; i <= amount; i++) {

View file

@ -1,4 +1,3 @@
package mage.abilities.effects.common.counter;
import java.util.ArrayList;
@ -96,7 +95,9 @@ public class AddCountersSourceEffect extends OneShotEffect {
if (permanent == null && source.getAbilityType() == AbilityType.STATIC) {
permanent = game.getPermanentEntering(source.getSourceId());
}
if (permanent != null) {
if (permanent != null
&& (source.getSourceObjectZoneChangeCounter() == 0 // from static ability
|| source.getSourceObjectZoneChangeCounter() == permanent.getZoneChangeCounter(game))) { // prevent to add counters to later source objects
if (counter != null) {
Counter newCounter = counter.copy();
int countersToAdd = amount.calculate(game, source, this);

View file

@ -44,7 +44,7 @@ public class DiscardHandTargetEffect extends OneShotEffect {
for (UUID playerId: getTargetPointer().getTargets(game, source)) {
Player player = game.getPlayer(playerId);
if (player != null) {
player.discard(player.getHand().size(), source, game);
player.discard(player.getHand().size(), false, source, game);
}
}
return true;

View file

@ -1,4 +1,3 @@
package mage.abilities.effects.common.search;
import java.util.List;
@ -87,7 +86,9 @@ public class SearchLibraryPutInPlayEffect extends SearchEffect {
}
sb.append(target.getTargetName()).append(" and put them onto the battlefield");
} else {
sb.append("a ").append(target.getTargetName()).append(" and put it onto the battlefield");
sb.append(target.getTargetName().startsWith("a ") || target.getTargetName().startsWith("an ") ? "" : "a ")
.append(target.getTargetName())
.append(" and put it onto the battlefield");
}
if (tapped) {
sb.append(" tapped");

View file

@ -106,7 +106,7 @@ public class SearchLibraryPutInPlayTargetPlayerEffect extends SearchEffect {
if (forceShuffle) {
sb.append(". Then that player shuffles their library");
} else {
sb.append(". If that player does, he or she shuffles their library");
sb.append(". If that player does, they shuffle their library");
}
staticText = sb.toString();
}

View file

@ -93,7 +93,7 @@ public class FatesealEffect extends OneShotEffect {
private void setText() {
StringBuilder sb = new StringBuilder("fateseal ").append(fatesealNumber);
if (fatesealNumber == 1) {
sb.append(". <i>(To fateseal 1, its controller looks at the top card of an opponent's library, then he or she may put that card on the bottom of that library.)</i>");
sb.append(". <i>(To fateseal 1, its controller looks at the top card of an opponent's library, then they may put that card on the bottom of that library.)</i>");
} else {
sb.append(". <i>(To fateseal ");
sb.append(CardUtil.numberToText(fatesealNumber));

View file

@ -29,6 +29,6 @@ public class AffinityForArtifactsAbility extends SimpleStaticAbility {
@Override
public String getRule() {
return "affinity for artifacts <i>(This spell costs {1} less to cast for each artifact you control.)</i>";
return "Affinity for artifacts <i>(This spell costs {1} less to cast for each artifact you control.)</i>";
}
}

View file

@ -1,4 +1,3 @@
package mage.abilities.keyword;
import mage.abilities.Ability;
@ -16,8 +15,9 @@ import mage.game.events.GameEvent.EventType;
import mage.players.Player;
/**
* If you would draw a card, instead you may put exactly X cards from the top of your library into your graveyard. If
* you do, return this card from your graveyard to your hand. Otherwise, draw a card.
* If you would draw a card, instead you may put exactly X cards from the top of
* your library into your graveyard. If you do, return this card from your
* graveyard to your hand. Otherwise, draw a card.
*
* @author North
*/
@ -42,7 +42,7 @@ class DredgeEffect extends ReplacementEffectImpl {
private final int amount;
public DredgeEffect(int value) {
super(Duration.WhileInGraveyard, Outcome.ReturnToHand);
super(Duration.WhileInGraveyard, Outcome.AIDontUseIt);
this.amount = value;
this.staticText = new StringBuilder("Dredge ").append(Integer.toString(value)).append(" <i>(If you would draw a card, instead you may put exactly ").append(value).append(" card(s) from the top of your library into your graveyard. If you do, return this card from your graveyard to your hand. Otherwise, draw a card.)</i>").toString();
}
@ -68,17 +68,18 @@ class DredgeEffect extends ReplacementEffectImpl {
if (sourceCard == null) {
return false;
}
Player player = game.getPlayer(source.getControllerId());
if (player != null && player.getLibrary().size() >= amount
&& player.chooseUse(outcome, new StringBuilder("Dredge ").append(sourceCard.getLogName()).
append("? (").append(amount).append(" cards go from top of library to graveyard)").toString(), source, game)) {
Player owner = game.getPlayer(game.getCard(source.getSourceId()).getOwnerId());
if (owner != null
&& owner.getLibrary().size() >= amount
&& owner.chooseUse(outcome, new StringBuilder("Dredge ").append(sourceCard.getLogName()).
append("? (").append(amount).append(" cards go from top of library to graveyard)").toString(), source, game)) {
if (!game.isSimulation()) {
game.informPlayers(new StringBuilder(player.getLogName()).append(" dredges ").append(sourceCard.getLogName()).toString());
game.informPlayers(new StringBuilder(owner.getLogName()).append(" dredges ").append(sourceCard.getLogName()).toString());
}
Cards cardsToGrave = new CardsImpl();
cardsToGrave.addAll(player.getLibrary().getTopCards(game, amount));
player.moveCards(cardsToGrave, Zone.GRAVEYARD, source, game);
player.moveCards(sourceCard, Zone.HAND, source, game);
cardsToGrave.addAll(owner.getLibrary().getTopCards(game, amount));
owner.moveCards(cardsToGrave, Zone.GRAVEYARD, source, game);
owner.moveCards(sourceCard, Zone.HAND, source, game);
return true;
}
return false;
@ -89,13 +90,11 @@ class DredgeEffect extends ReplacementEffectImpl {
return event.getType() == EventType.DRAW_CARD;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (event.getPlayerId().equals(source.getControllerId())) {
Player controller = game.getPlayer(source.getControllerId());
return controller != null && controller.getLibrary().size() >= amount;
}
return false;
Player owner = game.getPlayer(game.getCard(source.getSourceId()).getOwnerId());
return (owner != null
&& event.getPlayerId().equals(owner.getId())
&& owner.getLibrary().size() >= amount);
}
}

View file

@ -53,7 +53,8 @@ public class EmergeAbility extends SpellAbility {
if (super.canActivate(playerId, game).canActivate()) {
Player controller = game.getPlayer(this.getControllerId());
if (controller != null) {
for (Permanent creature : game.getBattlefield().getActivePermanents(new FilterControlledCreaturePermanent(), this.getControllerId(), this.getSourceId(), game)) {
for (Permanent creature : game.getBattlefield().getActivePermanents(
new FilterControlledCreaturePermanent(), this.getControllerId(), this.getSourceId(), game)) {
ManaCost costToPay = CardUtil.reduceCost(emergeCost.copy(), creature.getConvertedManaCost());
if (costToPay.canPay(this, this.getSourceId(), this.getControllerId(), game)) {
return ActivationStatus.getTrue();

View file

@ -0,0 +1,40 @@
package mage.abilities.keyword;
import mage.abilities.ActivatedAbilityImpl;
import mage.abilities.costs.Cost;
import mage.abilities.effects.common.AttachEffect;
import mage.constants.Outcome;
import mage.constants.TimingRule;
import mage.constants.Zone;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.target.common.TargetControlledCreaturePermanent;
/**
* @author TheElk801
*/
public class EquipFilterAbility extends ActivatedAbilityImpl {
private final FilterControlledCreaturePermanent filter;
public EquipFilterAbility(FilterControlledCreaturePermanent filter, Cost cost) {
super(Zone.BATTLEFIELD, new AttachEffect(Outcome.AddAbility, "Equip"), cost);
this.addTarget(new TargetControlledCreaturePermanent(filter));
this.filter = filter;
this.timing = TimingRule.SORCERY;
}
private EquipFilterAbility(final EquipFilterAbility ability) {
super(ability);
this.filter = ability.filter;
}
@Override
public EquipFilterAbility copy() {
return new EquipFilterAbility(this);
}
@Override
public String getRule() {
return "Equip " + filter.getMessage() + costs.getText() + manaCosts.getText();
}
}

View file

@ -1,69 +0,0 @@
package mage.abilities.keyword;
import java.util.UUID;
import mage.abilities.ActivatedAbilityImpl;
import mage.abilities.costs.Cost;
import mage.abilities.effects.common.AttachEffect;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.constants.TimingRule;
import mage.constants.Zone;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.mageobject.SupertypePredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.Target;
import mage.target.common.TargetControlledCreaturePermanent;
/**
* @author Rystan
*/
public class EquipLegendaryAbility extends ActivatedAbilityImpl {
private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("legendary creature you control");
static {
filter.add(new SupertypePredicate(SuperType.LEGENDARY));
}
public EquipLegendaryAbility(Outcome outcome, Cost cost) {
this(outcome, cost, new TargetControlledCreaturePermanent(filter));
}
public EquipLegendaryAbility(Outcome outcome, Cost cost, Target target) {
super(Zone.BATTLEFIELD, new AttachEffect(outcome, "Equip"), cost);
this.addTarget(target);
this.timing = TimingRule.SORCERY;
}
@Override
public ActivationStatus canActivate(UUID playerId, Game game) {
ActivationStatus activationStatus = super.canActivate(playerId, game);
if (activationStatus.canActivate()) {
Permanent permanent = game.getPermanent(sourceId);
if (permanent != null && permanent.hasSubtype(SubType.EQUIPMENT, game)) {
return activationStatus;
}
}
return activationStatus;
}
public EquipLegendaryAbility(final EquipLegendaryAbility ability) {
super(ability);
}
@Override
public EquipLegendaryAbility copy() {
return new EquipLegendaryAbility(this);
}
@Override
public String getRule() {
return "Equip legendary creature " + costs.getText()
+ manaCosts.getText() + " (" + manaCosts.getText()
+ ": <i>Attach to target legendary creature you control. Equip only as a sorcery.)</i>";
}
}

View file

@ -0,0 +1,63 @@
package mage.abilities.keyword;
import mage.abilities.SpellAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.common.ExileFromGraveCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.cards.Card;
import mage.constants.SpellAbilityType;
import mage.constants.Zone;
import mage.filter.FilterCard;
import mage.filter.predicate.permanent.AnotherPredicate;
import mage.target.common.TargetCardInYourGraveyard;
import mage.util.CardUtil;
/**
* @author TheElk801
*/
public class EscapeAbility extends SpellAbility {
private static final FilterCard filter = new FilterCard();
static {
filter.add(AnotherPredicate.instance);
}
private final String manaCost;
private final int exileCount;
public EscapeAbility(Card card, String manaCost, int exileCount) {
super(new ManaCostsImpl(manaCost), card.getName() + " with escape");
this.newId();
this.zone = Zone.GRAVEYARD;
this.spellAbilityType = SpellAbilityType.BASE_ALTERNATE;
this.manaCost = manaCost;
this.exileCount = exileCount;
Cost cost = new ExileFromGraveCost(new TargetCardInYourGraveyard(exileCount, filter));
cost.setText("");
this.addCost(cost);
}
private EscapeAbility(final EscapeAbility ability) {
super(ability);
this.manaCost = ability.manaCost;
this.exileCount = ability.exileCount;
}
@Override
public EscapeAbility copy() {
return new EscapeAbility(this);
}
@Override
public String getRule(boolean all) {
return getRule();
}
@Override
public String getRule() {
return "Escape &mdash; " + this.manaCost + ", Exile " + CardUtil.numberToText(this.exileCount) +
" other cards from your graveyard. <i>(You may cast this card from your graveyard for its escape cost.)</i>";
}
}

View file

@ -57,10 +57,13 @@ public class FlashbackAbility extends SpellAbility {
@Override
public ActivationStatus canActivate(UUID playerId, Game game) {
ActivationStatus activationStatus = super.canActivate(playerId, game);
if (activationStatus.canActivate()) {
if (super.canActivate(playerId, game).canActivate()) {
Card card = game.getCard(getSourceId());
if (card != null) {
// Card must be in the graveyard zone
if (game.getState().getZone(card.getId()) != Zone.GRAVEYARD) {
return ActivationStatus.getFalse();
}
// Cards with no Mana Costs cant't be flashbacked (e.g. Ancestral Vision)
if (card.getManaCost().isEmpty()) {
return ActivationStatus.getFalse();
@ -76,7 +79,7 @@ public class FlashbackAbility extends SpellAbility {
return card.getSpellAbility().canActivate(playerId, game);
}
}
return activationStatus;
return ActivationStatus.getFalse();
}
@Override

View file

@ -1,6 +1,6 @@
package mage.abilities.keyword;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.StaticAbility;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
@ -24,8 +24,6 @@ import mage.players.Player;
import mage.target.TargetCard;
import mage.util.CardUtil;
import java.util.UUID;
/**
* @author LevelX2
* <p>
@ -101,11 +99,13 @@ class HideawayExileEffect extends OneShotEffect {
cards.addAll(controller.getLibrary().getTopCards(game, 4));
if (!cards.isEmpty()) {
TargetCard target1 = new TargetCard(Zone.LIBRARY, filter1);
target1.setNotTarget(true);
if (controller.choose(Outcome.Detriment, cards, target1, game)) {
Card card = cards.get(target1.getFirstTarget(), game);
if (card != null) {
cards.remove(card);
controller.moveCardToExileWithInfo(card, CardUtil.getCardExileZoneId(game, source),
UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter());
controller.moveCardToExileWithInfo(card, exileId,
"Hideaway (" + hideawaySource.getIdName() + ')', source.getSourceId(), game, Zone.LIBRARY, false);
card.setFaceDown(true, game);
}
@ -121,7 +121,7 @@ class HideawayLookAtFaceDownCardEffect extends AsThoughEffectImpl {
public HideawayLookAtFaceDownCardEffect() {
super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit);
staticText = "You may look at cards exiled with {this}";
staticText = "You may look at the cards exiled with {this}";
}
private HideawayLookAtFaceDownCardEffect(final HideawayLookAtFaceDownCardEffect effect) {

View file

@ -22,7 +22,7 @@ import java.io.ObjectStreamException;
* 112.7a. Once activated or triggered, an ability exists on the stack independently of its source. Destruction or removal of the source after that time won't affect the ability. Note that some abilities cause a source to do something (for example, "Prodigal Sorcerer deals 1 damage...
* 608.2b. If the spell or ability specifies targets, it checks whether the targets are still legal. A target that's no longer in the zone it was in when it was targeted is illegal. Other changes to the game state may cause a target to no longer be legal; for example, its...
* 608.2g. If an effect requires information from the game (such as the number of creatures on the battlefield), the answer is determined only once, when the effect is applied. If the effect requires information from a specific object, including the source of the ability itself or a...
* 800.4f. If an effect requires information about a specific player, the effect uses the current information about that player if he or she is still in the game; otherwise, the effect uses the last known information about that player before he or she left the game.
* 800.4f. If an effect requires information about a specific player, the effect uses the current information about that player if they are still in the game; otherwise, the effect uses the last known information about that player before they left the game.
* is used to determine whether it had infect.
*
* 702.87e. The infect rules function no matter what zone an object with infect deals damage from.

View file

@ -36,7 +36,7 @@ import mage.players.Player;
* "Madness [cost]" means "If a player would discard this card, that player
* discards it, but may exile it instead of putting it into their graveyard" and
* "When this card is exiled this way, its owner may cast it by paying [cost]
* rather than paying its mana cost. If that player doesn't, he or she puts this
* rather than paying its mana cost. If that player doesn't, they put this
* card into their graveyard.
*
* 702.33b. Casting a spell using its madness ability follows the rules for
@ -133,7 +133,7 @@ class MadnessReplacementEffect extends ReplacementEffectImpl {
}
/**
* Checks for the MADNESS_CARD_EXILED event to ask the player if he wants to
* Checks for the MADNESS_CARD_EXILED event to ask the player if they want to
* cast the card by it's Madness costs. If not, the card goes to the graveyard.
*/
class MadnessTriggeredAbility extends TriggeredAbilityImpl {

View file

@ -26,9 +26,9 @@ import mage.watchers.common.MiracleWatcher;
* this card this way, you may cast it by paying [cost] rather than its mana
* cost."
*
* 702.92b If a player chooses to reveal a card using its miracle ability, he or
* she plays with that card revealed until that card leaves their hand, that
* ability resolves, or that ability otherwise leaves the stack.
* 702.92b If a player chooses to reveal a card using its miracle ability, they
* play with that card revealed until that card leaves their hand, that ability
* resolves, or that ability otherwise leaves the stack.
*
* You can cast a card for its miracle cost only as the miracle triggered
* ability resolves. If you don't want to cast it at that time (or you can't

View file

@ -186,6 +186,7 @@ public class MorphAbility extends StaticAbility implements AlternativeSourceCost
spell.setFaceDown(true, game); // so only the back is visible
if (alternateCosts.canPay(ability, sourceId, controllerId, game)) {
if (player.chooseUse(Outcome.Benefit, "Cast this card as a 2/2 face-down creature for " + getCosts().getText() + " ?", ability, game)) {
game.getState().setValue("MorphAbility" + ability.getSourceId(), "activated"); // Gift of Doom
activateMorph(game);
// change mana costs
ability.getManaCostsToPay().clear();

View file

@ -25,7 +25,7 @@ public class MyriadAbility extends AttacksTriggeredAbility {
super(new MyriadEffect(), false,
"Myriad <i>(Whenever this creature attacks, for each opponent other than the defending player, "
+ "put a token that's a copy of this creature onto the battlefield tapped and attacking "
+ "that player or a planeswalker he or she controls. Exile those tokens at the end of combat.)</i>",
+ "that player or a planeswalker they control. Exile those tokens at the end of combat.)</i>",
SetTargetPointer.PLAYER
);
}
@ -47,7 +47,7 @@ class MyriadEffect extends OneShotEffect {
super(Outcome.Benefit);
this.staticText = "for each opponent other than the defending player, you may put a token "
+ "that's a copy of this creature onto the battlefield tapped and attacking that "
+ "player or a planeswalker he or she controls. "
+ "player or a planeswalker they control. "
+ "Exile the tokens at the end of combat";
}

View file

@ -1,5 +1,6 @@
package mage.abilities.keyword;
import java.util.UUID;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
@ -17,8 +18,6 @@ import mage.target.Target;
import mage.target.common.TargetControlledCreaturePermanent;
import mage.util.CardUtil;
import java.util.UUID;
/**
* 702.46. Offering # 702.46a Offering is a static ability of a card that
* functions in any zone from which the card can be cast. "[Subtype] offering"
@ -121,7 +120,7 @@ class OfferingAsThoughEffect extends AsThoughEffectImpl {
if (game.getBattlefield().count(((OfferingAbility) source).getFilter(), source.getSourceId(), source.getControllerId(), game) > 0) {
if (CardUtil.isCheckPlayableMode(affectedAbility)) {
if (game.inCheckPlayableState()) {
return true;
}
FilterControlledCreaturePermanent filter = ((OfferingAbility) source).getFilter();
@ -130,7 +129,7 @@ class OfferingAsThoughEffect extends AsThoughEffectImpl {
return false;
}
Player player = game.getPlayer(source.getControllerId());
if (player != null && !CardUtil.isCheckPlayableMode(affectedAbility)
if (player != null && !game.inCheckPlayableState()
&& player.chooseUse(Outcome.Benefit, "Offer a " + filter.getMessage() + " to cast " + spellToCast.getName() + '?', source, game)) {
Target target = new TargetControlledCreaturePermanent(1, 1, filter, true);
player.chooseTarget(Outcome.Sacrifice, target, source, game);
@ -193,7 +192,7 @@ class OfferingCostReductionEffect extends CostModificationEffectImpl {
@Override
public boolean applies(Ability abilityToModify, Ability source, Game game) {
if (CardUtil.isCheckPlayableMode(abilityToModify)) { // Cost modifaction does not work correctly for checking available spells
if (game.inCheckPlayableState()) { // Cost modifaction does not work correctly for checking available spells
return false;
}
if (abilityToModify.getId().equals(spellAbilityId) && abilityToModify instanceof SpellAbility) {

View file

@ -1,16 +1,10 @@
package mage.abilities.keyword;
import java.util.Iterator;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
import mage.abilities.costs.OptionalAdditionalCost;
import mage.abilities.costs.OptionalAdditionalCostImpl;
import mage.abilities.costs.OptionalAdditionalSourceCosts;
import mage.abilities.costs.*;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
@ -23,8 +17,9 @@ import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.players.Player;
import java.util.Iterator;
/**
*
* @author LevelX2
*/
public class ReplicateAbility extends StaticAbility implements OptionalAdditionalSourceCosts {
@ -91,12 +86,12 @@ public class ReplicateAbility extends StaticAbility implements OptionalAdditiona
String times = "";
if (additionalCost.isRepeatable()) {
int numActivations = additionalCost.getActivateCount();
times = Integer.toString(numActivations + 1) + (numActivations == 0 ? " time " : " times ");
times = (numActivations + 1) + (numActivations == 0 ? " time " : " times ");
}
if (additionalCost.canPay(ability, sourceId, controllerId, game)
&& player.chooseUse(Outcome.Benefit, new StringBuilder("Pay ").append(times).append(additionalCost.getText(false)).append(" ?").toString(), ability, game)) {
additionalCost.activate();
for (Iterator it = ((Costs) additionalCost).iterator(); it.hasNext();) {
for (Iterator it = ((Costs) additionalCost).iterator(); it.hasNext(); ) {
Cost cost = (Cost) it.next();
if (cost instanceof ManaCostsImpl) {
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
@ -170,7 +165,7 @@ class ReplicateTriggeredAbility extends TriggeredAbilityImpl {
if (card != null) {
for (Ability ability : card.getAbilities(game)) {
if (ability instanceof ReplicateAbility) {
if (((ReplicateAbility) ability).isActivated()) {
if (ability.isActivated()) {
for (Effect effect : this.getEffects()) {
effect.setValue("ReplicateSpell", spell);
effect.setValue("ReplicateCount", ((ReplicateAbility) ability).getActivateCount());
@ -213,7 +208,7 @@ class ReplicateCopyEffect extends OneShotEffect {
if (card != null) {
for (Ability ability : card.getAbilities(game)) {
if (ability instanceof ReplicateAbility) {
if (((ReplicateAbility) ability).isActivated()) {
if (ability.isActivated()) {
((ReplicateAbility) ability).resetReplicate();
}
}

View file

@ -45,8 +45,9 @@ public class SpectacleAbility extends SpellAbility {
@Override
public ActivationStatus canActivate(UUID playerId, Game game) {
if (OpponentsLostLifeCount.instance.calculate(game, playerId) > 0) {
return super.canActivate(playerId, game);
if (OpponentsLostLifeCount.instance.calculate(game, playerId) > 0
&& super.canActivate(playerId, game).canActivate()) {
return ActivationStatus.getTrue();
}
return ActivationStatus.getFalse();
}

View file

@ -51,8 +51,9 @@ public class SurgeAbility extends SpellAbility {
if (player != null) {
for (UUID playerToCheckId : game.getState().getPlayersInRange(playerId, game)) {
if (!player.hasOpponent(playerToCheckId, game)) {
if (watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(playerToCheckId) > 0) {
return super.canActivate(playerId, game);
if (watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(playerToCheckId) > 0
&& super.canActivate(playerId, game).canActivate()) {
return ActivationStatus.getTrue();
}
}
}

View file

@ -92,12 +92,12 @@ import mage.target.targetpointer.FixedTarget;
*
* As the second triggered ability of suspend resolves, if playing the suspended
* card involves an additional cost, the card's owner must pay that cost if
* able. If he or she can't, the card remains removed from the game. If the
* able. If they can't, the card remains removed from the game. If the
* additional cost includes mana, the situation is more complex. If the player
* has enough mana in their mana pool to pay the cost, that player must do so.
* If the player can't possibly pay the cost, the card remains removed from the
* game. However, if the player has the means to produce enough mana to pay the
* cost, then he or she has a choice: The player may play the spell, produce
* cost, then they have a choice: The player may play the spell, produce
* mana, and pay the cost. Or the player may choose to play no mana abilities,
* thus making the card impossible to play because the additional mana can't be
* paid.

View file

@ -1,4 +1,3 @@
package mage.abilities.keyword;
import mage.abilities.Ability;
@ -66,8 +65,8 @@ public class TransformAbility extends SimpleStaticAbility {
for (Ability ability : sourceCard.getAbilities()) {
permanent.addAbility(ability, game);
}
permanent.getPower().setValue(sourceCard.getPower().getValue());
permanent.getToughness().setValue(sourceCard.getToughness().getValue());
permanent.getPower().modifyBaseValue(sourceCard.getPower().getValue());
permanent.getToughness().modifyBaseValue(sourceCard.getToughness().getValue());
permanent.setTransformable(sourceCard.isTransformable());
}
}

View file

@ -54,7 +54,7 @@ public abstract class ActivatedManaAbilityImpl extends ActivatedAbilityImpl impl
&& null == game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.ACTIVATE_AS_INSTANT, this, controllerId, game)) {
return ActivationStatus.getFalse();
}
// check if player is in the process of playing spell costs and he is no longer allowed to use activated mana abilities (e.g. because he started to use improvise)
// check if player is in the process of playing spell costs and they are no longer allowed to use activated mana abilities (e.g. because they started to use improvise)
//20091005 - 605.3a
return new ActivationStatus(costs.canPay(this, sourceId, controllerId, game), null);

View file

@ -0,0 +1,107 @@
package mage.cards;
import mage.abilities.Abilities;
import mage.abilities.AbilitiesImpl;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.constants.CardType;
import mage.constants.Zone;
import mage.game.Game;
import java.util.List;
import java.util.UUID;
/**
* @author TheElk801
*/
public abstract class AdventureCard extends CardImpl {
/* The adventure spell card, i.e. Swift End. */
protected Card spellCard;
public AdventureCard(UUID ownerId, CardSetInfo setInfo, CardType[] types, CardType[] typesSpell, String costs, String adventureName, String costsSpell) {
super(ownerId, setInfo, types, costs);
this.spellCard = new AdventureCardSpellImpl(ownerId, setInfo, adventureName, typesSpell, costsSpell, this);
}
public AdventureCard(AdventureCard card) {
super(card);
this.spellCard = card.getSpellCard().copy();
((AdventureCardSpell) this.spellCard).setParentCard(this);
}
public Card getSpellCard() {
return spellCard;
}
@Override
public void assignNewId() {
super.assignNewId();
spellCard.assignNewId();
}
@Override
public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag, List<UUID> appliedEffects) {
if (super.moveToZone(toZone, sourceId, game, flag, appliedEffects)) {
game.getState().setZone(getSpellCard().getId(), toZone);
return true;
}
return false;
}
@Override
public void setZone(Zone zone, Game game) {
super.setZone(zone, game);
game.setZone(getSpellCard().getId(), zone);
}
@Override
public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, List<UUID> appliedEffects) {
if (super.moveToExile(exileId, name, sourceId, game, appliedEffects)) {
Zone currentZone = game.getState().getZone(getId());
game.getState().setZone(getSpellCard().getId(), currentZone);
return true;
}
return false;
}
@Override
public boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId) {
switch (ability.getSpellAbilityType()) {
case ADVENTURE_SPELL:
return this.getSpellCard().cast(game, fromZone, ability, controllerId);
default:
this.getSpellCard().getSpellAbility().setControllerId(controllerId);
return super.cast(game, fromZone, ability, controllerId);
}
}
@Override
public Abilities<Ability> getAbilities() {
Abilities<Ability> allAbilities = new AbilitiesImpl<>();
allAbilities.addAll(spellCard.getAbilities());
allAbilities.addAll(super.getAbilities());
return allAbilities;
}
@Override
public Abilities<Ability> getAbilities(Game game) {
Abilities<Ability> allAbilities = new AbilitiesImpl<>();
allAbilities.addAll(spellCard.getAbilities(game));
allAbilities.addAll(super.getAbilities(game));
return allAbilities;
}
public Abilities<Ability> getSharedAbilities() {
// abilities without spellcard
return super.getAbilities();
}
@Override
public void setOwnerId(UUID ownerId) {
super.setOwnerId(ownerId);
abilities.setControllerId(ownerId);
spellCard.getAbilities().setControllerId(ownerId);
spellCard.setOwnerId(ownerId);
}
}

View file

@ -0,0 +1,20 @@
/*
* 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.cards;
/**
*
* @author phulin
*/
public interface AdventureCardSpell extends Card {
@Override
AdventureCardSpell copy();
void setParentCard(AdventureCard card);
AdventureCard getParentCard();
}

View file

@ -0,0 +1,154 @@
/*
* 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.cards;
import mage.abilities.Modes;
import mage.abilities.SpellAbility;
import mage.abilities.effects.common.ExileAdventureSpellEffect;
import mage.constants.CardType;
import mage.constants.SpellAbilityType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.game.ExileZone;
import mage.game.Game;
import java.util.List;
import java.util.UUID;
/**
* @author phulin
*/
public class AdventureCardSpellImpl extends CardImpl implements AdventureCardSpell {
private AdventureCard adventureCardParent;
public AdventureCardSpellImpl(UUID ownerId, CardSetInfo setInfo, String adventureName, CardType[] cardTypes, String costs, AdventureCard adventureCardParent) {
super(ownerId, setInfo, cardTypes, costs, SpellAbilityType.ADVENTURE_SPELL);
this.subtype.add(SubType.ADVENTURE);
AdventureCardSpellAbility newSpellAbility = new AdventureCardSpellAbility(getSpellAbility());
newSpellAbility.setName(adventureName, costs);
newSpellAbility.addEffect(ExileAdventureSpellEffect.getInstance());
newSpellAbility.setCardName(adventureName);
this.replaceSpellAbility(newSpellAbility);
spellAbility = newSpellAbility;
this.setName(adventureName);
this.adventureCardParent = adventureCardParent;
}
public AdventureCardSpellImpl(final AdventureCardSpellImpl card) {
super(card);
this.adventureCardParent = card.adventureCardParent;
}
@Override
public UUID getOwnerId() {
return adventureCardParent.getOwnerId();
}
@Override
public String getImageName() {
return adventureCardParent.getImageName();
}
@Override
public String getExpansionSetCode() {
return adventureCardParent.getExpansionSetCode();
}
@Override
public String getCardNumber() {
return adventureCardParent.getCardNumber();
}
@Override
public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag, List<UUID> appliedEffects) {
return adventureCardParent.moveToZone(toZone, sourceId, game, flag, appliedEffects);
}
@Override
public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, List<UUID> appliedEffects) {
return adventureCardParent.moveToExile(exileId, name, sourceId, game, appliedEffects);
}
@Override
public AdventureCard getMainCard() {
return adventureCardParent;
}
@Override
public void setZone(Zone zone, Game game) {
game.setZone(adventureCardParent.getId(), zone);
game.setZone(adventureCardParent.getSpellCard().getId(), zone);
}
@Override
public AdventureCardSpell copy() {
return new AdventureCardSpellImpl(this);
}
@Override
public void setParentCard(AdventureCard card) {
this.adventureCardParent = card;
}
@Override
public AdventureCard getParentCard() {
return this.adventureCardParent;
}
}
class AdventureCardSpellAbility extends SpellAbility {
public AdventureCardSpellAbility(final SpellAbility ability) {
super(ability);
}
@Override
public ActivationStatus canActivate(UUID playerId, Game game) {
ExileZone adventureExileZone = game.getExile().getExileZone(ExileAdventureSpellEffect.adventureExileId(playerId, game));
Card spellCard = game.getCard(this.getSourceId());
if (spellCard instanceof AdventureCardSpell) {
Card card = ((AdventureCardSpell) spellCard).getParentCard();
if (adventureExileZone != null && adventureExileZone.contains(card.getId())) {
return ActivationStatus.getFalse();
}
}
return super.canActivate(playerId, game);
}
public void setName(String name, String costs) {
this.name = "Adventure &mdash; " + name + " " + costs;
}
@Override
public String getRule(boolean all) {
return this.getRule();
}
@Override
public String getRule() {
StringBuilder sbRule = new StringBuilder();
sbRule.append("Adventure &mdash; ");
sbRule.append(this.getCardName());
sbRule.append(" ");
sbRule.append(manaCosts.getText());
sbRule.append(" &mdash; ");
Modes modes = this.getModes();
if (modes.size() <= 1) {
sbRule.append(modes.getMode().getEffects().getTextStartingUpperCase(modes.getMode()));
} else {
sbRule.append(getModes().getText());
}
sbRule.append(" <i>(Then exile this card. You may cast the creature later from exile.)</i>");
return sbRule.toString();
}
@Override
public SpellAbility copy() {
return new AdventureCardSpellAbility(this);
}
}

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