mirror of
https://github.com/magefree/mage.git
synced 2025-12-26 13:32:06 -08:00
Merge branch 'master' into wish
This commit is contained in:
commit
96ca260109
213 changed files with 3962 additions and 528 deletions
|
|
@ -552,8 +552,21 @@ public interface Ability extends Controllable, Serializable {
|
|||
|
||||
Ability addHint(Hint hint);
|
||||
|
||||
/**
|
||||
* For abilities with static icons
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
List<CardIcon> getIcons();
|
||||
|
||||
/**
|
||||
* For abilities with dynamic icons
|
||||
*
|
||||
* @param game can be null for static calls like copies
|
||||
* @return
|
||||
*/
|
||||
List<CardIcon> getIcons(Game game);
|
||||
|
||||
Ability addIcon(CardIcon cardIcon);
|
||||
|
||||
Ability addCustomOutcome(Outcome customOutcome);
|
||||
|
|
|
|||
|
|
@ -1342,7 +1342,12 @@ public abstract class AbilityImpl implements Ability {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<CardIcon> getIcons() {
|
||||
final public List<CardIcon> getIcons() {
|
||||
return getIcons(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CardIcon> getIcons(Game game) {
|
||||
return this.icons;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ public class ConstellationAbility extends TriggeredAbilityImpl {
|
|||
|
||||
public ConstellationAbility(Effect effect) {
|
||||
this(effect, false);
|
||||
setAbilityWord(AbilityWord.CONSTELLATION);
|
||||
}
|
||||
|
||||
public ConstellationAbility(Effect effect, boolean optional) {
|
||||
|
|
@ -32,6 +31,7 @@ public class ConstellationAbility extends TriggeredAbilityImpl {
|
|||
public ConstellationAbility(Effect effect, boolean optional, boolean thisOr) {
|
||||
super(Zone.BATTLEFIELD, effect, optional);
|
||||
this.thisOr = thisOr;
|
||||
setAbilityWord(AbilityWord.CONSTELLATION);
|
||||
}
|
||||
|
||||
public ConstellationAbility(final ConstellationAbility ability) {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,8 @@ public class StriveAbility extends SimpleStaticAbility {
|
|||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return new StringBuilder("this spell costs ").append(striveCost).append(" more to cast for each target beyond the first.").toString();
|
||||
return abilityWord.formatWord() + "This spell costs "
|
||||
+ striveCost + " more to cast for each target beyond the first.";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -118,24 +118,22 @@ public class BeginningOfEndStepTriggeredAbility extends TriggeredAbilityImpl {
|
|||
}
|
||||
|
||||
private String generateConditionString() {
|
||||
if (interveningIfClauseCondition != null) {
|
||||
if (interveningIfClauseCondition.toString().startsWith("if")) {
|
||||
|
||||
//Fixes punctuation on multiple sentence if-then construction
|
||||
// see -- Colfenor's Urn
|
||||
if (interveningIfClauseCondition.toString().endsWith(".")) {
|
||||
return interveningIfClauseCondition.toString() + " ";
|
||||
}
|
||||
|
||||
return interveningIfClauseCondition.toString() + ", ";
|
||||
} else {
|
||||
return "if {this} is " + interveningIfClauseCondition.toString() + ", ";
|
||||
if (interveningIfClauseCondition == null) {
|
||||
switch (getZone()) {
|
||||
case GRAVEYARD:
|
||||
return "if {this} is in your graveyard, ";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
switch (getZone()) {
|
||||
case GRAVEYARD:
|
||||
return "if {this} is in your graveyard, ";
|
||||
String clauseText = interveningIfClauseCondition.toString();
|
||||
if (clauseText.startsWith("if")) {
|
||||
//Fixes punctuation on multiple sentence if-then construction
|
||||
// see -- Colfenor's Urn
|
||||
if (clauseText.endsWith(".")) {
|
||||
return clauseText + " ";
|
||||
}
|
||||
return clauseText + ", ";
|
||||
}
|
||||
return "";
|
||||
return "if " + clauseText + ", ";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import mage.game.Game;
|
|||
import mage.game.events.GameEvent;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class CastOnlyIfConditionIsTrueEffect extends ContinuousRuleModifyingEffectImpl {
|
||||
|
|
@ -52,7 +51,7 @@ public class CastOnlyIfConditionIsTrueEffect extends ContinuousRuleModifyingEffe
|
|||
private String setText() {
|
||||
StringBuilder sb = new StringBuilder("cast this spell only ");
|
||||
if (condition != null) {
|
||||
sb.append(' ').append(condition.toString());
|
||||
sb.append(condition);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
|
||||
package mage.abilities.common;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import mage.abilities.TriggeredAbilityImpl;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.constants.AbilityWord;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.DamagedEvent;
|
||||
import mage.game.events.DamagedPermanentBatchEvent;
|
||||
import mage.game.events.GameEvent;
|
||||
|
||||
/**
|
||||
|
|
@ -15,7 +18,6 @@ import mage.game.events.GameEvent;
|
|||
public class DealtDamageToSourceTriggeredAbility extends TriggeredAbilityImpl {
|
||||
|
||||
private final boolean useValue;
|
||||
private boolean usedForCombatDamageStep;
|
||||
|
||||
public DealtDamageToSourceTriggeredAbility(Effect effect, boolean optional) {
|
||||
this(effect, optional, false);
|
||||
|
|
@ -28,7 +30,6 @@ public class DealtDamageToSourceTriggeredAbility extends TriggeredAbilityImpl {
|
|||
public DealtDamageToSourceTriggeredAbility(Effect effect, boolean optional, boolean enrage, boolean useValue) {
|
||||
super(Zone.BATTLEFIELD, effect, optional);
|
||||
this.useValue = useValue;
|
||||
this.usedForCombatDamageStep = false;
|
||||
if (enrage) {
|
||||
this.setAbilityWord(AbilityWord.ENRAGE);
|
||||
}
|
||||
|
|
@ -37,7 +38,6 @@ public class DealtDamageToSourceTriggeredAbility extends TriggeredAbilityImpl {
|
|||
public DealtDamageToSourceTriggeredAbility(final DealtDamageToSourceTriggeredAbility ability) {
|
||||
super(ability);
|
||||
this.useValue = ability.useValue;
|
||||
this.usedForCombatDamageStep = ability.usedForCombatDamageStep;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -47,30 +47,34 @@ public class DealtDamageToSourceTriggeredAbility extends TriggeredAbilityImpl {
|
|||
|
||||
@Override
|
||||
public boolean checkEventType(GameEvent event, Game game) {
|
||||
return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT || event.getType() == GameEvent.EventType.COMBAT_DAMAGE_STEP_POST;
|
||||
return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT_BATCH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
if (event.getType() == GameEvent.EventType.DAMAGED_PERMANENT && event.getTargetId().equals(getSourceId())) {
|
||||
if (useValue) {
|
||||
// TODO: this ability should only trigger once for multiple creatures dealing combat damage.
|
||||
// If the damaged creature uses the amount (e.g. Boros Reckoner), this will still trigger separately instead of all at once
|
||||
getEffects().setValue("damage", event.getAmount());
|
||||
return true;
|
||||
} else {
|
||||
if (((DamagedEvent) event).isCombatDamage()) {
|
||||
if (!usedForCombatDamageStep) {
|
||||
usedForCombatDamageStep = true;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event == null || game == null || this.getSourceId() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int damage = 0;
|
||||
DamagedPermanentBatchEvent dEvent = (DamagedPermanentBatchEvent) event;
|
||||
for (DamagedEvent damagedEvent : dEvent.getEvents()) {
|
||||
UUID targetID = damagedEvent.getTargetId();
|
||||
if (targetID == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (targetID == this.getSourceId()) {
|
||||
damage += damagedEvent.getAmount();
|
||||
}
|
||||
}
|
||||
if (event.getType() == GameEvent.EventType.COMBAT_DAMAGE_STEP_POST) {
|
||||
usedForCombatDamageStep = false;
|
||||
|
||||
if (damage > 0) {
|
||||
if (this.useValue) {
|
||||
this.getEffects().setValue("damage", damage);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import mage.game.events.ZoneChangeEvent;
|
|||
import mage.target.targetpointer.FixedTarget;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class PutCardIntoGraveFromAnywhereAllTriggeredAbility extends TriggeredAbilityImpl {
|
||||
|
|
@ -42,7 +41,8 @@ public class PutCardIntoGraveFromAnywhereAllTriggeredAbility extends TriggeredAb
|
|||
this.filter.add(targetController.getOwnerPredicate());
|
||||
StringBuilder sb = new StringBuilder("Whenever ");
|
||||
sb.append(filter.getMessage());
|
||||
sb.append(" is put into ");
|
||||
sb.append(filter.getMessage().startsWith("one or more") ? " are" : "is");
|
||||
sb.append(" put into ");
|
||||
switch (targetController) {
|
||||
case OPPONENT:
|
||||
sb.append("an opponent's");
|
||||
|
|
@ -103,6 +103,6 @@ public class PutCardIntoGraveFromAnywhereAllTriggeredAbility extends TriggeredAb
|
|||
|
||||
@Override
|
||||
public String getTriggerPhrase() {
|
||||
return ruleText ;
|
||||
return ruleText;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ public enum EquippedSourceCondition implements Condition {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "equipped";
|
||||
return "{this} is equipped";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ import mage.game.Game;
|
|||
import mage.watchers.common.ManaSpentToCastWatcher;
|
||||
|
||||
public enum ManacostVariableValue implements DynamicValue {
|
||||
REGULAR, ETB;
|
||||
|
||||
REGULAR, // if you need X on cast/activate (in stack)
|
||||
ETB; // if you need X after ETB (in battlefield)
|
||||
|
||||
@Override
|
||||
public int calculate(Game game, Ability sourceAbility, Effect effect) {
|
||||
|
|
@ -15,7 +17,10 @@ public enum ManacostVariableValue implements DynamicValue {
|
|||
return sourceAbility.getManaCostsToPay().getX();
|
||||
}
|
||||
ManaSpentToCastWatcher watcher = game.getState().getWatcher(ManaSpentToCastWatcher.class);
|
||||
return watcher != null ? watcher.getAndResetLastXValue(sourceAbility.getSourceId()) : sourceAbility.getManaCostsToPay().getX();
|
||||
if (watcher != null) {
|
||||
return watcher.getAndResetLastXValue(sourceAbility);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -77,4 +77,9 @@ public class CreateDelayedTriggeredAbilityEffect extends OneShotEffect {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(String key, Object value) {
|
||||
ability.getEffects().setValue(key, value);
|
||||
super.setValue(key, value);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect {
|
|||
private boolean isntLegendary = false;
|
||||
private int startingLoyalty = -1;
|
||||
private final List<Ability> additionalAbilities = new ArrayList();
|
||||
private Permanent savedPermanent = null;
|
||||
|
||||
public CreateTokenCopyTargetEffect(boolean useLKI) {
|
||||
this();
|
||||
|
|
@ -133,7 +134,9 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect {
|
|||
targetId = getTargetPointer().getFirst(game, source);
|
||||
}
|
||||
Permanent permanent;
|
||||
if (useLKI) {
|
||||
if (savedPermanent != null) {
|
||||
permanent = savedPermanent;
|
||||
} else if (useLKI) {
|
||||
permanent = getTargetPointer().getFirstTargetPermanentOrLKI(game, source);
|
||||
} else {
|
||||
permanent = game.getPermanentOrLKIBattlefield(targetId);
|
||||
|
|
@ -319,4 +322,8 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect {
|
|||
public void addAdditionalAbilities(Ability... abilities) {
|
||||
Arrays.stream(abilities).forEach(this.additionalAbilities::add);
|
||||
}
|
||||
|
||||
public void setSavedPermanent(Permanent savedPermanent) {
|
||||
this.savedPermanent = savedPermanent;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package mage.abilities.effects.common;
|
|||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.effects.ContinuousEffect;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect;
|
||||
import mage.cards.Card;
|
||||
|
|
@ -21,21 +20,28 @@ public class ExileTopXMayPlayUntilEndOfTurnEffect extends OneShotEffect {
|
|||
|
||||
private final int amount;
|
||||
private final boolean showHint;
|
||||
private final Duration duration;
|
||||
|
||||
public ExileTopXMayPlayUntilEndOfTurnEffect(int amount) {
|
||||
this(amount, false);
|
||||
}
|
||||
|
||||
public ExileTopXMayPlayUntilEndOfTurnEffect(int amount, boolean showHint) {
|
||||
this(amount, showHint, Duration.EndOfTurn);
|
||||
}
|
||||
|
||||
public ExileTopXMayPlayUntilEndOfTurnEffect(int amount, boolean showHint, Duration duration) {
|
||||
super(Outcome.Benefit);
|
||||
this.amount = amount;
|
||||
this.showHint = showHint;
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
private ExileTopXMayPlayUntilEndOfTurnEffect(final ExileTopXMayPlayUntilEndOfTurnEffect effect) {
|
||||
super(effect);
|
||||
this.amount = effect.amount;
|
||||
this.showHint = effect.showHint;
|
||||
this.duration = effect.duration;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -47,21 +53,21 @@ public class ExileTopXMayPlayUntilEndOfTurnEffect extends OneShotEffect {
|
|||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
MageObject sourceObject = game.getObject(source.getSourceId());
|
||||
if (controller != null && sourceObject != null) {
|
||||
Set<Card> cards = controller.getLibrary().getTopCards(game, amount);
|
||||
if (!cards.isEmpty()) {
|
||||
controller.moveCardsToExile(cards, source, game, true, source.getSourceId(), sourceObject.getIdName());
|
||||
// remove cards that could not be moved to exile
|
||||
cards.removeIf(card -> !Zone.EXILED.equals(game.getState().getZone(card.getId())));
|
||||
if (!cards.isEmpty()) {
|
||||
ContinuousEffect effect = new PlayFromNotOwnHandZoneTargetEffect(Zone.EXILED, Duration.EndOfTurn);
|
||||
effect.setTargetPointer(new FixedTargets(cards, game));
|
||||
game.addEffect(effect, source);
|
||||
}
|
||||
}
|
||||
if (controller == null || sourceObject == null) {
|
||||
return false;
|
||||
}
|
||||
Set<Card> cards = controller.getLibrary().getTopCards(game, amount);
|
||||
if (cards.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
controller.moveCardsToExile(cards, source, game, true, source.getSourceId(), sourceObject.getIdName());
|
||||
// remove cards that could not be moved to exile
|
||||
cards.removeIf(card -> !Zone.EXILED.equals(game.getState().getZone(card.getId())));
|
||||
if (!cards.isEmpty()) {
|
||||
game.addEffect(new PlayFromNotOwnHandZoneTargetEffect(Zone.EXILED, duration)
|
||||
.setTargetPointer(new FixedTargets(cards, game)), source);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -71,11 +77,15 @@ public class ExileTopXMayPlayUntilEndOfTurnEffect extends OneShotEffect {
|
|||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (amount == 1) {
|
||||
sb.append("exile the top card of your library. You may play that card this turn");
|
||||
sb.append("exile the top card of your library. ");
|
||||
sb.append(CardUtil.getTextWithFirstCharUpperCase(duration.toString()));
|
||||
sb.append(", you may play that card");
|
||||
} else {
|
||||
sb.append("exile the top ");
|
||||
sb.append(CardUtil.numberToText(amount));
|
||||
sb.append(" cards of your library. Until end of turn, you may play cards exiled this way");
|
||||
sb.append(" cards of your library. ");
|
||||
sb.append(CardUtil.getTextWithFirstCharUpperCase(duration.toString()));
|
||||
sb.append(", you may play cards exiled this way");
|
||||
}
|
||||
if (showHint) {
|
||||
sb.append(". <i>(You still pay its costs. You can play a land this way only if you have an available land play remaining.)</i>");
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import mage.game.permanent.Permanent;
|
|||
/**
|
||||
* @author LevelX2
|
||||
*/
|
||||
|
||||
public class CantBeBlockedByCreaturesAllEffect extends RestrictionEffect {
|
||||
|
||||
private final FilterCreaturePermanent filterBlockedBy;
|
||||
|
|
@ -20,8 +19,9 @@ public class CantBeBlockedByCreaturesAllEffect extends RestrictionEffect {
|
|||
super(duration);
|
||||
this.filterCreatures = filterCreatures;
|
||||
this.filterBlockedBy = filterBlockedBy;
|
||||
staticText = new StringBuilder(filterCreatures.getMessage()).append(" can't be blocked ")
|
||||
.append(filterBlockedBy.getMessage().startsWith("except by") ? "" : "by ").append(filterBlockedBy.getMessage()).toString();
|
||||
staticText = filterCreatures.getMessage() + " can't be blocked "
|
||||
+ (filterBlockedBy.getMessage().startsWith("except by") ? "" : "by ")
|
||||
+ filterBlockedBy.getMessage();
|
||||
}
|
||||
|
||||
public CantBeBlockedByCreaturesAllEffect(final CantBeBlockedByCreaturesAllEffect effect) {
|
||||
|
|
|
|||
|
|
@ -182,10 +182,13 @@ public class BecomesCreatureTargetEffect extends ContinuousEffectImpl {
|
|||
sb.append(token.getDescription());
|
||||
sb.append(' ').append(duration.toString());
|
||||
if (addStillALandText) {
|
||||
if (!sb.toString().endsWith("\" ")) {
|
||||
sb.append(". ");
|
||||
}
|
||||
if (target.getMaxNumberOfTargets() > 1) {
|
||||
sb.append(". They're still lands");
|
||||
sb.append("They're still lands");
|
||||
} else {
|
||||
sb.append(". It's still a land");
|
||||
sb.append("It's still a land");
|
||||
}
|
||||
}
|
||||
return sb.toString().replace(" .", ".");
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import mage.target.Target;
|
|||
import mage.target.Targets;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
|
|
@ -28,8 +28,8 @@ public class GainAbilityWithAttachmentEffect extends ContinuousEffectImpl {
|
|||
|
||||
private final Effects effects = new Effects();
|
||||
private final Targets targets = new Targets();
|
||||
private final Costs costs = new CostsImpl();
|
||||
private final UseAttachedCost useAttachedCost;
|
||||
private final Costs<Cost> costs = new CostsImpl<>();
|
||||
protected final UseAttachedCost useAttachedCost;
|
||||
|
||||
public GainAbilityWithAttachmentEffect(String rule, Effect effect, Target target, UseAttachedCost attachedCost, Cost... costs) {
|
||||
this(rule, new Effects(effect), new Targets(target), attachedCost, costs);
|
||||
|
|
@ -40,12 +40,12 @@ public class GainAbilityWithAttachmentEffect extends ContinuousEffectImpl {
|
|||
this.staticText = rule;
|
||||
this.effects.addAll(effects);
|
||||
this.targets.addAll(targets);
|
||||
this.costs.addAll(Arrays.asList(costs));
|
||||
Collections.addAll(this.costs, costs);
|
||||
this.useAttachedCost = attachedCost;
|
||||
this.generateGainAbilityDependencies(makeAbility(this.effects, this.targets, this.costs), null);
|
||||
this.generateGainAbilityDependencies(makeAbility(null, null), null);
|
||||
}
|
||||
|
||||
public GainAbilityWithAttachmentEffect(final GainAbilityWithAttachmentEffect effect) {
|
||||
protected GainAbilityWithAttachmentEffect(final GainAbilityWithAttachmentEffect effect) {
|
||||
super(effect);
|
||||
this.effects.addAll(effect.effects);
|
||||
this.targets.addAll(effect.targets);
|
||||
|
|
@ -87,14 +87,13 @@ public class GainAbilityWithAttachmentEffect extends ContinuousEffectImpl {
|
|||
if (permanent == null) {
|
||||
return true;
|
||||
}
|
||||
Ability ability = makeAbility(this.effects, this.targets, this.costs);
|
||||
Ability ability = makeAbility(game, source);
|
||||
ability.getEffects().setValue("attachedPermanent", game.getPermanent(source.getSourceId()));
|
||||
ability.addCost(useAttachedCost.copy().setMageObjectReference(source, game));
|
||||
permanent.addAbility(ability, source.getSourceId(), game);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Ability makeAbility(Effects effects, Targets targets, Cost... costs) {
|
||||
protected Ability makeAbility(Game game, Ability source) {
|
||||
Ability ability = new SimpleActivatedAbility(null, null);
|
||||
for (Effect effect : effects) {
|
||||
if (effect == null) {
|
||||
|
|
@ -108,12 +107,15 @@ public class GainAbilityWithAttachmentEffect extends ContinuousEffectImpl {
|
|||
}
|
||||
ability.addTarget(target);
|
||||
}
|
||||
for (Cost cost : costs) {
|
||||
for (Cost cost : this.costs) {
|
||||
if (cost == null) {
|
||||
continue;
|
||||
}
|
||||
ability.addCost(cost.copy());
|
||||
}
|
||||
if (source != null && game != null) {
|
||||
ability.addCost(useAttachedCost.copy().setMageObjectReference(source, game));
|
||||
}
|
||||
return ability;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ public class SpellCostReductionForEachSourceEffect extends CostModificationEffec
|
|||
if (reduceManaCosts != null) {
|
||||
// color reduce
|
||||
ManaCosts<ManaCost> needReduceMana = new ManaCostsImpl<>();
|
||||
for (int i = 0; i <= needReduceAmount; i++) {
|
||||
for (int i = 0; i < needReduceAmount; i++) {
|
||||
needReduceMana.add(reduceManaCosts.copy());
|
||||
}
|
||||
CardUtil.adjustCost((SpellAbility) abilityToModify, needReduceMana, false);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ public class SearchLibraryGraveyardPutInHandEffect extends OneShotEffect {
|
|||
this.filter = filter;
|
||||
this.forceToSearchBoth = forceToSearchBoth;
|
||||
staticText = (youMay ? "you may " : "") + "search your library and" + (forceToSearchBoth ? "" : "/or") + " graveyard for a card named " + filter.getMessage()
|
||||
+ ", reveal it, and put it into your hand. " + (forceToSearchBoth ? "Then shuffle" : "If you search your library this way, shuffle");
|
||||
+ ", reveal it, and put it into your hand. " + (forceToSearchBoth ? "Then shuffle" : "If you search your library this way, shuffle it");
|
||||
}
|
||||
|
||||
public SearchLibraryGraveyardPutInHandEffect(final SearchLibraryGraveyardPutInHandEffect effect) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package mage.abilities.icon;
|
||||
|
||||
/**
|
||||
* For GUI: different icons category can go to different position/panels on the card
|
||||
*
|
||||
* @author JayDi85
|
||||
*/
|
||||
public enum CardIconCategory {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package mage.abilities.icon;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class CardIconImpl implements CardIcon {
|
||||
public class CardIconImpl implements CardIcon, Serializable {
|
||||
|
||||
private final CardIconType cardIconType;
|
||||
private final String text;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ public enum CardIconType {
|
|||
ABILITY_INFECT("prepared/flask.svg", CardIconCategory.ABILITY, 100),
|
||||
ABILITY_INDESTRUCTIBLE("prepared/ankh.svg", CardIconCategory.ABILITY, 100),
|
||||
ABILITY_VIGILANCE("prepared/eye.svg", CardIconCategory.ABILITY, 100),
|
||||
ABILITY_CLASS_LEVEL("prepared/hexagon-fill.svg", CardIconCategory.ABILITY, 100),
|
||||
//
|
||||
OTHER_FACEDOWN("prepared/reply-fill.svg", CardIconCategory.ABILITY, 100),
|
||||
OTHER_COST_X("prepared/square-fill.svg", CardIconCategory.ABILITY, 100),
|
||||
//
|
||||
SYSTEM_COMBINED("prepared/square-fill.svg", CardIconCategory.SYSTEM, 1000), // inner usage, must use last order
|
||||
SYSTEM_DEBUG("prepared/link.svg", CardIconCategory.SYSTEM, 1000); // used for test render dialog
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
package mage.abilities.icon.other;
|
||||
|
||||
import mage.abilities.icon.CardIcon;
|
||||
import mage.abilities.icon.CardIconType;
|
||||
|
||||
/**
|
||||
* @author JayDi85
|
||||
*/
|
||||
public enum FaceDownCardIcon implements CardIcon {
|
||||
instance;
|
||||
|
||||
@Override
|
||||
public CardIconType getIconType() {
|
||||
return CardIconType.OTHER_FACEDOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHint() {
|
||||
return "Card is face down";
|
||||
}
|
||||
|
||||
@Override
|
||||
public CardIcon copy() {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package mage.abilities.icon.other;
|
||||
|
||||
import mage.abilities.icon.CardIconImpl;
|
||||
import mage.abilities.icon.CardIconType;
|
||||
|
||||
/**
|
||||
* Showing x cost value
|
||||
*
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class VariableCostCardIcon extends CardIconImpl {
|
||||
|
||||
public VariableCostCardIcon(int costX) {
|
||||
super(CardIconType.OTHER_COST_X, "Announced X = " + costX, "x=" + costX);
|
||||
}
|
||||
}
|
||||
|
|
@ -61,6 +61,7 @@ class SetClassLevelEffect extends OneShotEffect {
|
|||
SetClassLevelEffect(int level) {
|
||||
super(Outcome.Benefit);
|
||||
this.level = level;
|
||||
staticText = "level up to " + level;
|
||||
}
|
||||
|
||||
private SetClassLevelEffect(final SetClassLevelEffect effect) {
|
||||
|
|
@ -76,9 +77,17 @@ class SetClassLevelEffect extends OneShotEffect {
|
|||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
|
||||
if (permanent == null || !permanent.setClassLevel(level)) {
|
||||
if (permanent == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int oldLevel = permanent.getClassLevel();
|
||||
if (!permanent.setClassLevel(level)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
game.informPlayers(permanent.getLogName() + " levelled up from " + oldLevel + " to " + permanent.getClassLevel());
|
||||
|
||||
game.fireEvent(GameEvent.getEvent(
|
||||
GameEvent.EventType.GAINS_CLASS_LEVEL, source.getSourceId(),
|
||||
source, source.getControllerId(), level
|
||||
|
|
|
|||
|
|
@ -2,7 +2,15 @@ package mage.abilities.keyword;
|
|||
|
||||
import mage.abilities.StaticAbility;
|
||||
import mage.abilities.hint.common.ClassLevelHint;
|
||||
import mage.abilities.icon.CardIcon;
|
||||
import mage.abilities.icon.CardIconImpl;
|
||||
import mage.abilities.icon.CardIconType;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
|
|
@ -27,4 +35,27 @@ public class ClassReminderAbility extends StaticAbility {
|
|||
public String getRule() {
|
||||
return "<i>(Gain the next level as a sorcery to add its ability.)</i>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CardIcon> getIcons(Game game) {
|
||||
if (game == null) {
|
||||
return this.icons;
|
||||
}
|
||||
|
||||
// dynamic GUI icon with current level
|
||||
List<CardIcon> res = new ArrayList<>();
|
||||
Permanent permanent = this.getSourcePermanentOrLKI(game);
|
||||
if (permanent == null) {
|
||||
return res;
|
||||
}
|
||||
|
||||
CardIcon levelIcon = new CardIconImpl(
|
||||
CardIconType.ABILITY_CLASS_LEVEL,
|
||||
"Current class level: " + permanent.getClassLevel(),
|
||||
String.valueOf(permanent.getClassLevel())
|
||||
);
|
||||
res.add(levelIcon);
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ public class EquipAbility extends ActivatedAbilityImpl {
|
|||
public EquipAbility(Outcome outcome, Cost cost, Target target) {
|
||||
super(Zone.BATTLEFIELD, new EquipEffect(outcome), cost);
|
||||
this.addTarget(target);
|
||||
this.timing = TimingRule.SORCERY;
|
||||
this.timing = TimingRule.SORCERY;
|
||||
}
|
||||
|
||||
public EquipAbility(final EquipAbility ability) {
|
||||
|
|
@ -50,19 +50,23 @@ public class EquipAbility extends ActivatedAbilityImpl {
|
|||
String targetText = getTargets().get(0) != null ? getTargets().get(0).getFilter().getMessage() : "creature";
|
||||
String reminderText = " <i>(" + manaCosts.getText() + ": Attach to target " + targetText + ". Equip only as a sorcery. This card enters the battlefield unattached and stays on the battlefield if the creature leaves.)</i>";
|
||||
|
||||
StringBuilder sb = new StringBuilder("Equip ");
|
||||
StringBuilder sb = new StringBuilder("Equip");
|
||||
if (!targetText.equals("creature you control")) {
|
||||
sb.append(targetText);
|
||||
sb.append(' ').append(targetText);
|
||||
}
|
||||
String costText = costs.getText();
|
||||
if (costText != null && !costText.isEmpty()) {
|
||||
sb.append("—").append(costText).append('.');
|
||||
} else {
|
||||
sb.append(' ');
|
||||
}
|
||||
sb.append(costs.getText());
|
||||
sb.append(manaCosts.getText());
|
||||
if (costReduceText != null && !costReduceText.isEmpty()) {
|
||||
sb.append(' ');
|
||||
sb.append(". ");
|
||||
sb.append(costReduceText);
|
||||
}
|
||||
if (maxActivationsPerTurn == 1) {
|
||||
sb.append(" Activate only once each turn.");
|
||||
sb.append(". Activate only once each turn.");
|
||||
}
|
||||
sb.append(reminderText);
|
||||
return sb.toString();
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ public class IntimidateAbility extends EvasionAbility implements MageSingleton {
|
|||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "Intimidate";
|
||||
return "intimidate";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
|
||||
|
||||
package mage.abilities.keyword;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.ActivatedAbilityImpl;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
|
||||
import mage.abilities.hint.common.MonstrousHint;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
|
|
@ -19,41 +16,40 @@ import mage.util.CardUtil;
|
|||
|
||||
/**
|
||||
* Monstrosity
|
||||
*
|
||||
* <p>
|
||||
* 701.28. Monstrosity
|
||||
*
|
||||
* <p>
|
||||
* 701.28a “Monstrosity N” means “If this permanent isn't monstrous, put N +1/+1 counters on it
|
||||
* and it becomes monstrous.” Monstrous is a condition of that permanent that can be
|
||||
* referred to by other abilities.
|
||||
*
|
||||
* and it becomes monstrous.” Monstrous is a condition of that permanent that can be
|
||||
* referred to by other abilities.
|
||||
* <p>
|
||||
* 701.28b If a permanent's ability instructs a player to “monstrosity X,” other abilities of
|
||||
* that permanent may also refer to X. The value of X in those abilities is equal to
|
||||
* the value of X as that permanent became monstrous.
|
||||
*
|
||||
* that permanent may also refer to X. The value of X in those abilities is equal to
|
||||
* the value of X as that permanent became monstrous.
|
||||
* <p>
|
||||
* * Once a creature becomes monstrous, it can't become monstrous again. If the creature
|
||||
* is already monstrous when the monstrosity ability resolves, nothing happens.
|
||||
*
|
||||
* is already monstrous when the monstrosity ability resolves, nothing happens.
|
||||
* <p>
|
||||
* * Monstrous isn't an ability that a creature has. It's just something true about that
|
||||
* creature. If the creature stops being a creature or loses its abilities, it will
|
||||
* continue to be monstrous.
|
||||
*
|
||||
* creature. If the creature stops being a creature or loses its abilities, it will
|
||||
* continue to be monstrous.
|
||||
* <p>
|
||||
* * An ability that triggers when a creature becomes monstrous won't trigger if that creature
|
||||
* isn't on the battlefield when its monstrosity ability resolves.
|
||||
* isn't on the battlefield when its monstrosity ability resolves.
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
|
||||
public class MonstrosityAbility extends ActivatedAbilityImpl {
|
||||
|
||||
private int monstrosityValue;
|
||||
private final int monstrosityValue;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param manaString
|
||||
* @param monstrosityValue use Integer.MAX_VALUE for monstrosity X.
|
||||
*/
|
||||
public MonstrosityAbility(String manaString, int monstrosityValue) {
|
||||
super(Zone.BATTLEFIELD, new BecomeMonstrousSourceEffect(monstrosityValue),new ManaCostsImpl(manaString));
|
||||
super(Zone.BATTLEFIELD, new BecomeMonstrousSourceEffect(monstrosityValue), new ManaCostsImpl<>(manaString));
|
||||
this.monstrosityValue = monstrosityValue;
|
||||
|
||||
this.addHint(MonstrousHint.instance);
|
||||
|
|
@ -72,7 +68,6 @@ public class MonstrosityAbility extends ActivatedAbilityImpl {
|
|||
public int getMonstrosityValue() {
|
||||
return monstrosityValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -94,28 +89,33 @@ class BecomeMonstrousSourceEffect extends OneShotEffect {
|
|||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent permanent = game.getPermanent(source.getSourceId());
|
||||
if (permanent != null && !permanent.isMonstrous() && source instanceof MonstrosityAbility) {
|
||||
int monstrosityValue = ((MonstrosityAbility) source).getMonstrosityValue();
|
||||
// handle monstrosity = X
|
||||
if (monstrosityValue == Integer.MAX_VALUE) {
|
||||
monstrosityValue = source.getManaCostsToPay().getX();
|
||||
}
|
||||
new AddCountersSourceEffect(CounterType.P1P1.createInstance(monstrosityValue)).apply(game, source);
|
||||
permanent.setMonstrous(true);
|
||||
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.BECOMES_MONSTROUS, source.getSourceId(), source, source.getControllerId(), monstrosityValue));
|
||||
return true;
|
||||
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
|
||||
if (permanent == null || permanent.isMonstrous()) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
int monstrosityValue = ((MonstrosityAbility) source).getMonstrosityValue();
|
||||
// handle monstrosity = X
|
||||
if (monstrosityValue == Integer.MAX_VALUE) {
|
||||
monstrosityValue = source.getManaCostsToPay().getX();
|
||||
}
|
||||
permanent.addCounters(
|
||||
CounterType.P1P1.createInstance(monstrosityValue),
|
||||
source.getControllerId(), source, game
|
||||
);
|
||||
permanent.setMonstrous(true);
|
||||
game.fireEvent(GameEvent.getEvent(
|
||||
GameEvent.EventType.BECOMES_MONSTROUS, source.getSourceId(),
|
||||
source, source.getControllerId(), monstrosityValue
|
||||
));
|
||||
return true;
|
||||
}
|
||||
|
||||
private String setText(int monstrosityValue) {
|
||||
StringBuilder sb = new StringBuilder("Monstrosity ");
|
||||
sb.append(monstrosityValue == Integer.MAX_VALUE ? "X":monstrosityValue)
|
||||
sb.append(monstrosityValue == Integer.MAX_VALUE ? "X" : monstrosityValue)
|
||||
.append(". <i>(If this creature isn't monstrous, put ")
|
||||
.append(monstrosityValue == Integer.MAX_VALUE ? "X":CardUtil.numberToText(monstrosityValue))
|
||||
.append(monstrosityValue == Integer.MAX_VALUE ? "X" : CardUtil.numberToText(monstrosityValue))
|
||||
.append(" +1/+1 counters on it and it becomes monstrous.)</i>").toString();
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -467,7 +467,8 @@ public enum SubType {
|
|||
XENAGOS("Xenagos", SubTypeSet.PlaneswalkerType),
|
||||
YANGGU("Yanggu", SubTypeSet.PlaneswalkerType),
|
||||
YANLING("Yanling", SubTypeSet.PlaneswalkerType),
|
||||
YODA("Yoda", SubTypeSet.PlaneswalkerType, true); // Star Wars
|
||||
YODA("Yoda", SubTypeSet.PlaneswalkerType, true), // Star Wars,
|
||||
ZARIEL("Zariel", SubTypeSet.PlaneswalkerType);
|
||||
|
||||
public static class SubTypePredicate implements Predicate<MageObject> {
|
||||
|
||||
|
|
|
|||
|
|
@ -542,15 +542,71 @@ public interface Game extends MageItem, Serializable {
|
|||
* @param commanderCardType commander or signature spell
|
||||
* @return
|
||||
*/
|
||||
default Set<Card> getCommanderCardsFromAnyZones(Player player, CommanderCardType commanderCardType) {
|
||||
// from command zone
|
||||
Set<Card> res = getCommanderCardsFromCommandZone(player, commanderCardType);
|
||||
|
||||
// from battlefield
|
||||
this.getCommandersIds(player, commanderCardType, true).stream()
|
||||
.map(this::getPermanent)
|
||||
default Set<Card> getCommanderCardsFromAnyZones(Player player, CommanderCardType commanderCardType, Zone... searchZones) {
|
||||
Set<Zone> needZones = Arrays.stream(searchZones).collect(Collectors.toSet());
|
||||
if (needZones.isEmpty()) {
|
||||
throw new IllegalArgumentException("Empty zones list in searching commanders");
|
||||
}
|
||||
Set<UUID> needCommandersIds = this.getCommandersIds(player, commanderCardType, true);
|
||||
Set<Card> needCommandersCards = needCommandersIds.stream()
|
||||
.map(this::getCard)
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(res::add);
|
||||
.collect(Collectors.toSet());
|
||||
Set<Card> res = new HashSet<>();
|
||||
|
||||
// hand
|
||||
if (needZones.contains(Zone.ALL) || needZones.contains(Zone.HAND)) {
|
||||
needCommandersCards.stream()
|
||||
.filter(card -> Zone.HAND.equals(this.getState().getZone(card.getId())))
|
||||
.forEach(res::add);
|
||||
}
|
||||
|
||||
// graveyard
|
||||
if (needZones.contains(Zone.ALL) || needZones.contains(Zone.GRAVEYARD)) {
|
||||
needCommandersCards.stream()
|
||||
.filter(card -> Zone.GRAVEYARD.equals(this.getState().getZone(card.getId())))
|
||||
.forEach(res::add);
|
||||
}
|
||||
|
||||
// library
|
||||
if (needZones.contains(Zone.ALL) || needZones.contains(Zone.LIBRARY)) {
|
||||
needCommandersCards.stream()
|
||||
.filter(card -> Zone.LIBRARY.equals(this.getState().getZone(card.getId())))
|
||||
.forEach(res::add);
|
||||
}
|
||||
|
||||
// battlefield (need permanent card)
|
||||
if (needZones.contains(Zone.ALL) || needZones.contains(Zone.BATTLEFIELD)) {
|
||||
needCommandersIds.stream()
|
||||
.map(this::getPermanent)
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(res::add);
|
||||
}
|
||||
|
||||
// stack
|
||||
if (needZones.contains(Zone.ALL) || needZones.contains(Zone.STACK)) {
|
||||
needCommandersCards.stream()
|
||||
.filter(card -> Zone.STACK.equals(this.getState().getZone(card.getId())))
|
||||
.forEach(res::add);
|
||||
}
|
||||
|
||||
// exiled
|
||||
if (needZones.contains(Zone.ALL) || needZones.contains(Zone.EXILED)) {
|
||||
needCommandersCards.stream()
|
||||
.filter(card -> Zone.EXILED.equals(this.getState().getZone(card.getId())))
|
||||
.forEach(res::add);
|
||||
}
|
||||
|
||||
// command
|
||||
if (needZones.contains(Zone.ALL) || needZones.contains(Zone.COMMAND)) {
|
||||
res.addAll(getCommanderCardsFromCommandZone(player, commanderCardType));
|
||||
}
|
||||
|
||||
// outside must be ignored (example: second side of MDFC commander after cast)
|
||||
if (needZones.contains(Zone.OUTSIDE)) {
|
||||
throw new IllegalArgumentException("Outside zone doesn't supported in searching commanders");
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3185,8 +3185,8 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
@Override
|
||||
public void cheat(UUID ownerId, List<Card> library, List<Card> hand, List<PermanentCard> battlefield, List<Card> graveyard, List<Card> command) {
|
||||
// fake test ability for triggers and events
|
||||
Ability fakeSourceAbility = new SimpleStaticAbility(Zone.OUTSIDE, new InfoEffect("adding testing cards"));
|
||||
fakeSourceAbility.setControllerId(ownerId);
|
||||
Ability fakeSourceAbilityTemplate = new SimpleStaticAbility(Zone.OUTSIDE, new InfoEffect("adding testing cards"));
|
||||
fakeSourceAbilityTemplate.setControllerId(ownerId);
|
||||
|
||||
Player player = getPlayer(ownerId);
|
||||
if (player != null) {
|
||||
|
|
@ -3221,6 +3221,8 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
}
|
||||
|
||||
for (PermanentCard permanentCard : battlefield) {
|
||||
Ability fakeSourceAbility = fakeSourceAbilityTemplate.copy();
|
||||
fakeSourceAbility.setSourceId(permanentCard.getId());
|
||||
CardUtil.putCardOntoBattlefieldWithEffects(fakeSourceAbility, this, permanentCard, player);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -178,6 +178,7 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
numberCreaturesDefenderAttackedBy.clear();
|
||||
creaturesForcedToAttack.clear();
|
||||
maxAttackers = Integer.MIN_VALUE;
|
||||
attackersTappedByAttack.clear();
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
package mage.game.command.emblems;
|
||||
|
||||
import mage.abilities.TriggeredAbilityImpl;
|
||||
import mage.abilities.effects.common.AdditionalCombatPhaseEffect;
|
||||
import mage.abilities.effects.common.UntapTargetEffect;
|
||||
import mage.constants.TurnPhase;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.command.Emblem;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.target.common.TargetControlledCreaturePermanent;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class ZarielArchdukeOfAvernusEmblem extends Emblem {
|
||||
|
||||
// −6: You get an emblem with "At the end of the first combat phase on your turn, untap target creature you control. After this phase, there is an additional combat phase."
|
||||
public ZarielArchdukeOfAvernusEmblem() {
|
||||
this.setName("Emblem Zariel");
|
||||
this.setExpansionSetCodeForImage("AFR");
|
||||
this.getAbilities().add(new ZarielArchdukeOfAvernusEmblemAbility());
|
||||
}
|
||||
}
|
||||
|
||||
class ZarielArchdukeOfAvernusEmblemAbility extends TriggeredAbilityImpl {
|
||||
|
||||
ZarielArchdukeOfAvernusEmblemAbility() {
|
||||
super(Zone.COMMAND, new UntapTargetEffect());
|
||||
this.addEffect(new AdditionalCombatPhaseEffect());
|
||||
this.addTarget(new TargetControlledCreaturePermanent());
|
||||
}
|
||||
|
||||
private ZarielArchdukeOfAvernusEmblemAbility(final ZarielArchdukeOfAvernusEmblemAbility ability) {
|
||||
super(ability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZarielArchdukeOfAvernusEmblemAbility copy() {
|
||||
return new ZarielArchdukeOfAvernusEmblemAbility(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkEventType(GameEvent event, Game game) {
|
||||
return event.getType() == GameEvent.EventType.END_COMBAT_STEP_PRE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
return game.isActivePlayer(getControllerId())
|
||||
&& game.getTurn().getPhase(TurnPhase.COMBAT).getCount() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "At the end of the first combat phase on your turn, untap target creature you control. " +
|
||||
"After this phase, there is an additional combat phase.";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -75,6 +75,12 @@ public interface Permanent extends Card, Controllable {
|
|||
|
||||
int getClassLevel();
|
||||
|
||||
/**
|
||||
* Level up to next level.
|
||||
*
|
||||
* @param classLevel
|
||||
* @return false on wrong settings (e.g. level up to multiple levels)
|
||||
*/
|
||||
boolean setClassLevel(int classLevel);
|
||||
|
||||
void setCardNumber(String cid);
|
||||
|
|
|
|||
|
|
@ -220,4 +220,9 @@ public class PermanentCard extends PermanentImpl {
|
|||
public Card getMainCard() {
|
||||
return card.getMainCard();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return card.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1528,6 +1528,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
|
||||
@Override
|
||||
public boolean setClassLevel(int classLevel) {
|
||||
// can level up to next (+1) level only
|
||||
if (this.classLevel == classLevel - 1) {
|
||||
this.classLevel = classLevel;
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ public final class EldraziHorrorToken extends TokenImpl {
|
|||
}
|
||||
|
||||
public EldraziHorrorToken() {
|
||||
super("Eldrazi Horror", "3/2 colorless Eldrazi Horror creature");
|
||||
super("Eldrazi Horror", "3/2 colorless Eldrazi Horror creature token");
|
||||
cardType.add(CardType.CREATURE);
|
||||
subtype.add(SubType.ELDRAZI);
|
||||
subtype.add(SubType.HORROR);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import mage.MageInt;
|
|||
public final class GrovetenderDruidsPlantToken extends TokenImpl {
|
||||
|
||||
public GrovetenderDruidsPlantToken() {
|
||||
super("Plant", "1/1 green Plant creature");
|
||||
super("Plant", "1/1 green Plant creature token");
|
||||
cardType.add(CardType.CREATURE);
|
||||
color.setGreen(true);
|
||||
subtype.add(SubType.PLANT);
|
||||
|
|
|
|||
|
|
@ -692,6 +692,11 @@ public class StackAbility extends StackObjectImpl implements Ability {
|
|||
return this.ability.getIcons();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CardIcon> getIcons(Game game) {
|
||||
return this.ability.getIcons(game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ability addIcon(CardIcon cardIcon) {
|
||||
throw new IllegalArgumentException("Stack ability is not supports icon adding");
|
||||
|
|
|
|||
|
|
@ -81,7 +81,11 @@ public class CopyTokenFunction implements Function<Token, Card> {
|
|||
|
||||
for (Ability ability0 : sourceObj.getAbilities()) {
|
||||
Ability ability = ability0.copy();
|
||||
ability.newOriginalId(); // The token is independant from the copy from object so it need a new original Id, otherwise there are problems to check for created continuous effects to check if the source (the Token) has still this ability
|
||||
|
||||
// The token is independant from the copy from object so it need a new original Id,
|
||||
// otherwise there are problems to check for created continuous effects to check if
|
||||
// the source (the Token) has still this ability
|
||||
ability.newOriginalId();
|
||||
|
||||
target.addAbility(ability);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package mage.watchers.common;
|
||||
|
||||
import mage.Mana;
|
||||
import mage.abilities.Ability;
|
||||
import mage.constants.WatcherScope;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
|
|
@ -51,8 +52,14 @@ public class ManaSpentToCastWatcher extends Watcher {
|
|||
return manaMap.getOrDefault(sourceId, null);
|
||||
}
|
||||
|
||||
public int getAndResetLastXValue(UUID sourceId) {
|
||||
return xValueMap.getOrDefault(sourceId, 0);
|
||||
public int getAndResetLastXValue(Ability source) {
|
||||
if (xValueMap.containsKey(source.getSourceId())) {
|
||||
// cast normal way
|
||||
return xValueMap.get(source.getSourceId());
|
||||
} else {
|
||||
// put to battlefield without cast (example: copied spell must keep announced X)
|
||||
return source.getManaCostsToPay().getX();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue