Merge pull request 'master' (#26) from External/mage:master into master
All checks were successful
/ build_release (push) Successful in 14m41s

Reviewed-on: #26
This commit is contained in:
Failure 2025-05-19 22:03:43 -07:00
commit 9a2382cd2c
788 changed files with 29882 additions and 3734 deletions

View file

@ -60,6 +60,7 @@ public enum MageIdentifier {
DemonicEmbraceAlternateCast,
FalcoSparaPactweaverAlternateCast,
HelbruteAlternateCast,
IntoThePitAlternateCast,
MaestrosAscendencyAlternateCast,
NashiMoonSagesScionAlternateCast,
OsteomancerAdeptAlternateCast,
@ -78,7 +79,8 @@ public enum MageIdentifier {
FiresOfMountDoomAlternateCast,
PrimalPrayersAlternateCast,
QuilledGreatwurmAlternateCast,
WickerfolkIndomitableAlternateCast;
WickerfolkIndomitableAlternateCast,
ValgavothTerrorEaterAlternateCast;
/**
* Additional text if there is need to differentiate two very similar effects

View file

@ -92,7 +92,12 @@ public class AbilitiesImpl<T extends Ability> extends ArrayList<T> implements Ab
String rule = ability.getRule();
if (rule != null) {
if (!rule.isEmpty()) {
rules.add(Character.toUpperCase(rule.charAt(0)) + rule.substring(1));
rule = Character.toUpperCase(rule.charAt(0)) + rule.substring(1);
if (ability.getRuleAtTheTop()) {
rules.add(0, rule);
} else {
rules.add(rule);
}
}
} else { // logging so we can still can be made aware of rule problems a card has
String cardName = ((SpellAbility) ability).getCardName();

View file

@ -787,8 +787,8 @@ public abstract class AbilityImpl implements Ability {
xValue = variableManaCost.getAmount();
} else {
// announce by player
xValue = controller.announceXMana(variableManaCost.getMinX(), variableManaCost.getMaxX(),
"Announce the value for " + variableManaCost.getText(), game, this);
xValue = controller.announceX(variableManaCost.getMinX(), variableManaCost.getMaxX(),
"Announce the value for " + variableManaCost.getText(), game, this, true);
}
int amountMana = xValue * variableManaCost.getXInstancesCount();

View file

@ -99,6 +99,11 @@ public interface ActivatedAbility extends Ability {
int getMaxActivationsPerTurn(Game game);
/**
* how many more time can this be activated this turn?
*/
int getMaxMoreActivationsThisTurn(Game game);
ActivatedAbility setTiming(TimingRule timing);
ActivatedAbility setCondition(Condition condition);

View file

@ -215,6 +215,23 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
|| activationInfo.activationCounter < getMaxActivationsPerTurn(game);
}
public int getMaxMoreActivationsThisTurn(Game game) {
if (getMaxActivationsPerTurn(game) == Integer.MAX_VALUE && maxActivationsPerGame == Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
}
ActivationInfo activationInfo = getActivationInfo(game);
if (activationInfo == null) {
return Math.min(maxActivationsPerGame, getMaxActivationsPerTurn(game));
}
if (activationInfo.totalActivations >= maxActivationsPerGame) {
return 0;
}
if (activationInfo.turnNum != game.getTurnNum()) {
return getMaxActivationsPerTurn(game);
}
return Math.max(0, getMaxActivationsPerTurn(game) - activationInfo.activationCounter);
}
@Override
public boolean activate(Game game, Set<MageIdentifier> allowedIdentifiers, boolean noMana) {
if (!hasMoreActivationsThisTurn(game) || !super.activate(game, allowedIdentifiers, noMana)) {

View file

@ -617,18 +617,16 @@ public class Modes extends LinkedHashMap<UUID, Mode> implements Copyable<Modes>
sb.append("<br>");
for (Mode mode : this.values()) {
if (mode.getCost() != null) {
if (mode.getCost() != null && mode.getFlavorWord() == null) {
// for Spree
sb.append("+ ");
sb.append(mode.getCost().getText());
sb.append(" &mdash; ");
} else if (mode.getPawPrintValue() > 0) {
for (int i = 0; i < mode.getPawPrintValue(); ++i) {
sb.append("{P}");
}
sb.append(" &mdash; ");
} else {
sb.append("&bull ");
sb.append("&bull ");
}
sb.append(mode.getEffects().getTextStartingUpperCase(mode));
sb.append("<br>");

View file

@ -29,7 +29,7 @@ public class CounterRemovedFromSourceWhileExiledTriggeredAbility extends Trigger
this.onlyController = onlyController;
setTriggerPhrase("Whenever " + (
onlyController ? ("you remove a " + counterType.getName() + " counter") : ("a " + counterType.getName() + " counter is removed")
) + " from {this} while it's exiled, ");
) + " from this card while it's exiled, ");
}
private CounterRemovedFromSourceWhileExiledTriggeredAbility(final CounterRemovedFromSourceWhileExiledTriggeredAbility ability) {

View file

@ -59,7 +59,7 @@ public class DealsDamageToACreatureTriggeredAbility extends TriggeredAbilityImpl
if (!filter.match(creature, getControllerId(), this, game)) {
return false;
}
this.getEffects().setValue("damagedCreature", game.getPermanent(event.getTargetId()));
if (setTargetPointer) {
this.getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game));
this.getEffects().setValue("damage", event.getAmount());

View file

@ -15,6 +15,8 @@ import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
/**
* Whenever [[filtered permanent]] deals (combat)? damage, [[effect]]
*
* @author xenohedron
*/
public class DealsDamageToAnyTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility<DamagedEvent> {

View file

@ -55,22 +55,16 @@ public class DealsDamageToOpponentTriggeredAbility extends TriggeredAbilityImpl
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (event.getSourceId().equals(this.sourceId)
&& game.getOpponents(this.getControllerId()).contains(event.getTargetId())) {
if (onlyCombat && event instanceof DamagedPlayerEvent) {
DamagedPlayerEvent damageEvent = (DamagedPlayerEvent) event;
if (!damageEvent.isCombatDamage()) {
return false;
}
}
if (setTargetPointer) {
for (Effect effect : getEffects()) {
effect.setTargetPointer(new FixedTarget(event.getTargetId(), game));
effect.setValue("damage", event.getAmount());
}
}
return true;
if (!event.getSourceId().equals(this.getSourceId())
|| !game.getOpponents(this.getControllerId()).contains(event.getTargetId())
|| onlyCombat
&& !((DamagedPlayerEvent) event).isCombatDamage()) {
return false;
}
return false;
this.getEffects().setValue("damage", event.getAmount());
if (setTargetPointer) {
this.getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game));
}
return true;
}
}

View file

@ -7,7 +7,6 @@ import mage.abilities.effects.Effect;
import mage.constants.AttachmentType;
import mage.constants.SetTargetPointer;
import mage.constants.Zone;
import mage.filter.StaticFilters;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game;
import mage.game.events.GameEvent;
@ -21,10 +20,6 @@ public class DealtDamageAttachedAndDiedTriggeredAbility extends TriggeredAbility
private final FilterCreaturePermanent filter;
private final SetTargetPointer setTargetPointer;
public DealtDamageAttachedAndDiedTriggeredAbility(Effect effect, boolean optional) {
this(effect, optional, StaticFilters.FILTER_PERMANENT_CREATURE, SetTargetPointer.PERMANENT, AttachmentType.EQUIPMENT);
}
public DealtDamageAttachedAndDiedTriggeredAbility(Effect effect, boolean optional, FilterCreaturePermanent filter,
SetTargetPointer setTargetPointer, AttachmentType attachmentType) {
super(Zone.ALL, effect, optional);
@ -72,8 +67,17 @@ public class DealtDamageAttachedAndDiedTriggeredAbility extends TriggeredAbility
})) {
return false;
}
if (this.setTargetPointer == SetTargetPointer.PERMANENT) {
getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game));
switch (setTargetPointer) {
case PERMANENT:
getEffects().setTargetPointer(new FixedTarget(creatureMOR));
break;
case CARD:
getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game));
break;
case NONE:
break;
default:
throw new IllegalArgumentException("Unsupported setTargetPointer value in DealtDamageAttachedAndDiedTriggeredAbility");
}
return true;
}

View file

@ -29,7 +29,7 @@ public class DiesThisOrAnotherTriggeredAbility extends TriggeredAbilityImpl {
if (filterMessage.startsWith("a ")) {
filterMessage = filterMessage.substring(2);
}
setTriggerPhrase("Whenever {this} or another " + filterMessage + " dies, ");
setTriggerPhrase("Whenever {this} or " + (filterMessage.startsWith("another") ? "" : "another ") + filterMessage + " dies, ");
setLeavesTheBattlefieldTrigger(true);
}
@ -61,8 +61,12 @@ public class DiesThisOrAnotherTriggeredAbility extends TriggeredAbilityImpl {
return false;
}
// TODO: remove applyFilterOnSource workaround for Basri's Lieutenant
return ((!applyFilterOnSource && zEvent.getTarget().getId().equals(this.getSourceId()))
|| filter.match(zEvent.getTarget(), getControllerId(), this, game));
if ((applyFilterOnSource || !zEvent.getTarget().getId().equals(this.getSourceId()))
&& !filter.match(zEvent.getTarget(), getControllerId(), this, game)) {
return false;
}
this.getEffects().setValue("creatureDied", zEvent.getTarget());
return true;
}
@Override

View file

@ -44,6 +44,7 @@ public class DrawCardOpponentTriggeredAbility extends TriggeredAbilityImpl {
if (!game.getPlayer(this.getControllerId()).hasOpponent(event.getPlayerId(), game)) {
return false;
}
this.getEffects().setValue("playerDrew", event.getPlayerId());
if (setTargetPointer) {
this.getEffects().setTargetPointer(new FixedTarget(event.getPlayerId()));
}

View file

@ -6,11 +6,13 @@ import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.abilities.effects.Effects;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.GainAbilityControlledEffect;
import mage.abilities.keyword.ReadAheadAbility;
import mage.cards.Card;
import mage.constants.Outcome;
import mage.constants.SagaChapter;
import mage.constants.Zone;
import mage.constants.*;
import mage.counters.CounterType;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
@ -47,9 +49,12 @@ public class SagaAbility extends SimpleStaticAbility {
this.readAhead = readAhead;
this.setRuleVisible(true);
this.setRuleAtTheTop(true);
Ability ability = new EntersBattlefieldAbility(new SagaLoreCountersEffect(readAhead, maxChapter));
Ability ability = new EntersBattlefieldAbility(new SagaLoreCountersEffect(maxChapter));
ability.setRuleVisible(false);
card.addAbility(ability);
if (readAhead) {
card.addAbility(ReadAheadAbility.getInstance());
}
}
protected SagaAbility(final SagaAbility ability) {
@ -115,7 +120,7 @@ public class SagaAbility extends SimpleStaticAbility {
public void addChapterEffect(Card card, SagaChapter fromChapter, SagaChapter toChapter, boolean optional, Consumer<TriggeredAbility> applier) {
for (int i = fromChapter.getNumber(); i <= toChapter.getNumber(); i++) {
ChapterTriggeredAbility ability = new ChapterTriggeredAbility(
SagaChapter.getChapter(i), toChapter, optional, readAhead
SagaChapter.getChapter(i), toChapter, optional
);
applier.accept(ability);
if (i > fromChapter.getNumber()) {
@ -132,7 +137,7 @@ public class SagaAbility extends SimpleStaticAbility {
@Override
public String getRule() {
return (readAhead
? "Read ahead <i>(Choose a chapter and start with that many lore counters. " +
? "<i>(Choose a chapter and start with that many lore counters. " +
"Add one after your draw step. Skipped chapters don't trigger."
: "<i>(As this Saga enters and after your draw step, add a lore counter.")
+ (showSacText ? " Sacrifice after " + maxChapter.toString() + '.' : "") + ")</i> ";
@ -158,22 +163,23 @@ public class SagaAbility extends SimpleStaticAbility {
return ability instanceof ChapterTriggeredAbility
&& ((ChapterTriggeredAbility) ability).getChapterFrom().getNumber() == maxChapter;
}
public static Ability makeGainReadAheadAbility() {
return new GainReadAheadAbility();
}
}
class SagaLoreCountersEffect extends OneShotEffect {
private final boolean readAhead;
private final SagaChapter maxChapter;
SagaLoreCountersEffect(boolean readAhead, SagaChapter maxChapter) {
SagaLoreCountersEffect(SagaChapter maxChapter) {
super(Outcome.Benefit);
this.readAhead = readAhead;
this.maxChapter = maxChapter;
}
private SagaLoreCountersEffect(final SagaLoreCountersEffect effect) {
super(effect);
this.readAhead = effect.readAhead;
this.maxChapter = effect.maxChapter;
}
@ -188,7 +194,8 @@ class SagaLoreCountersEffect extends OneShotEffect {
if (permanent == null) {
return false;
}
if (!readAhead) {
if (!permanent.hasAbility(ReadAheadAbility.getInstance(), game)
&& !GainReadAheadAbility.checkForAbility(game, source)) {
return permanent.addCounters(CounterType.LORE.createInstance(), source, game);
}
Player player = game.getPlayer(source.getControllerId());
@ -197,7 +204,7 @@ class SagaLoreCountersEffect extends OneShotEffect {
}
int counters = player.getAmount(
1, maxChapter.getNumber(),
"Choose the number of lore counters to enter with", game
"Choose the number of lore counters to enter with", source, game
);
return permanent.addCounters(CounterType.LORE.createInstance(counters), source, game);
}
@ -206,20 +213,17 @@ class SagaLoreCountersEffect extends OneShotEffect {
class ChapterTriggeredAbility extends TriggeredAbilityImpl {
private final SagaChapter chapterFrom, chapterTo;
private final boolean readAhead;
ChapterTriggeredAbility(SagaChapter chapterFrom, SagaChapter chapterTo, boolean optional, boolean readAhead) {
ChapterTriggeredAbility(SagaChapter chapterFrom, SagaChapter chapterTo, boolean optional) {
super(Zone.ALL, null, optional);
this.chapterFrom = chapterFrom;
this.chapterTo = chapterTo;
this.readAhead = readAhead;
}
private ChapterTriggeredAbility(final ChapterTriggeredAbility ability) {
super(ability);
this.chapterFrom = ability.chapterFrom;
this.chapterTo = ability.chapterTo;
this.readAhead = ability.readAhead;
}
@Override
@ -238,7 +242,9 @@ class ChapterTriggeredAbility extends TriggeredAbilityImpl {
return false;
}
int loreCounters = permanent.getCounters(game).getCount(CounterType.LORE);
if (readAhead && permanent.getTurnsOnBattlefield() == 0) {
if (permanent.getTurnsOnBattlefield() == 0
&& (permanent.hasAbility(ReadAheadAbility.getInstance(), game)
|| GainReadAheadAbility.checkForAbility(game, this))) {
return chapterFrom.getNumber() == loreCounters;
}
return loreCounters - event.getAmount() < chapterFrom.getNumber()
@ -275,3 +281,35 @@ class ChapterTriggeredAbility extends TriggeredAbilityImpl {
+ " - " + CardUtil.getTextWithFirstCharUpperCase(super.getRule());
}
}
class GainReadAheadAbility extends SimpleStaticAbility {
private static final FilterPermanent filter = new FilterPermanent(SubType.SAGA, "Sagas");
GainReadAheadAbility() {
super(new GainAbilityControlledEffect(
ReadAheadAbility.getInstance(), Duration.WhileOnBattlefield, filter
).setText("Sagas you control have read ahead. <i>(As a Saga enters, choose a chapter " +
"and start with that many lore counters. Skipped chapters don't trigger.)</i>"));
}
private GainReadAheadAbility(final GainReadAheadAbility ability) {
super(ability);
}
@Override
public GainReadAheadAbility copy() {
return new GainReadAheadAbility(this);
}
static boolean checkForAbility(Game game, Ability source) {
return game
.getBattlefield()
.getActivePermanents(
StaticFilters.FILTER_CONTROLLED_PERMANENT,
source.getControllerId(), source, game
)
.stream()
.anyMatch(p -> p.getAbilities(game).containsClass(GainReadAheadAbility.class));
}
}

View file

@ -1,48 +0,0 @@
package mage.abilities.common;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.Spell;
import mage.target.targetpointer.FixedTarget;
/**
* @author Susucr
*/
public class SpellCastOpponentNoManaSpentTriggeredAbility extends TriggeredAbilityImpl {
public SpellCastOpponentNoManaSpentTriggeredAbility(Effect effect) {
super(Zone.BATTLEFIELD, effect, false);
this.setTriggerPhrase("Whenever an opponent casts a spell, if no mana was spent to cast it, ");
}
protected SpellCastOpponentNoManaSpentTriggeredAbility(final SpellCastOpponentNoManaSpentTriggeredAbility ability) {
super(ability);
}
@Override
public SpellCastOpponentNoManaSpentTriggeredAbility copy() {
return new SpellCastOpponentNoManaSpentTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.SPELL_CAST;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (game.getPlayer(this.getControllerId()).hasOpponent(event.getPlayerId(), game)) {
Spell spell = game.getStack().getSpell(event.getTargetId());
if (spell != null && spell.getStackAbility().getManaCostsToPay().getUsedManaToPay().count() == 0) {
for (Effect effect : this.getEffects()) {
effect.setTargetPointer(new FixedTarget(event.getTargetId()));
}
return true;
}
}
return false;
}
}

View file

@ -0,0 +1,46 @@
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.CoinFlippedEvent;
import mage.game.events.GameEvent;
/**
* @author TheElk801
*/
public class WonCoinFlipControllerTriggeredAbility extends TriggeredAbilityImpl {
public WonCoinFlipControllerTriggeredAbility(Effect effect) {
this(effect, false);
}
public WonCoinFlipControllerTriggeredAbility(Effect effect, boolean optional) {
this(Zone.BATTLEFIELD, effect, optional);
}
public WonCoinFlipControllerTriggeredAbility(Zone zone, Effect effect, boolean optional) {
super(zone, effect, optional);
}
private WonCoinFlipControllerTriggeredAbility(final WonCoinFlipControllerTriggeredAbility ability) {
super(ability);
}
@Override
public WonCoinFlipControllerTriggeredAbility copy() {
return new WonCoinFlipControllerTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.COIN_FLIPPED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
CoinFlippedEvent flipEvent = (CoinFlippedEvent) event;
return isControlledBy(event.getPlayerId()) && flipEvent.isWinnable() && flipEvent.wasWon();
}
}

View file

@ -1,4 +1,3 @@
package mage.abilities.condition.common;
import mage.MageObjectReference;
@ -9,7 +8,6 @@ import mage.game.permanent.Permanent;
import mage.watchers.common.AttackedThisTurnWatcher;
/**
*
* @author LevelX2
*/
public enum AttackedThisTurnSourceCondition implements Condition {
@ -21,4 +19,9 @@ public enum AttackedThisTurnSourceCondition implements Condition {
AttackedThisTurnWatcher watcher = game.getState().getWatcher(AttackedThisTurnWatcher.class);
return sourcePermanent != null && watcher.getAttackedThisTurnCreatures().contains(new MageObjectReference(sourcePermanent, game));
}
@Override
public String toString() {
return "{this} attacked this turn";
}
}

View file

@ -29,6 +29,6 @@ public enum KickedCondition implements Condition {
@Override
public String toString() {
return "{this} was kicked" + (text.isEmpty() ? "" : " " + text);
return "it was kicked" + (text.isEmpty() ? "" : " " + text);
}
}

View file

@ -31,6 +31,6 @@ public enum RevoltCondition implements Condition {
@Override
public String toString() {
return "a permanent you controlled left the battlefield this turn";
return "a permanent left the battlefield under your control this turn";
}
}

View file

@ -158,8 +158,8 @@ public abstract class VariableCostImpl implements Cost, VariableCost {
if (controller != null
&& (source instanceof ManaAbility
|| stackObject != null)) {
xValue = controller.announceXCost(getMinValue(source, game), getMaxValue(source, game),
"Announce the number of " + actionText, game, source, this);
xValue = controller.announceX(getMinValue(source, game), getMaxValue(source, game),
"Announce the value for {X} (" + actionText + ")", game, source, false);
}
return xValue;
}

View file

@ -89,7 +89,7 @@ public class CollectEvidenceCost extends CostImpl {
);
}
}.withNotTarget(true);
player.choose(Outcome.Exile, target, source, game);
target.choose(Outcome.Exile, player.getId(), source.getSourceId(), source, game);
Cards cards = new CardsImpl(target.getTargets());
paid = cards
.getCards(game)

View file

@ -12,22 +12,17 @@ import mage.players.Player;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class DiscardSourceCost extends CostImpl {
private boolean nameCard = true;
public DiscardSourceCost() {}
public DiscardSourceCost(boolean nameCard){
this.nameCard = nameCard;
public DiscardSourceCost() {
super();
setText("discard this card");
}
public DiscardSourceCost(DiscardSourceCost cost) {
super(cost);
nameCard = cost.nameCard;
}
@Override
@ -41,21 +36,10 @@ public class DiscardSourceCost extends CostImpl {
if (player != null) {
Card card = player.getHand().get(source.getSourceId(), game);
paid = player.discard(card, true, source, game);
}
return paid;
}
@Override
public String getText() {
if(nameCard) {
return "Discard {this}";
}
else{
return "Discard this card";
}
}
@Override
public DiscardSourceCost copy() {
return new DiscardSourceCost(this);

View file

@ -47,7 +47,7 @@ public class DiscardTargetCost extends CostImpl {
if (player == null) {
return false;
}
int amount = this.getTargets().get(0).getNumberOfTargets();
int amount = this.getTargets().get(0).getMinNumberOfTargets();
if (randomDiscard) {
this.cards.addAll(player.discard(amount, true, true, source, game).getCards(game));
} else if (this.getTargets().choose(Outcome.Discard, controllerId, source.getSourceId(), source, game)) {

View file

@ -33,9 +33,9 @@ public class ExileFromGraveCost extends CostImpl {
this.addTarget(target);
if (target.getMaxNumberOfTargets() > 1) {
this.text = "exile "
+ (target.getNumberOfTargets() == 1
+ (target.getMinNumberOfTargets() == 1
&& target.getMaxNumberOfTargets() == Integer.MAX_VALUE ? "one or more"
: ((target.getNumberOfTargets() < target.getMaxNumberOfTargets() ? "up to " : ""))
: ((target.getMinNumberOfTargets() < target.getMaxNumberOfTargets() ? "up to " : ""))
+ CardUtil.numberToText(target.getMaxNumberOfTargets()))
+ ' ' + target.getTargetName();
} else {

View file

@ -1,88 +0,0 @@
package mage.abilities.costs.common;
import java.util.Locale;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
import mage.abilities.effects.common.continuous.GainSuspendEffect;
import mage.abilities.keyword.SuspendAbility;
import mage.cards.Card;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.game.Game;
import mage.MageObjectReference;
import mage.players.Player;
/**
* @author padfoot
*/
public class ExileSourceWithTimeCountersCost extends CostImpl {
private final int counters;
private final boolean checksSuspend;
private final boolean givesSuspend;
private final Zone fromZone;
public ExileSourceWithTimeCountersCost(int counters) {
this (counters, true, false, null);
}
public ExileSourceWithTimeCountersCost(int counters, boolean givesSuspend, boolean checksSuspend, Zone fromZone) {
this.counters = counters;
this.givesSuspend = givesSuspend;
this.checksSuspend = checksSuspend;
this.fromZone = fromZone;
this.text = "exile {this} " +
((fromZone != null) ? " from your " + fromZone.toString().toLowerCase(Locale.ENGLISH) : "") +
" and put " + counters + " time counters on it" +
(givesSuspend ? ". It gains suspend" : "") +
(checksSuspend ? ". If it doesn't have suspend, it gains suspend" : "");
}
private ExileSourceWithTimeCountersCost(final ExileSourceWithTimeCountersCost cost) {
super(cost);
this.counters = cost.counters;
this.givesSuspend = cost.givesSuspend;
this.checksSuspend = cost.checksSuspend;
this.fromZone = cost.fromZone;
}
@Override
public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) {
Player controller = game.getPlayer(controllerId);
if (controller == null) {
return paid;
}
Card card = game.getCard(source.getSourceId());
boolean hasSuspend = card.getAbilities(game).containsClass(SuspendAbility.class);
if (card != null && (fromZone == null || fromZone == game.getState().getZone(source.getSourceId()))) {
UUID exileId = SuspendAbility.getSuspendExileId(controller.getId(), game);
if (controller.moveCardsToExile(card, source, game, true, exileId, "Suspended cards of " + controller.getName())) {
card.addCounters(CounterType.TIME.createInstance(counters), controller.getId(), source, game);
game.informPlayers(controller.getLogName() + " exiles " + card.getLogName() + ((fromZone != null) ? " from their " + fromZone.toString().toLowerCase(Locale.ENGLISH) : "") + " with " + counters + " time counters on it.");
if (givesSuspend || (checksSuspend && !hasSuspend)) {
game.addEffect(new GainSuspendEffect(new MageObjectReference(card, game)), source);
}
}
// 117.11. The actions performed when paying a cost may be modified by effects.
// Even if they are, meaning the actions that are performed don't match the actions
// that are called for, the cost has still been paid.
// so return state here is not important because the user indended to exile the target anyway
paid = true;
}
return paid;
}
@Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
return (game.getCard(source.getSourceId()) != null && (fromZone == null || fromZone == game.getState().getZone(source.getSourceId())));
}
@Override
public ExileSourceWithTimeCountersCost copy() {
return new ExileSourceWithTimeCountersCost(this);
}
}

View file

@ -10,7 +10,6 @@ import mage.game.permanent.Permanent;
import java.util.UUID;
/**
*
* @author jeffwadsworth
*/
public class PutCountersSourceCost extends CostImpl {
@ -29,6 +28,7 @@ public class PutCountersSourceCost extends CostImpl {
@Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
// TODO: implement permanent.canAddCounters with replacement events check, see tests with Devoted Druid
return true;
}

View file

@ -136,7 +136,8 @@ public class RemoveCounterCost extends CostImpl {
int numberOfCountersSelected = 1;
if (countersLeft > 1 && countersOnPermanent > 1) {
numberOfCountersSelected = controller.getAmount(1, Math.min(countersLeft, countersOnPermanent),
"Choose how many counters (" + counterName + ") to remove from " + targetObject.getLogName() + " as payment", game);
"Choose how many counters (" + counterName + ") to remove from " + targetObject.getLogName() + " as payment",
source, game);
}
targetObject.removeCounters(counterName, numberOfCountersSelected, source, game);
countersRemoved += numberOfCountersSelected;

View file

@ -25,7 +25,7 @@ public class ReturnToHandChosenControlledPermanentCost extends CostImpl {
public ReturnToHandChosenControlledPermanentCost(TargetControlledPermanent target) {
target.withNotTarget(true);
this.addTarget(target);
if (target.getMaxNumberOfTargets() > 1 && target.getMaxNumberOfTargets() == target.getNumberOfTargets()) {
if (target.getMaxNumberOfTargets() > 1 && target.getMaxNumberOfTargets() == target.getMinNumberOfTargets()) {
this.text = "return " + CardUtil.numberToText(target.getMaxNumberOfTargets()) + ' '
+ target.getTargetName()
+ (target.getTargetName().endsWith(" you control") ? "" : " you control")

View file

@ -29,7 +29,7 @@ public class RevealTargetFromHandCost extends CostImpl {
public RevealTargetFromHandCost(TargetCardInHand target) {
this.addTarget(target);
this.allowNoReveal = target.getNumberOfTargets() == 0;
this.allowNoReveal = target.getMinNumberOfTargets() == 0;
this.text = "reveal " + target.getDescription();
this.revealedCards = new ArrayList<>();
}
@ -62,7 +62,7 @@ public class RevealTargetFromHandCost extends CostImpl {
MageObject baseObject = game.getBaseObject(source.getSourceId());
player.revealCards(baseObject == null ? "card cost" : baseObject.getIdName(), cards, game);
}
if (this.getTargets().get(0).getNumberOfTargets() <= numberCardsRevealed) {
if (this.getTargets().get(0).getMinNumberOfTargets() <= numberCardsRevealed) {
paid = true; // e.g. for optional additional costs. example: Dragonlord's Prerogative also true if 0 cards shown
return paid;
}

View file

@ -67,7 +67,7 @@ public class SacrificeTargetCost extends CostImpl implements SacrificeCost {
addSacrificeTarget(game, permanent);
paid |= permanent.sacrifice(source, game);
}
if (!paid && this.getTargets().get(0).getNumberOfTargets() == 0) {
if (!paid && this.getTargets().get(0).getMinNumberOfTargets() == 0) {
paid = true; // e.g. for Devouring Rage
}
}
@ -88,7 +88,7 @@ public class SacrificeTargetCost extends CostImpl implements SacrificeCost {
return false;
}
int validTargets = 0;
int neededTargets = this.getTargets().get(0).getNumberOfTargets();
int neededTargets = this.getTargets().get(0).getMinNumberOfTargets();
for (Permanent permanent : game.getBattlefield().getActivePermanents(((TargetPermanent) this.getTargets().get(0)).getFilter(), controllerId, source, game)) {
if (controller.canPaySacrificeCost(permanent, source, controllerId, game)) {
validTargets++;
@ -114,11 +114,11 @@ public class SacrificeTargetCost extends CostImpl implements SacrificeCost {
if (target.getMinNumberOfTargets() != target.getMaxNumberOfTargets()) {
return target.getTargetName();
}
if (target.getNumberOfTargets() == 1
if (target.getMinNumberOfTargets() == 1
|| target.getTargetName().startsWith("a ")
|| target.getTargetName().startsWith("an ")) {
return CardUtil.addArticle(target.getTargetName());
}
return CardUtil.numberToText(target.getNumberOfTargets()) + ' ' + target.getTargetName();
return CardUtil.numberToText(target.getMinNumberOfTargets()) + ' ' + target.getTargetName();
}
}

View file

@ -24,7 +24,7 @@ public class TapTargetCost extends CostImpl {
this.target = target;
this.target.withNotTarget(true); // costs are never targeted
this.target.setRequired(false); // can be cancel by user
this.text = "tap " + (target.getNumberOfTargets() > 1
this.text = "tap " + (target.getMinNumberOfTargets() > 1
? CardUtil.numberToText(target.getMaxNumberOfTargets()) + ' ' + target.getTargetName()
: CardUtil.addArticle(target.getTargetName()));
}
@ -47,7 +47,7 @@ public class TapTargetCost extends CostImpl {
permanents.add(permanent);
}
}
if (target.getNumberOfTargets() == 0) {
if (target.getMinNumberOfTargets() == 0) {
paid = true; // e.g. Aryel with X = 0
}
source.getEffects().setValue("tappedPermanents", permanents);

View file

@ -1,7 +1,6 @@
package mage.abilities.costs.common;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
@ -10,6 +9,8 @@ import mage.counters.CounterType;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.util.UUID;
/**
*
* @author Plopman
@ -43,7 +44,7 @@ public class UntapSourceCost extends CostImpl {
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null) {
return permanent.isTapped() && (permanent.canTap(game) || game.getContinuousEffects().asThough(source.getSourceId(), AsThoughEffectType.ACTIVATE_HASTE, ability, controllerId, game).isEmpty());
return permanent.isTapped() && (permanent.canTap(game) || !game.getContinuousEffects().asThough(source.getSourceId(), AsThoughEffectType.ACTIVATE_HASTE, ability, controllerId, game).isEmpty());
}
return false;
}

View file

@ -40,7 +40,7 @@ public class ExileCardsFromHandAdjuster implements CostAdjuster {
// real - need to choose
// TODO: need early target cost instead dialog here
int toExile = cardCount == 0 ? 0 : player.getAmount(
0, cardCount, "Choose how many " + filter.getMessage() + " to exile", game
0, cardCount, "Choose how many " + filter.getMessage() + " to exile", ability, game
);
reduceCount = 2 * toExile;
if (toExile > 0) {

View file

@ -3,7 +3,6 @@ package mage.abilities.decorator;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.condition.Condition;
import mage.abilities.effects.Effect;
import mage.abilities.effects.Effects;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
@ -126,10 +125,11 @@ public class ConditionalOneShotEffect extends OneShotEffect {
}
@Override
public Effect setTargetPointer(TargetPointer targetPointer) {
public ConditionalOneShotEffect setTargetPointer(TargetPointer targetPointer) {
effects.setTargetPointer(targetPointer);
otherwiseEffects.setTargetPointer(targetPointer);
return super.setTargetPointer(targetPointer);
super.setTargetPointer(targetPointer);
return this;
}
@Override

View file

@ -4,8 +4,6 @@ package mage.abilities.dynamicvalue.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.abilities.hint.Hint;
import mage.abilities.hint.ValueHint;
import mage.game.Game;
import mage.watchers.common.CreaturesDiedWatcher;
@ -15,12 +13,6 @@ import mage.watchers.common.CreaturesDiedWatcher;
public enum CreaturesDiedThisTurnCount implements DynamicValue {
instance;
private static final Hint hint = new ValueHint("Number of creatures that died this turn", instance);
public static Hint getHint() {
return hint;
}
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
CreaturesDiedWatcher watcher = game.getState().getWatcher(CreaturesDiedWatcher.class);

View file

@ -29,7 +29,7 @@ public enum GreatestToughnessAmongControlledCreaturesValue implements DynamicVal
public int calculate(Game game, Ability sourceAbility, Effect effect) {
return game
.getBattlefield()
.getActivePermanents(filter, sourceAbility.getControllerId(), game)
.getActivePermanents(filter, sourceAbility.getControllerId(), sourceAbility, game)
.stream()
.map(MageObject::getToughness)
.mapToInt(MageInt::getValue)

View file

@ -21,6 +21,8 @@ public interface CostModificationEffect extends ContinuousEffect {
/**
* Called by the {@link ContinuousEffects#costModification(java.util.UUID, mage.abilities.Ability, mage.game.Game) ContinuousEffects.costModification}
* method.
* <p>
* Warning, choose dialogs restricted in plyable calculation, so you must check inCheckPlayableState
*
* @param game The game for which this effect should be applied.
* @param source The source ability of this effect.

View file

@ -115,10 +115,18 @@ public class Effects extends ArrayList<Effect> {
sbText.append('.');
}
if (mode.getCost() != null || mode.getFlavorWord() != null) {
sbText.replace(0, 1, sbText.substring(0, 1).toUpperCase());
}
// cost
if (mode.getCost() != null) {
sbText.insert(0, " &mdash; ");
sbText.insert(0, mode.getCost().getText());
}
// flavor word
if (mode.getFlavorWord() != null) {
return CardUtil.italicizeWithEmDash(mode.getFlavorWord())
+ CardUtil.getTextWithFirstCharUpperCase(sbText.toString());
sbText.insert(0, CardUtil.italicizeWithEmDash(mode.getFlavorWord()));
}
return sbText.toString();

View file

@ -31,7 +31,13 @@ public abstract class OneShotEffect extends EffectImpl {
}
@Override
public Effect setTargetPointer(TargetPointer targetPointer) {
public OneShotEffect concatBy(String concatPrefix) {
super.concatBy(concatPrefix);
return this;
}
@Override
public OneShotEffect setTargetPointer(TargetPointer targetPointer) {
super.setTargetPointer(targetPointer);
return this;
}

View file

@ -50,14 +50,7 @@ public class ChooseACardNameEffect extends OneShotEffect {
return nameSupplier.get();
}
public String getChoice(Game game, Ability source) {
return getChoice(game.getPlayer(source.getControllerId()), game, source, true);
}
public String getChoice(Player player, Game game, Ability source, boolean setValue) {
if (player == null) {
return null;
}
public Choice makeChoiceObject() {
Choice cardChoice = new ChoiceImpl(true, ChoiceHintType.CARD);
Set<String> names = this.getNames();
if (names.isEmpty()) {
@ -68,6 +61,18 @@ public class ChooseACardNameEffect extends OneShotEffect {
cardChoice.setChoices(names);
cardChoice.setMessage(CardUtil.getTextWithFirstCharUpperCase(this.getMessage()));
cardChoice.clearChoice();
return cardChoice;
}
public String getChoice(Game game, Ability source) {
return getChoice(game.getPlayer(source.getControllerId()), game, source, true);
}
public String getChoice(Player player, Game game, Ability source, boolean setValue) {
if (player == null) {
return null;
}
Choice cardChoice = makeChoiceObject();
player.choose(Outcome.Detriment, cardChoice, game);
String cardName = cardChoice.getChoice();
if (cardName == null) {

View file

@ -13,27 +13,30 @@ import mage.target.TargetPermanent;
import mage.util.CardUtil;
/**
* To be used with AsEntersBattlefieldAbility (otherwise Zone Change Counter will be wrong)
* To be used with AsEntersBattlefieldAbility with useOffset=false (otherwise Zone Change Counter will be wrong)
*
* @author weirddan455
*/
public class ChooseCreatureEffect extends OneShotEffect {
private final FilterPermanent filter;
private final boolean useOffset;
public ChooseCreatureEffect() {
this(StaticFilters.FILTER_ANOTHER_CREATURE_YOU_CONTROL);
this(StaticFilters.FILTER_ANOTHER_CREATURE_YOU_CONTROL, true);
}
public ChooseCreatureEffect(FilterPermanent filter) {
public ChooseCreatureEffect(FilterPermanent filter, boolean useOffset) {
super(Outcome.Benefit);
this.filter = filter;
this.staticText = "choose " + filter.getMessage();
this.useOffset = useOffset;
}
private ChooseCreatureEffect(final ChooseCreatureEffect effect) {
super(effect);
this.filter = effect.filter;
this.useOffset = effect.useOffset;
}
@Override
@ -44,11 +47,8 @@ public class ChooseCreatureEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return false;
}
Permanent sourcePermanent = game.getPermanentEntering(source.getSourceId());
if (sourcePermanent == null) {
if (controller == null || sourcePermanent == null) {
return false;
}
TargetPermanent target = new TargetPermanent(1, 1, filter, true);
@ -58,7 +58,10 @@ public class ChooseCreatureEffect extends OneShotEffect {
return false;
}
game.getState().setValue(
CardUtil.getObjectZoneString("chosenCreature", sourcePermanent.getId(), game, sourcePermanent.getZoneChangeCounter(game) + 1, false),
CardUtil.getObjectZoneString(
"chosenCreature", sourcePermanent.getId(), game,
sourcePermanent.getZoneChangeCounter(game) + (useOffset ? 1 : 0), false
),
new MageObjectReference(chosenCreature, game)
);
sourcePermanent.addInfo("chosen creature", CardUtil.addToolTipMarkTags("Chosen Creature " + chosenCreature.getIdName()), game);

View file

@ -89,8 +89,9 @@ public class CounterUnlessPaysEffect extends OneShotEffect {
&& costToPay.pay(source, game, source, spell.getControllerId(), false, null))) {
game.informPlayers(player.getLogName() + " chooses not to pay " + costValueMessage + " to prevent the counter effect");
game.getStack().counter(spell.getId(), source, game, exile ? PutCards.EXILED : PutCards.GRAVEYARD);
} else {
game.informPlayers(player.getLogName() + " chooses to pay " + costValueMessage + " to prevent the counter effect");
}
game.informPlayers(player.getLogName() + " chooses to pay " + costValueMessage + " to prevent the counter effect");
return true;
}

View file

@ -81,8 +81,9 @@ public class CreateDelayedTriggeredAbilityEffect extends OneShotEffect {
}
@Override
public Effect setTargetPointer(TargetPointer targetPointer) {
public CreateDelayedTriggeredAbilityEffect setTargetPointer(TargetPointer targetPointer) {
ability.getEffects().setTargetPointer(targetPointer);
return super.setTargetPointer(targetPointer);
super.setTargetPointer(targetPointer);
return this;
}
}

View file

@ -26,7 +26,7 @@ public class CreateTokenAttachSourceEffect extends CreateTokenEffect {
public CreateTokenAttachSourceEffect(Token token, String innerConcat, boolean optional) {
super(token);
this.optional = optional;
staticText = staticText.concat(innerConcat + (optional ? " you may" : "") + " attach {this} to it");
staticText = staticText.concat(innerConcat + (optional ? ". You may" : "") + " attach {this} to it");
}
private CreateTokenAttachSourceEffect(final CreateTokenAttachSourceEffect effect) {

View file

@ -27,7 +27,7 @@ public class CreateXXTokenExiledEffectManaValueEffect extends OneShotEffect {
super(Outcome.Benefit);
this.tokenMaker = tokenMaker;
staticText = "the exiled card's owner creates an X/X " + description +
"creature token, where X is the mana value of the exiled card";
" creature token, where X is the mana value of the exiled card";
}
private CreateXXTokenExiledEffectManaValueEffect(final CreateXXTokenExiledEffectManaValueEffect effect) {

View file

@ -142,7 +142,7 @@ public class DevourEffect extends ReplacementEffectImpl {
text += devourFactor;
}
text += " <i>(As this enters, you may sacrifice any number of "
text += " <i>(As this creature enters, you may sacrifice any number of "
+ filterMessage + "s. "
+ "This creature enters with ";

View file

@ -1,4 +1,3 @@
package mage.abilities.effects.common;
import mage.abilities.Ability;
@ -9,7 +8,6 @@ import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.events.ZoneChangeEvent;
import mage.game.stack.StackObject;
import mage.players.Player;
@ -21,7 +19,7 @@ public class DiscardOntoBattlefieldEffect extends ReplacementEffectImpl {
public DiscardOntoBattlefieldEffect() {
super(Duration.EndOfGame, Outcome.PutCardInPlay);
staticText = "If a spell or ability an opponent controls causes you to discard {this}, put it onto the battlefield instead of putting it into your graveyard";
staticText = "If a spell or ability an opponent controls causes you to discard this card, put it onto the battlefield instead of putting it into your graveyard";
}
protected DiscardOntoBattlefieldEffect(final DiscardOntoBattlefieldEffect effect) {
@ -40,30 +38,24 @@ public class DiscardOntoBattlefieldEffect extends ReplacementEffectImpl {
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (event.getTargetId().equals(source.getSourceId())) {
ZoneChangeEvent zcEvent = (ZoneChangeEvent) event;
if (zcEvent.getFromZone() == Zone.HAND && zcEvent.getToZone() == Zone.GRAVEYARD) {
StackObject spell = game.getStack().getStackObject(event.getSourceId());
if (spell != null && game.getOpponents(source.getControllerId()).contains(spell.getControllerId())) {
return true;
}
}
if (!event.getTargetId().equals(source.getSourceId())) {
return false;
}
return false;
ZoneChangeEvent zcEvent = (ZoneChangeEvent) event;
if (zcEvent.getFromZone() != Zone.HAND || zcEvent.getToZone() != Zone.GRAVEYARD) {
return false;
}
StackObject spell = game.getStack().getStackObject(event.getSourceId());
return spell != null && game.getOpponents(source.getControllerId()).contains(spell.getControllerId());
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Card card = game.getCard(source.getSourceId());
if (card != null) {
Player owner = game.getPlayer(card.getOwnerId());
if (owner != null) {
if (owner.moveCards(card, Zone.BATTLEFIELD, source, game)) {
return true;
}
}
if (card == null) {
return false;
}
return false;
Player owner = game.getPlayer(card.getOwnerId());
return owner != null && owner.moveCards(card, Zone.BATTLEFIELD, source, game);
}
}

View file

@ -64,7 +64,7 @@ public class DrawCardTargetEffect extends OneShotEffect {
&& player.canRespond()) {
int cardsToDraw = amount.calculate(game, source, this);
if (upTo) {
cardsToDraw = player.getAmount(0, cardsToDraw, "Draw how many cards?", game);
cardsToDraw = player.getAmount(0, cardsToDraw, "Draw how many cards?", source, game);
}
if (!optional
|| player.chooseUse(outcome, "Use draw effect?", source, game)) {

View file

@ -72,7 +72,7 @@ public class ExileAdventureSpellEffect extends OneShotEffect implements MageSing
class AdventureCastFromExileEffect extends AsThoughEffectImpl {
public AdventureCastFromExileEffect() {
super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit);
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.";
}

View file

@ -23,7 +23,7 @@ public class ExileSpellWithTimeCountersEffect extends OneShotEffect {
private final boolean gainsSuspend;
public ExileSpellWithTimeCountersEffect(int counters) {
this (counters, false);
this(counters, false);
}
public ExileSpellWithTimeCountersEffect(int counters, boolean gainsSuspend) {
@ -33,6 +33,7 @@ public class ExileSpellWithTimeCountersEffect extends OneShotEffect {
this.staticText = "exile {this} with " + CardUtil.numberToText(this.counters) + " time counters on it"
+ (gainsSuspend ? ". It gains suspend" : "");
}
private ExileSpellWithTimeCountersEffect(final ExileSpellWithTimeCountersEffect effect) {
super(effect);
this.counters = effect.counters;

View file

@ -0,0 +1,45 @@
package mage.abilities.effects.common;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.choices.FaceVillainousChoice;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import java.util.UUID;
/**
* @author TheElk801
*/
public class FaceVillainousChoiceOpponentsEffect extends OneShotEffect {
private final FaceVillainousChoice choice;
public FaceVillainousChoiceOpponentsEffect(FaceVillainousChoice choice) {
super(Outcome.Benefit);
this.choice = choice;
staticText = "each opponent " + choice.generateRule();
}
private FaceVillainousChoiceOpponentsEffect(final FaceVillainousChoiceOpponentsEffect effect) {
super(effect);
this.choice = effect.choice;
}
@Override
public FaceVillainousChoiceOpponentsEffect copy() {
return new FaceVillainousChoiceOpponentsEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
for (UUID playerId : game.getOpponents(source.getControllerId())) {
Player player = game.getPlayer(playerId);
if (player != null) {
choice.faceChoice(player, game, source);
}
}
return true;
}
}

View file

@ -0,0 +1,32 @@
package mage.abilities.effects.common;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.game.Game;
/**
* @author TheElk801
*/
public class LockOrUnlockRoomTargetEffect extends OneShotEffect {
public LockOrUnlockRoomTargetEffect() {
super(Outcome.Benefit);
staticText = "lock or unlock a door of target Room you control";
}
private LockOrUnlockRoomTargetEffect(final LockOrUnlockRoomTargetEffect effect) {
super(effect);
}
@Override
public LockOrUnlockRoomTargetEffect copy() {
return new LockOrUnlockRoomTargetEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
// TODO: Implement this
return true;
}
}

View file

@ -132,8 +132,8 @@ public class PutOnLibraryTargetEffect extends OneShotEffect {
sb.append("put ");
if (target.getMaxNumberOfTargets() == 0 || target.getMaxNumberOfTargets() == Integer.MAX_VALUE) {
sb.append("any number of ");
} else if (target.getMaxNumberOfTargets() != 1 || target.getNumberOfTargets() != 1) {
if (target.getMaxNumberOfTargets() > target.getNumberOfTargets()) {
} else if (target.getMaxNumberOfTargets() != 1 || target.getMinNumberOfTargets() != 1) {
if (target.getMaxNumberOfTargets() > target.getMinNumberOfTargets()) {
sb.append("up to ");
}
sb.append(CardUtil.numberToText(target.getMaxNumberOfTargets())).append(' ');

View file

@ -89,7 +89,7 @@ public class ReturnFromGraveyardToBattlefieldTargetEffect extends OneShotEffect
if (target.getMaxNumberOfTargets() == Integer.MAX_VALUE
&& target.getMinNumberOfTargets() == 0) {
sb.append("any number of ");
} else if (target.getMaxNumberOfTargets() != target.getNumberOfTargets()) {
} else if (target.getMaxNumberOfTargets() != target.getMinNumberOfTargets()) {
sb.append("up to ");
sb.append(CardUtil.numberToText(target.getMaxNumberOfTargets()));
sb.append(' ');

View file

@ -153,10 +153,11 @@ public class RollDieWithResultTableEffect extends OneShotEffect {
}
@Override
public Effect setTargetPointer(TargetPointer targetPointer) {
public RollDieWithResultTableEffect setTargetPointer(TargetPointer targetPointer) {
for (TableEntry tableEntry : resultsTable) {
tableEntry.effects.setTargetPointer(targetPointer);
}
return super.setTargetPointer(targetPointer);
super.setTargetPointer(targetPointer);
return this;
}
}

View file

@ -91,9 +91,7 @@ public class SacrificeAllEffect extends OneShotEffect {
continue;
}
TargetSacrifice target = new TargetSacrifice(numTargets, filter);
while (!target.isChosen(game) && target.canChoose(player.getId(), source, game) && player.canRespond()) {
player.choose(Outcome.Sacrifice, target, source, game);
}
target.choose(Outcome.Sacrifice, player.getId(), source, game);
perms.addAll(target.getTargets());
}

View file

@ -64,13 +64,12 @@ public class SacrificeEffect extends OneShotEffect {
continue;
}
TargetSacrifice target = new TargetSacrifice(amount, filter);
while (!target.isChosen(game) && target.canChoose(player.getId(), source, game) && player.canRespond()) {
player.choose(Outcome.Sacrifice, target, source, game);
}
for (UUID targetId : target.getTargets()) {
Permanent permanent = game.getPermanent(targetId);
if (permanent != null && permanent.sacrifice(source, game)) {
applied = true;
if (target.choose(Outcome.Sacrifice, player.getId(), source.getSourceId(), source, game)) {
for (UUID targetId : target.getTargets()) {
Permanent permanent = game.getPermanent(targetId);
if (permanent != null && permanent.sacrifice(source, game)) {
applied = true;
}
}
}
}

View file

@ -12,8 +12,12 @@ import mage.game.permanent.Permanent;
public class TransformSourceEffect extends OneShotEffect {
public TransformSourceEffect() {
this(false);
}
public TransformSourceEffect(boolean it) {
super(Outcome.Transform);
staticText = "transform {this}";
staticText = "transform " + (it ? "it" : "{this}");
}
protected TransformSourceEffect(final TransformSourceEffect effect) {

View file

@ -0,0 +1,54 @@
package mage.abilities.effects.common.asthought;
import mage.abilities.Ability;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.cards.Card;
import mage.constants.AsThoughEffectType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import java.util.UUID;
/**
* @author Susucr
*/
public class MayCastFromGraveyardAsAdventureEffect extends AsThoughEffectImpl {
public MayCastFromGraveyardAsAdventureEffect() {
super(AsThoughEffectType.CAST_ADVENTURE_FROM_NOT_OWN_HAND_ZONE, Duration.UntilEndOfYourNextTurn, Outcome.Benefit);
staticText = "you may cast it from your graveyard as an Adventure until the end of your next turn";
}
private MayCastFromGraveyardAsAdventureEffect(final MayCastFromGraveyardAsAdventureEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
return true;
}
@Override
public MayCastFromGraveyardAsAdventureEffect copy() {
return new MayCastFromGraveyardAsAdventureEffect(this);
}
@Override
public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) {
if (!source.isControlledBy(affectedControllerId)) {
return false;
}
Card card = game.getCard(sourceId);
if (card == null || card.getMainCard() == null || !card.getMainCard().getId().equals(source.getSourceId())) {
return false;
}
Card sourceCard = game.getCard(source.getSourceId());
return sourceCard != null
&& game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD
&& source.getSourceObjectZoneChangeCounter() == sourceCard.getZoneChangeCounter(game);
}
}

View file

@ -45,7 +45,9 @@ public class CanBlockAdditionalCreatureAllEffect extends ContinuousEffectImpl {
if (permanent != null) {
// maxBlocks = 0 equals to "can block any number of creatures"
if (amount > 0) {
permanent.setMaxBlocks(permanent.getMaxBlocks() + amount);
if (permanent.getMaxBlocks() > 0) {
permanent.setMaxBlocks(permanent.getMaxBlocks() + amount);
}
} else {
permanent.setMaxBlocks(0);
}

View file

@ -53,7 +53,9 @@ public class CanBlockAdditionalCreatureEffect extends ContinuousEffectImpl {
if (permanent != null) {
// maxBlocks = 0 equals to "can block any number of creatures"
if (amount > 0) {
permanent.setMaxBlocks(permanent.getMaxBlocks() + amount);
if (permanent.getMaxBlocks() > 0) {
permanent.setMaxBlocks(permanent.getMaxBlocks() + amount);
}
} else {
permanent.setMaxBlocks(0);
}

View file

@ -53,7 +53,9 @@ public class CanBlockAdditionalCreatureTargetEffect extends ContinuousEffectImpl
// maxBlocks = 0 equals to "can block any number of creatures"
if (amount > 0) {
target.setMaxBlocks(target.getMaxBlocks() + amount);
if (target.getMaxBlocks() > 0) {
target.setMaxBlocks(target.getMaxBlocks() + amount);
}
} else {
target.setMaxBlocks(0);
}

View file

@ -33,7 +33,7 @@ public class GoadTargetEffect extends ContinuousEffectImpl {
super(duration, Layer.RulesEffects, SubLayer.NA, Outcome.Detriment);
}
private GoadTargetEffect(final GoadTargetEffect effect) {
protected GoadTargetEffect(final GoadTargetEffect effect) {
super(effect);
}

View file

@ -9,7 +9,6 @@ import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.util.CardUtil;
@ -27,10 +26,6 @@ public class GainAbilityControlledEffect extends ContinuousEffectImpl {
protected boolean forceQuotes = false;
protected boolean durationRuleAtStart = false; // put duration rule to the start of the rules instead end
public GainAbilityControlledEffect(Ability ability, Duration duration) {
this(ability, duration, StaticFilters.FILTER_PERMANENTS);
}
public GainAbilityControlledEffect(Ability ability, Duration duration, FilterPermanent filter) {
this(ability, duration, filter, false);
}

View file

@ -7,6 +7,7 @@ import mage.abilities.effects.Effect;
import mage.constants.*;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.util.CardUtil;
/**
* @author TheElk801
@ -22,7 +23,7 @@ public class GainAnchorWordAbilitySourceEffect extends ContinuousEffectImpl {
public GainAnchorWordAbilitySourceEffect(Ability ability, ModeChoice modeChoice) {
super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility);
this.staticText = "&bull " + modeChoice + " &mdash; " + ability.getRule();
this.staticText = "&bull " + modeChoice + " &mdash; " + CardUtil.getTextWithFirstCharUpperCase(ability.getRule());
this.ability = ability;
this.modeChoice = modeChoice;
this.ability.setRuleVisible(false);

View file

@ -2,41 +2,34 @@ package mage.abilities.effects.common.continuous;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.constants.*;
import mage.game.Game;
import mage.game.permanent.Permanent;
/**
* @author LevelX2
*/
public class SetBasePowerToughnessEnchantedEffect extends ContinuousEffectImpl {
public class SetBasePowerToughnessAttachedEffect extends ContinuousEffectImpl {
private final int power;
private final int toughness;
public SetBasePowerToughnessEnchantedEffect() {
this(0, 2);
}
public SetBasePowerToughnessEnchantedEffect(int power, int toughness) {
public SetBasePowerToughnessAttachedEffect(int power, int toughness, AttachmentType attachmentType) {
super(Duration.WhileOnBattlefield, Layer.PTChangingEffects_7, SubLayer.SetPT_7b, Outcome.BoostCreature);
staticText = "Enchanted creature has base power and toughness " + power + "/" + toughness;
staticText = attachmentType.verb() + " creature has base power and toughness " + power + "/" + toughness;
this.power = power;
this.toughness = toughness;
}
protected SetBasePowerToughnessEnchantedEffect(final SetBasePowerToughnessEnchantedEffect effect) {
protected SetBasePowerToughnessAttachedEffect(final SetBasePowerToughnessAttachedEffect effect) {
super(effect);
this.power = effect.power;
this.toughness = effect.toughness;
}
@Override
public SetBasePowerToughnessEnchantedEffect copy() {
return new SetBasePowerToughnessEnchantedEffect(this);
public SetBasePowerToughnessAttachedEffect copy() {
return new SetBasePowerToughnessAttachedEffect(this);
}
@Override

View file

@ -35,6 +35,7 @@ public class GraveyardFromAnywhereExileReplacementEffect extends ReplacementEffe
public GraveyardFromAnywhereExileReplacementEffect(Duration duration) {
this(duration, StaticFilters.FILTER_CARD_A, true, false);
}
protected GraveyardFromAnywhereExileReplacementEffect(Duration duration, FilterCard filter, boolean onlyYou, boolean tokens) {
super(duration, Outcome.Exile);
this.filter = filter;
@ -43,7 +44,7 @@ public class GraveyardFromAnywhereExileReplacementEffect extends ReplacementEffe
this.setText();
}
private GraveyardFromAnywhereExileReplacementEffect(final GraveyardFromAnywhereExileReplacementEffect effect) {
protected GraveyardFromAnywhereExileReplacementEffect(final GraveyardFromAnywhereExileReplacementEffect effect) {
super(effect);
this.filter = effect.filter;
this.onlyYou = effect.onlyYou;

View file

@ -19,11 +19,19 @@ import java.util.UUID;
*/
public class PlayFromGraveyardControllerEffect extends AsThoughEffectImpl {
private static final FilterCard filterPlayCards = new FilterCard("cards");
private static final FilterCard filterPlayLands = new FilterLandCard("lands");
private static final FilterCard filterPlayCast = new FilterCard("play lands and cast spells");
private final FilterCard filter;
/**
* You may play cards from your graveyard.
*/
public static PlayFromGraveyardControllerEffect playCards() {
return new PlayFromGraveyardControllerEffect(filterPlayCards);
}
/**
* You may play lands from your graveyard.
*/
@ -53,7 +61,7 @@ public class PlayFromGraveyardControllerEffect extends AsThoughEffectImpl {
this.filter = filter;
String filterMessage = filter.getMessage();
if (!filterMessage.startsWith("play ") && !filterMessage.startsWith("cast")) {
if (filterMessage.contains("lands")) {
if (filterMessage.contains("cards") || filterMessage.contains("lands")) {
filterMessage = "play " + filterMessage;
} else {
filterMessage = "cast " + filterMessage;

View file

@ -87,7 +87,7 @@ public class SearchLibraryPutInHandOrOnBattlefieldEffect extends SearchEffect {
private void setText() {
StringBuilder sb = new StringBuilder();
sb.append("search your library for ");
if (target.getNumberOfTargets() == 0 && target.getMaxNumberOfTargets() > 0) {
if (target.getMinNumberOfTargets() == 0 && target.getMaxNumberOfTargets() > 0) {
sb.append("up to ").append(CardUtil.numberToText(target.getMaxNumberOfTargets())).append(' ');
sb.append(target.getTargetName()).append(revealCards ? ", reveal them," : "").append(" and put them into your hand");
} else {

View file

@ -32,7 +32,7 @@ public class AddManaFromColorChoicesEffect extends ManaEffect {
.map(Mana::new)
.forEach(netMana::add);
staticText = "add " + CardUtil
.concatWithOr(this.netMana.stream().map(s -> "{" + s + '}').collect(Collectors.toList()));
.concatWithOr(this.netMana.stream().map(Mana::toString).collect(Collectors.toList()));
}
private AddManaFromColorChoicesEffect(final AddManaFromColorChoicesEffect effect) {

View file

@ -20,7 +20,7 @@ public class ConditionHint implements Hint {
private final boolean useIcons;
public ConditionHint(Condition condition) {
this(condition, condition.toString());
this(condition, CardUtil.getTextWithFirstCharUpperCase(condition.toString()));
}
public ConditionHint(Condition condition, String textWithIcons) {

View file

@ -173,8 +173,8 @@ class AssistEffect extends OneShotEffect {
// AI can't assist other players, maybe for teammates only (but tests must work as normal)
int amountToPay = 0;
if (!targetPlayer.isComputer()) {
amountToPay = targetPlayer.announceXMana(0, unpaid.getMana().getGeneric(),
"How much mana to pay as assist for " + controller.getName() + "?", game, source);
amountToPay = targetPlayer.announceX(0, unpaid.getMana().getGeneric(),
"How much mana to pay as assist for " + controller.getName() + "?", game, source, true);
}
if (amountToPay > 0) {

View file

@ -0,0 +1,30 @@
package mage.abilities.keyword;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.common.CreateTokenAttachSourceEffect;
import mage.game.permanent.token.HeroToken;
/**
* @author balazskristof
*/
public class JobSelectAbility extends EntersBattlefieldTriggeredAbility {
public JobSelectAbility() {
super(new CreateTokenAttachSourceEffect(new HeroToken()));
}
protected JobSelectAbility(final JobSelectAbility ability) {
super(ability);
}
@Override
public String getRule() {
return "Job select <i>(When this Equipment enters, " +
"create a 1/1 colorless Hero creature token, then attach this to it.)</i>";
}
@Override
public JobSelectAbility copy() {
return new JobSelectAbility(this);
}
}

View file

@ -1,7 +1,5 @@
package mage.abilities.keyword;
import java.io.ObjectStreamException;
import mage.abilities.MageSingleton;
import mage.abilities.OpeningHandAction;
import mage.abilities.StaticAbility;
@ -11,8 +9,9 @@ import mage.constants.Zone;
import mage.game.Game;
import mage.players.Player;
import java.io.ObjectStreamException;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class LeylineAbility extends StaticAbility implements MageSingleton, OpeningHandAction {
@ -33,7 +32,7 @@ public class LeylineAbility extends StaticAbility implements MageSingleton, Open
@Override
public String getRule() {
return "If {this} is in your opening hand, you may begin the game with it on the battlefield.";
return "If this card is in your opening hand, you may begin the game with it on the battlefield.";
}
@Override

View file

@ -0,0 +1,43 @@
package mage.abilities.keyword;
import mage.abilities.MageSingleton;
import mage.abilities.StaticAbility;
import mage.constants.Zone;
import java.io.ObjectStreamException;
/**
* @author TheElk801
*/
public class ReadAheadAbility extends StaticAbility implements MageSingleton {
private static final ReadAheadAbility instance;
static {
instance = new ReadAheadAbility();
}
private Object readResolve() throws ObjectStreamException {
return instance;
}
public static ReadAheadAbility getInstance() {
return instance;
}
private ReadAheadAbility() {
super(Zone.BATTLEFIELD, null);
this.setRuleAtTheTop(true);
}
@Override
public String getRule() {
return "read ahead";
}
@Override
public ReadAheadAbility copy() {
return instance;
}
}

View file

@ -1,10 +1,10 @@
package mage.abilities.keyword;
import mage.MageIdentifier;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.SpecialAction;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.condition.common.SuspendedCondition;
import mage.abilities.costs.VariableCostType;
import mage.abilities.costs.mana.ManaCost;
@ -14,7 +14,9 @@ import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.GainSuspendEffect;
import mage.abilities.effects.common.counter.RemoveCounterSourceEffect;
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
import mage.cards.Card;
import mage.cards.CardsImpl;
import mage.cards.ModalDoubleFacedCard;
@ -230,6 +232,34 @@ public class SuspendAbility extends SpecialAction {
return super.canActivate(playerId, game);
}
public static boolean addTimeCountersAndSuspend(Card card, int amount, Ability source, Game game) {
if (card == null || card.isCopy()) {
return false;
}
if (!Zone.EXILED.match(game.getState().getZone(card.getId()))) {
return false;
}
Player owner = game.getPlayer(card.getOwnerId());
if (owner == null) {
return false;
}
game.getExile().moveToAnotherZone(
card.getMainCard(), game,
game.getExile().createZone(
SuspendAbility.getSuspendExileId(owner.getId(), game),
"Suspended cards of " + owner.getName()
)
);
if (amount > 0) {
card.addCounters(CounterType.TIME.createInstance(amount), owner.getId(), source, game);
}
if (!card.getAbilities(game).containsClass(SuspendAbility.class)) {
game.addEffect(new GainSuspendEffect(new MageObjectReference(card, game)), source);
}
game.informPlayers(owner.getLogName() + " suspends " + amount + " - " + card.getName());
return true;
}
@Override
public String getRule() {
return ruleText;

View file

@ -0,0 +1,26 @@
package mage.abilities.keyword;
import mage.abilities.StaticAbility;
import mage.cards.Card;
import mage.constants.Zone;
/**
* @author TheElk801
*/
public class TieredAbility extends StaticAbility {
public TieredAbility(Card card) {
super(Zone.ALL, null);
this.setRuleVisible(false);
card.getSpellAbility().getModes().setChooseText("Tiered <i>(Choose one additional cost.)</i>");
}
private TieredAbility(final TieredAbility ability) {
super(ability);
}
@Override
public TieredAbility copy() {
return new TieredAbility(this);
}
}

View file

@ -1,13 +1,13 @@
package mage.abilities.mana;
import java.util.List;
import java.util.Set;
import mage.Mana;
import mage.constants.ManaType;
import mage.game.Game;
import java.util.List;
import java.util.Set;
/**
*
* @author LevelX2
*/
public interface ManaAbility {
@ -20,7 +20,7 @@ public interface ManaAbility {
* @return
*/
List<Mana> getNetMana(Game game);
/**
* Used to check the possible mana production to determine which spells
* and/or abilities can be used. (player.getPlayable()).
@ -28,7 +28,7 @@ public interface ManaAbility {
*
* @param game
* @param possibleManaInPool The possible mana already produced by other sources for this calculation option
* @return
* @return
*/
List<Mana> getNetMana(Game game, Mana possibleManaInPool);
@ -60,7 +60,12 @@ public interface ManaAbility {
* @return
*/
boolean isPoolDependant();
/**
* How many more times can this ability be activated this turn
*/
int getMaxMoreActivationsThisTurn(Game game);
ManaAbility setPoolDependant(boolean pooleDependant);
}

View file

@ -393,6 +393,7 @@ public class ManaOptions extends LinkedHashSet<Mana> {
&& onlyManaCosts
&& manaToAdd.countColored() > 0;
boolean canHaveBetterValues;
int maxRepeat = manaAbility.getMaxMoreActivationsThisTurn(game);
Mana possibleMana = new Mana();
Mana improvedMana = new Mana();
@ -401,9 +402,11 @@ public class ManaOptions extends LinkedHashSet<Mana> {
// example: {G}: Add one mana of any color
for (Mana possiblePay : ManaOptions.getPossiblePayCombinations(cost, startingMana)) {
improvedMana.setToMana(startingMana);
int currentAttempt = 0;
do {
// loop until all mana replaced by better values
canHaveBetterValues = false;
currentAttempt++;
// it's impossible to analyse all payment order (pay {R} for {1}, {Any} for {G}, etc)
// so use simple cost simulation by subtract
@ -441,7 +444,7 @@ public class ManaOptions extends LinkedHashSet<Mana> {
}
improvedMana.setToMana(possibleMana);
}
} while (repeatable && canHaveBetterValues && improvedMana.includesMana(possiblePay));
} while (repeatable && (currentAttempt < maxRepeat) && canHaveBetterValues && improvedMana.includesMana(possiblePay));
}
return oldManaWasReplaced;
}
@ -670,12 +673,14 @@ final class Comparators {
for (T first : elements) {
for (T second : elements) {
int firstGreaterThanSecond = comparator.compare(first, second);
if (firstGreaterThanSecond <= 0)
if (firstGreaterThanSecond <= 0) {
continue;
}
for (T third : elements) {
int secondGreaterThanThird = comparator.compare(second, third);
if (secondGreaterThanThird <= 0)
if (secondGreaterThanThird <= 0) {
continue;
}
int firstGreaterThanThird = comparator.compare(first, third);
if (firstGreaterThanThird <= 0) {
// Uncomment the following line to step through the failed case

View file

@ -104,6 +104,11 @@ public abstract class TriggeredManaAbility extends TriggeredAbilityImpl implemen
return poolDependant;
}
@Override
public int getMaxMoreActivationsThisTurn(Game game) {
return getRemainingTriggersLimitEachTurn(game);
}
@Override
public TriggeredManaAbility setPoolDependant(boolean poolDependant) {
this.poolDependant = poolDependant;

View file

@ -9,11 +9,12 @@ import mage.constants.Zone;
public class FoodAbility extends ActivatedAbilityImpl {
public FoodAbility(boolean named) {
public FoodAbility() {
super(Zone.BATTLEFIELD, new GainLifeEffect(3), new GenericManaCost(2));
// {2}, {T}, Sacrifice this artifact: You gain 3 life.
this.addCost(new TapSourceCost());
this.addCost(new SacrificeSourceCost().setText("sacrifice " + (named ? "{this}" : "this artifact")));
this.addCost(new SacrificeSourceCost());
}
private FoodAbility(final FoodAbility ability) {
@ -24,5 +25,5 @@ public class FoodAbility extends ActivatedAbilityImpl {
public FoodAbility copy() {
return new FoodAbility(this);
}
}

View file

@ -9,6 +9,7 @@ import mage.constants.SubType;
import mage.constants.Zone;
import mage.game.ExileZone;
import mage.game.Game;
import mage.util.CardUtil;
import java.util.Arrays;
import java.util.List;
@ -166,7 +167,7 @@ class AdventureCardSpellAbility extends SpellAbility {
+ " "
+ getManaCosts().getText()
+ " &mdash; "
+ super.getRule(false) // without cost
+ CardUtil.getTextWithFirstCharUpperCase(super.getRule(false)) // without cost
+ " <i>(Then exile this card. You may cast the creature later from exile.)</i>";
}

View file

@ -7,7 +7,7 @@ import java.awt.geom.Rectangle2D;
*/
public enum ArtRect {
NORMAL(new Rectangle2D.Double(.079f, .11f, .84f, .42f)),
RETRO(new Rectangle2D.Double(.12f, .11f, .77f, .43f)),
RETRO(new Rectangle2D.Double(.12f, .11f, .76f, .43f)),
AFTERMATH_TOP(new Rectangle2D.Double(0.075, 0.113, 0.832, 0.227)),
AFTERMATH_BOTTOM(new Rectangle2D.Double(0.546, 0.562, 0.272, 0.346)),
SPLIT_LEFT(new Rectangle2D.Double(0.152, 0.539, 0.386, 0.400)),

View file

@ -82,7 +82,7 @@ public interface Card extends MageObject, Ownerable {
return null;
}
default Card getMeldsToCard() {
default MeldCard getMeldsToCard() {
return null;
}

View file

@ -45,8 +45,8 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
protected Rarity rarity;
protected Class<? extends Card> secondSideCardClazz;
protected Class<? extends Card> meldsWithClazz;
protected Class<? extends Card> meldsToClazz;
protected Card meldsToCard;
protected Class<? extends MeldCard> meldsToClazz;
protected MeldCard meldsToCard;
protected Card secondSideCard;
protected boolean nightCard;
protected SpellAbility spellAbility;
@ -708,14 +708,14 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
}
@Override
public Card getMeldsToCard() {
public MeldCard getMeldsToCard() {
// init card on first call
if (meldsToClazz == null && meldsToCard == null) {
return null;
}
if (meldsToCard == null) {
meldsToCard = initSecondSideCard(meldsToClazz);
meldsToCard = (MeldCard) initSecondSideCard(meldsToClazz);
}
return meldsToCard;
@ -802,7 +802,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
game.fireEvent(addedOneEvent);
} else {
finalAmount--;
returnCode = false;
returnCode = false; // restricted by ADD_COUNTER
}
}
if (finalAmount > 0) {
@ -810,10 +810,15 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
addedAllEvent.setFlag(isEffectFlag);
game.fireEvent(addedAllEvent);
} else {
// TODO: must return true, cause it's not replaced here (rework Fangs of Kalonia and Spectacular Showdown)
// example from Devoted Druid
// If you can put counters on it, but that is modified by an effect (such as that of Vizier of Remedies),
// you can activate the ability even if paying the cost causes no counters to be put on Devoted Druid.
// (2018-12-07)
returnCode = false;
}
} else {
returnCode = false;
returnCode = false; // restricted by ADD_COUNTERS
}
return returnCode;
}

View file

@ -9,6 +9,7 @@ import mage.constants.SpellAbilityType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.game.Game;
import mage.util.CardUtil;
import java.util.Arrays;
import java.util.List;
@ -151,7 +152,7 @@ class OmenCardSpellAbility extends SpellAbility {
+ " "
+ getManaCosts().getText()
+ " &mdash; "
+ super.getRule(false) // without cost
+ CardUtil.getTextWithFirstCharUpperCase(super.getRule(false)) // without cost
+ " <i>(Then shuffle this card into its owner's library.)</i>";
}

View file

@ -50,6 +50,7 @@ public abstract class DeckValidator implements Serializable {
maxCopiesMap.put("Templar Knight", Integer.MAX_VALUE);
maxCopiesMap.put("Hare Apparent", Integer.MAX_VALUE);
maxCopiesMap.put("Tempest Hawk", Integer.MAX_VALUE);
maxCopiesMap.put("Cid, Timeless Artificer", Integer.MAX_VALUE);
maxCopiesMap.put("Once More with Feeling", 1);
maxCopiesMap.put("Seven Dwarves", 7);
maxCopiesMap.put("Nazgul", 9);

View file

@ -43,6 +43,9 @@ public enum CardRepository {
private Dao<CardInfo, Object> cardsDao;
// store names lists like all cards, lands, etc (it's static data and can be calculated one time only)
private static final Map<String, Set<String>> namesQueryCache = new HashMap<>();
// sets with exclusively snow basics
public static final Set<String> snowLandSetCodes = new HashSet<>(Arrays.asList(
"CSP",
@ -156,8 +159,11 @@ public enum CardRepository {
return snowLandSetCodes.contains(setCode);
}
public Set<String> getNames() {
Set<String> names = new TreeSet<>();
public synchronized Set<String> getNames() {
Set<String> names = namesQueryCache.computeIfAbsent("getNames", x -> new TreeSet<>());
if (!names.isEmpty()) {
return names;
}
try {
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
@ -172,8 +178,11 @@ public enum CardRepository {
return names;
}
public Set<String> getNonLandNames() {
Set<String> names = new TreeSet<>();
public synchronized Set<String> getNonLandNames() {
Set<String> names = namesQueryCache.computeIfAbsent("getNonLandNames", x -> new TreeSet<>());
if (!names.isEmpty()) {
return names;
}
try {
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
@ -189,8 +198,11 @@ public enum CardRepository {
return names;
}
public Set<String> getNonbasicLandNames() {
Set<String> names = new TreeSet<>();
public synchronized Set<String> getNonbasicLandNames() {
Set<String> names = namesQueryCache.computeIfAbsent("getNonbasicLandNames", x -> new TreeSet<>());
if (!names.isEmpty()) {
return names;
}
try {
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
@ -210,8 +222,11 @@ public enum CardRepository {
return names;
}
public Set<String> getNotBasicLandNames() {
Set<String> names = new TreeSet<>();
public synchronized Set<String> getNotBasicLandNames() {
Set<String> names = namesQueryCache.computeIfAbsent("getNotBasicLandNames", x -> new TreeSet<>());
if (!names.isEmpty()) {
return names;
}
try {
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
@ -227,8 +242,11 @@ public enum CardRepository {
return names;
}
public Set<String> getCreatureNames() {
Set<String> names = new TreeSet<>();
public synchronized Set<String> getCreatureNames() {
Set<String> names = namesQueryCache.computeIfAbsent("getCreatureNames", x -> new TreeSet<>());
if (!names.isEmpty()) {
return names;
}
try {
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
@ -244,8 +262,11 @@ public enum CardRepository {
return names;
}
public Set<String> getArtifactNames() {
Set<String> names = new TreeSet<>();
public synchronized Set<String> getArtifactNames() {
Set<String> names = namesQueryCache.computeIfAbsent("getArtifactNames", x -> new TreeSet<>());
if (!names.isEmpty()) {
return names;
}
try {
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
@ -261,8 +282,11 @@ public enum CardRepository {
return names;
}
public Set<String> getNonLandAndNonCreatureNames() {
Set<String> names = new TreeSet<>();
public synchronized Set<String> getNonLandAndNonCreatureNames() {
Set<String> names = namesQueryCache.computeIfAbsent("getNonLandAndNonCreatureNames", x -> new TreeSet<>());
if (!names.isEmpty()) {
return names;
}
try {
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
@ -282,8 +306,11 @@ public enum CardRepository {
return names;
}
public Set<String> getNonArtifactAndNonLandNames() {
Set<String> names = new TreeSet<>();
public synchronized Set<String> getNonArtifactAndNonLandNames() {
Set<String> names = namesQueryCache.computeIfAbsent("getNonArtifactAndNonLandNames", x -> new TreeSet<>());
if (!names.isEmpty()) {
return names;
}
try {
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");

View file

@ -36,6 +36,7 @@ public enum SubType {
MINE("Mine", SubTypeSet.NonBasicLandType),
POWER_PLANT("Power-Plant", SubTypeSet.NonBasicLandType),
TOWER("Tower", SubTypeSet.NonBasicLandType),
TOWN("Town", SubTypeSet.NonBasicLandType),
// 205.3h Enchantments have their own unique set of subtypes; these subtypes are called enchantment types.
AURA("Aura", SubTypeSet.EnchantmentType),
BACKGROUND("Background", SubTypeSet.EnchantmentType),

View file

@ -38,6 +38,7 @@ public enum CounterType {
BURDEN("burden"),
CAGE("cage"),
CARRION("carrion"),
CELL("cell"),
CHARGE("charge"),
CHIP("chip"),
CHORUS("chorus"),
@ -219,6 +220,7 @@ public enum CounterType {
STUN("stun"),
SUPPLY("supply"),
SUSPECT("suspect"),
TAKEOVER("takeover"),
TASK("task"),
THEFT("theft"),
TIDE("tide"),

View file

@ -1,10 +1,7 @@
package mage.filter;
import mage.ObjectColor;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.constants.TargetController;
import mage.constants.*;
import mage.counters.CounterType;
import mage.filter.common.*;
import mage.filter.predicate.Predicates;
@ -61,6 +58,12 @@ public final class StaticFilters {
FILTER_CARD_ENCHANTMENT.setLockedFilter(true);
}
public static final FilterCard FILTER_CARD_ENCHANTMENTS = new FilterEnchantmentCard("enchantment cards");
static {
FILTER_CARD_ENCHANTMENTS.setLockedFilter(true);
}
public static final FilterArtifactCard FILTER_CARD_ARTIFACT = new FilterArtifactCard();
static {
@ -139,6 +142,12 @@ public final class StaticFilters {
FILTER_CARD_ARTIFACT_FROM_YOUR_GRAVEYARD.setLockedFilter(true);
}
public static final FilterCard FILTER_CARD_LAND_FROM_YOUR_GRAVEYARD = new FilterLandCard("land card from your graveyard");
static {
FILTER_CARD_LAND_FROM_YOUR_GRAVEYARD.setLockedFilter(true);
}
public static final FilterCreatureCard FILTER_CARD_CREATURE_A_GRAVEYARD = new FilterCreatureCard("creature card from a graveyard");
static {
@ -329,7 +338,7 @@ public final class StaticFilters {
FILTER_PERMANENTS_ARTIFACT_CREATURE.setLockedFilter(true);
}
public static final FilterControlledArtifactPermanent FILTER_ARTIFACT_NON_CREATURE = new FilterControlledArtifactPermanent("noncreature artifact");
public static final FilterArtifactPermanent FILTER_ARTIFACT_NON_CREATURE = new FilterArtifactPermanent("noncreature artifact");
static {
FILTER_ARTIFACT_NON_CREATURE.add(Predicates.not(CardType.CREATURE.getPredicate()));
@ -1033,6 +1042,21 @@ public final class StaticFilters {
FILTER_SPELL_KICKED_A.setLockedFilter(true);
}
public static final FilterSpell FILTER_SPELL_NO_MANA_SPENT = new FilterSpell("a spell, if no mana was spent to cast it");
static {
FILTER_SPELL_NO_MANA_SPENT.add(new ManaSpentToCastPredicate(ComparisonType.EQUAL_TO, 0));
FILTER_SPELL_NO_MANA_SPENT.setLockedFilter(true);
}
public static final FilterSpell FILTER_NONCREATURE_SPELL_FOUR_MANA_SPENT = new FilterSpell("a noncreature spell, if at least four mana was spent to cast it");
static {
FILTER_NONCREATURE_SPELL_FOUR_MANA_SPENT.add(Predicates.not(CardType.CREATURE.getPredicate()));
FILTER_NONCREATURE_SPELL_FOUR_MANA_SPENT.add(new ManaSpentToCastPredicate(ComparisonType.MORE_THAN, 3));
FILTER_NONCREATURE_SPELL_FOUR_MANA_SPENT.setLockedFilter(true);
}
public static final FilterPermanent FILTER_PERMANENT_TOKEN = new FilterPermanent("token");
static {

View file

@ -0,0 +1,25 @@
package mage.filter.predicate.mageobject;
import mage.constants.ComparisonType;
import mage.filter.predicate.IntComparePredicate;
import mage.game.stack.StackObject;
/**
* @author TheElk801
*/
public class ManaSpentToCastPredicate extends IntComparePredicate<StackObject> {
public ManaSpentToCastPredicate(ComparisonType type, int value) {
super(type, value);
}
@Override
protected int getInputValue(StackObject input) {
return input.getStackAbility().getManaCostsToPay().getUsedManaToPay().count();
}
@Override
public String toString() {
return "ManaSpent" + super.toString();
}
}

View file

@ -1522,7 +1522,7 @@ public abstract class GameImpl implements Game {
UUID[] players = getPlayers().keySet().toArray(new UUID[0]);
UUID playerId;
while (!hasEnded()) {
playerId = players[RandomUtil.nextInt(players.length)];
playerId = players[RandomUtil.nextInt(players.length)]; // test game
Player player = getPlayer(playerId);
if (player != null && player.canRespond()) {
fireInformEvent(state.getPlayer(playerId).getLogName() + " won the toss");
@ -1810,14 +1810,21 @@ public abstract class GameImpl implements Game {
protected void resolve() {
StackObject top = null;
boolean wasError = false;
try {
top = state.getStack().peek();
top.resolve(this);
resetControlAfterSpellResolve(top.getId());
} catch (Throwable e) {
// workaround to show real error in tests instead checkInfiniteLoop
wasError = true;
throw e;
} finally {
if (top != null) {
state.getStack().remove(top, this); // seems partly redundant because move card from stack to grave is already done and the stack removed
checkInfiniteLoop(top.getSourceId());
if (!wasError) {
checkInfiniteLoop(top.getSourceId());
}
if (!getTurn().isEndTurnRequested()) {
while (state.hasSimultaneousEvents()) {
state.handleSimultaneousEvent(this);
@ -3746,7 +3753,7 @@ public abstract class GameImpl implements Game {
@Override
public void cheat(UUID ownerId, List<Card> library, List<Card> hand, List<PutToBattlefieldInfo> battlefield, List<Card> graveyard, List<Card> command, List<Card> exiled) {
// fake test ability for triggers and events
Ability fakeSourceAbilityTemplate = new SimpleStaticAbility(Zone.OUTSIDE, new InfoEffect("adding testing cards"));
Ability fakeSourceAbilityTemplate = new SimpleStaticAbility(Zone.OUTSIDE, new InfoEffect("fake ability"));
fakeSourceAbilityTemplate.setControllerId(ownerId);
Player player = getPlayer(ownerId);
@ -4014,7 +4021,6 @@ public abstract class GameImpl implements Game {
playerObject.resetStoredBookmark(this);
playerObject.resetPlayerPassedActions();
playerObject.abort();
}
}
fireUpdatePlayersEvent();

View file

@ -724,7 +724,7 @@ public class GameState implements Serializable, Copyable<GameState> {
/**
* Returns a list of all players of the game ignoring range or if a player
* has lost or left the game.
*
* <p>
* Warning, it's ignore range, must be used by game engine only.
*/
public PlayerList getPlayerList() {
@ -734,7 +734,7 @@ public class GameState implements Serializable, Copyable<GameState> {
/**
* Returns a list of all active players of the game, setting the playerId to
* the current player of the list.
*
* <p>
* Warning, it's ignore range, must be used by game engine only.
*/
public PlayerList getPlayerList(UUID playerId) {
@ -1369,8 +1369,9 @@ public class GameState implements Serializable, Copyable<GameState> {
* @param valueId
* @param value
*/
public void setValue(String valueId, Object value) {
public <T> T setValue(String valueId, T value) {
values.put(valueId, value);
return value;
}
/**

View file

@ -458,10 +458,14 @@ public final class ZonesHandler {
target.setRequired(true);
while (player.canRespond() && cards.size() > 1) {
player.choose(Outcome.Neutral, cards, target, source, game);
UUID targetObjectId = target.getFirstTarget();
order.add(cards.get(targetObjectId, game));
cards.remove(targetObjectId);
target.clearChosen();
Card card = cards.get(target.getFirstTarget(), game);
if (card != null) {
order.add(card);
cards.remove(target.getFirstTarget());
target.clearChosen();
} else {
break;
}
}
order.addAll(cards.getCards(game));
return order;

View file

@ -382,7 +382,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
break;
}
int damageAssigned = 0;
damageAssigned = player.getAmount(0, damage, "Assign damage to " + defendingCreature.getName(), game);
damageAssigned = player.getAmount(0, damage, "Assign damage to " + defendingCreature.getName(), null, game);
assigned.put(defendingCreature.getId(), damageAssigned);
damage -= damageAssigned;
}
@ -755,9 +755,9 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
*/
private int getDamageValueFromPermanent(Permanent permanent, Game game) {
if (game.getCombat().useToughnessForDamage(permanent, game)) {
return permanent.getToughness().getValue();
return Math.max(0, permanent.getToughness().getValue());
} else {
return permanent.getPower().getValue();
return Math.max(0, permanent.getPower().getValue());
}
}

View file

@ -5,6 +5,8 @@ import mage.abilities.Ability;
import mage.abilities.common.SpellCastControllerTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.constants.SetTargetPointer;
import mage.constants.Zone;
import mage.filter.FilterSpell;
import mage.filter.common.FilterInstantOrSorcerySpell;
import mage.filter.predicate.mageobject.ColorPredicate;
@ -26,8 +28,8 @@ public final class JayaFieryNegotiatorEmblem extends Emblem {
// 8: You get an emblem with "Whenever you cast a red instant or sorcery spell, copy it twice. You may choose new targets for the copies."
public JayaFieryNegotiatorEmblem() {
super("Emblem Jaya");
this.getAbilities().add(new SpellCastControllerTriggeredAbility(
new JayaFieryNegotiatorEmblemEffect(), filter, false
this.getAbilities().add(new SpellCastControllerTriggeredAbility(Zone.COMMAND,
new JayaFieryNegotiatorEmblemEffect(), filter, false, SetTargetPointer.NONE
));
}

View file

@ -0,0 +1,37 @@
package mage.game.command.emblems;
import mage.abilities.Ability;
import mage.abilities.common.DiesCreatureTriggeredAbility;
import mage.abilities.effects.common.LoseLifeSourceControllerEffect;
import mage.abilities.effects.common.LoseLifeTargetEffect;
import mage.constants.Zone;
import mage.filter.StaticFilters;
import mage.game.command.Emblem;
import mage.target.common.TargetOpponent;
/**
* @author TheElk801
*/
public final class SephirothOneWingedAngelEmblem extends Emblem {
// you get an emblem with "Whenever a creature dies, target opponent loses 1 life and you gain 1 life."
public SephirothOneWingedAngelEmblem() {
super("Emblem Sephiroth");
Ability ability = new DiesCreatureTriggeredAbility(
Zone.COMMAND, new LoseLifeTargetEffect(1), false,
StaticFilters.FILTER_PERMANENT_A_CREATURE, false
);
ability.addEffect(new LoseLifeSourceControllerEffect(1).concatBy("and"));
ability.addTarget(new TargetOpponent());
this.getAbilities().add(ability);
}
private SephirothOneWingedAngelEmblem(final SephirothOneWingedAngelEmblem card) {
super(card);
}
@Override
public SephirothOneWingedAngelEmblem copy() {
return new SephirothOneWingedAngelEmblem(this);
}
}

View file

@ -0,0 +1,72 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SpellCastOpponentTriggeredAbility;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.keyword.FirstStrikeAbility;
import mage.abilities.keyword.VigilanceAbility;
import mage.constants.*;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.permanent.Permanent;
/**
* @author TheElk801
*/
public final class AlienAngelToken extends TokenImpl {
public AlienAngelToken() {
super("Alien Angel Token", "2/2 black Alien Angel artifact creature token with first strike, vigilance, and \"Whenever an opponent casts a creature spell, this token isn't a creature until end of turn.\"");
cardType.add(CardType.ARTIFACT);
cardType.add(CardType.CREATURE);
color.setBlack(true);
subtype.add(SubType.ALIEN);
subtype.add(SubType.ANGEL);
power = new MageInt(2);
toughness = new MageInt(2);
addAbility(FirstStrikeAbility.getInstance());
addAbility(VigilanceAbility.getInstance());
addAbility(new SpellCastOpponentTriggeredAbility(
new AlienAngelTokenEffect(), StaticFilters.FILTER_SPELL_A_CREATURE, false
));
}
private AlienAngelToken(final AlienAngelToken token) {
super(token);
}
public AlienAngelToken copy() {
return new AlienAngelToken(this);
}
}
class AlienAngelTokenEffect extends ContinuousEffectImpl {
AlienAngelTokenEffect() {
super(Duration.EndOfTurn, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.UnboostCreature);
staticText = "this token isn't a creature until end of turn";
}
private AlienAngelTokenEffect(final AlienAngelTokenEffect effect) {
super(effect);
}
@Override
public AlienAngelTokenEffect copy() {
return new AlienAngelTokenEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
if (permanent == null) {
discard();
return false;
}
permanent.removeAllSubTypes(game, SubTypeSet.CreatureType);
permanent.removeCardType(game, CardType.CREATURE);
return true;
}
}

View file

@ -0,0 +1,32 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
/**
* @author TheElk801
*/
public final class AngeloToken extends TokenImpl {
public AngeloToken() {
super("Angelo", "Angelo, a legendary 1/1 green and white Dog creature token");
supertype.add(SuperType.LEGENDARY);
cardType.add(CardType.CREATURE);
subtype.add(SubType.DOG);
color.setGreen(true);
color.setWhite(true);
power = new MageInt(1);
toughness = new MageInt(1);
}
private AngeloToken(final AngeloToken token) {
super(token);
}
public AngeloToken copy() {
return new AngeloToken(this);
}
}

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