forked from External/mage
Merge pull request 'master' (#26) from External/mage:master into master
All checks were successful
/ build_release (push) Successful in 14m41s
All checks were successful
/ build_release (push) Successful in 14m41s
Reviewed-on: #26
This commit is contained in:
commit
9a2382cd2c
788 changed files with 29882 additions and 3734 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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(" — ");
|
||||
} else if (mode.getPawPrintValue() > 0) {
|
||||
for (int i = 0; i < mode.getPawPrintValue(); ++i) {
|
||||
sb.append("{P}");
|
||||
}
|
||||
sb.append(" — ");
|
||||
} else {
|
||||
sb.append("&bull ");
|
||||
sb.append("&bull ");
|
||||
}
|
||||
sb.append(mode.getEffects().getTextStartingUpperCase(mode));
|
||||
sb.append("<br>");
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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, " — ");
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 ";
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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.";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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(' ');
|
||||
|
|
|
|||
|
|
@ -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(' ');
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 + " — " + ability.getRule();
|
||||
this.staticText = "&bull " + modeChoice + " — " + CardUtil.getTextWithFirstCharUpperCase(ability.getRule());
|
||||
this.ability = ability;
|
||||
this.modeChoice = modeChoice;
|
||||
this.ability.setRuleVisible(false);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
26
Mage/src/main/java/mage/abilities/keyword/TieredAbility.java
Normal file
26
Mage/src/main/java/mage/abilities/keyword/TieredAbility.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
+ " — "
|
||||
+ 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>";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ public interface Card extends MageObject, Ownerable {
|
|||
return null;
|
||||
}
|
||||
|
||||
default Card getMeldsToCard() {
|
||||
default MeldCard getMeldsToCard() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
+ " — "
|
||||
+ super.getRule(false) // without cost
|
||||
+ CardUtil.getTextWithFirstCharUpperCase(super.getRule(false)) // without cost
|
||||
+ " <i>(Then shuffle this card into its owner's library.)</i>";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue