Merge branch 'master' of git.cardforge.org:core-developers/forge into agetian-master

This commit is contained in:
Michael Kamensky
2021-06-11 07:50:33 +03:00
813 changed files with 2245 additions and 1611 deletions

View File

@@ -1354,7 +1354,7 @@ public class AiController {
public AiPlayDecision canPlayFromEffectAI(Spell spell, boolean mandatory, boolean withoutPayingManaCost) { public AiPlayDecision canPlayFromEffectAI(Spell spell, boolean mandatory, boolean withoutPayingManaCost) {
int damage = ComputerUtil.getDamageForPlaying(player, spell); int damage = ComputerUtil.getDamageForPlaying(player, spell);
if (damage >= player.getLife() && !player.cantLoseForZeroOrLessLife() && player.canLoseLife()) { if (!mandatory && damage >= player.getLife() && !player.cantLoseForZeroOrLessLife() && player.canLoseLife()) {
return AiPlayDecision.CurseEffects; return AiPlayDecision.CurseEffects;
} }

View File

@@ -2636,7 +2636,7 @@ public class ComputerUtil {
continue; continue;
} }
if (trigger.hasParam("ValidCard")) { if (trigger.hasParam("ValidCard")) {
if (!card.isValid(trigger.getParam("ValidCard"), source.getController(), source, sa)) { if (!card.isValid(trigger.getParam("ValidCard").split(","), source.getController(), source, sa)) {
continue; continue;
} }
} }

View File

@@ -297,7 +297,7 @@ public class PermanentAi extends SpellAbilityAi {
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) { if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
return false; return false;
} }
return mandatory || checkApiLogic(ai, sa); return checkApiLogic(ai, sa) || mandatory;
} }
} }

View File

@@ -192,7 +192,7 @@ public class PlayAi extends SpellAbilityAi {
spell = (Spell) spell.copyWithDefinedCost(abCost); spell = (Spell) spell.copyWithDefinedCost(abCost);
} }
if( AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlayFromEffectAI(spell, !isOptional, true)) { if (AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlayFromEffectAI(spell, !isOptional, true)) {
// Before accepting, see if the spell has a valid number of targets (it should at this point). // Before accepting, see if the spell has a valid number of targets (it should at this point).
// Proceeding past this point if the spell is not correctly targeted will result // Proceeding past this point if the spell is not correctly targeted will result
// in "Failed to add to stack" error and the card disappearing from the game completely. // in "Failed to add to stack" error and the card disappearing from the game completely.

View File

@@ -70,7 +70,9 @@ import forge.util.storage.StorageReaderFolder;
* @author Forge * @author Forge
* @version $Id: CardSet.java 9708 2011-08-09 19:34:12Z jendave $ * @version $Id: CardSet.java 9708 2011-08-09 19:34:12Z jendave $
*/ */
public final class CardEdition implements Comparable<CardEdition> { // immutable public final class CardEdition implements Comparable<CardEdition> {
// immutable
public enum Type { public enum Type {
UNKNOWN, UNKNOWN,
@@ -234,6 +236,8 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
private String code; private String code;
private String code2; private String code2;
private String mciCode; private String mciCode;
private String scryfallCode;
private String cardsLanguage;
private Type type; private Type type;
private String name; private String name;
private String alias = null; private String alias = null;
@@ -323,6 +327,8 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
public String getCode() { return code; } public String getCode() { return code; }
public String getCode2() { return code2; } public String getCode2() { return code2; }
public String getMciCode() { return mciCode; } public String getMciCode() { return mciCode; }
public String getScryfallCode() { return scryfallCode.toLowerCase(); }
public String getCardsLangCode() { return cardsLanguage.toLowerCase(); }
public Type getType() { return type; } public Type getType() { return type; }
public String getName() { return name; } public String getName() { return name; }
public String getAlias() { return alias; } public String getAlias() { return alias; }
@@ -475,7 +481,8 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
* rarity - grouping #4 * rarity - grouping #4
* name - grouping #5 * name - grouping #5
*/ */
"(^([0-9A-Z]+.?) )?(([SCURML]) )?(.*)$" // "(^(.?[0-9A-Z]+.?))?(([SCURML]) )?(.*)$"
"(^(.?[0-9A-Z]+\\S?[A-Z]*)\\s)?(([SCURML])\\s)?(.*)$"
); );
ListMultimap<String, CardInSet> cardMap = ArrayListMultimap.create(); ListMultimap<String, CardInSet> cardMap = ArrayListMultimap.create();
@@ -541,6 +548,14 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
if (res.mciCode == null) { if (res.mciCode == null) {
res.mciCode = res.code2.toLowerCase(); res.mciCode = res.code2.toLowerCase();
} }
res.scryfallCode = section.get("ScryfallCode");
if (res.scryfallCode == null){
res.scryfallCode = res.code;
}
res.cardsLanguage = section.get("CardLang");
if (res.cardsLanguage == null){
res.cardsLanguage = "en";
}
res.boosterArts = section.getInt("BoosterCovers", 1); res.boosterArts = section.getInt("BoosterCovers", 1);
String boosterDesc = section.get("Booster"); String boosterDesc = section.get("Booster");

View File

@@ -118,10 +118,6 @@ public class ImageUtil {
return getImageRelativePath(cp, backFace, true, true); return getImageRelativePath(cp, backFace, true, true);
} }
public static String getScryfallDownloadUrl(PaperCard cp, boolean backFace, String setCode){
return getScryfallDownloadUrl(cp, backFace, setCode, "en");
}
public static String getScryfallDownloadUrl(PaperCard cp, boolean backFace, String setCode, String langCode){ public static String getScryfallDownloadUrl(PaperCard cp, boolean backFace, String setCode, String langCode){
String editionCode; String editionCode;
if ((setCode != null) && (setCode.length() > 0)) if ((setCode != null) && (setCode.length() > 0))

View File

@@ -807,6 +807,8 @@ public class AbilityUtils {
final SpellAbility root = sa.getRootAbility(); final SpellAbility root = sa.getRootAbility();
list = new CardCollection((Card) root.getReplacingObject(AbilityKey.fromString(calcX[0].substring(8)))); list = new CardCollection((Card) root.getReplacingObject(AbilityKey.fromString(calcX[0].substring(8))));
} }
// there could be null inside!
list = Iterables.filter(list, Card.class);
if (list != null) { if (list != null) {
val = handlePaid(list, calcX[1], card, ability); val = handlePaid(list, calcX[1], card, ability);
} }
@@ -3424,7 +3426,6 @@ public class AbilityUtils {
} else { } else {
return size; return size;
} }
} }
if (string.startsWith("DifferentCMC")) { if (string.startsWith("DifferentCMC")) {
@@ -3437,14 +3438,13 @@ public class AbilityUtils {
if (string.startsWith("SumCMC")) { if (string.startsWith("SumCMC")) {
int sumCMC = 0; int sumCMC = 0;
for(Card c : paidList) { for (Card c : paidList) {
sumCMC += c.getCMC(); sumCMC += c.getCMC();
} }
return sumCMC; return sumCMC;
} }
if (string.startsWith("Valid")) { if (string.startsWith("Valid")) {
final String[] splitString = string.split("/", 2); final String[] splitString = string.split("/", 2);
String valid = splitString[0].substring(6); String valid = splitString[0].substring(6);
final List<Card> list = CardLists.getValidCardsAsList(paidList, valid, source.getController(), source, ctb); final List<Card> list = CardLists.getValidCardsAsList(paidList, valid, source.getController(), source, ctb);
@@ -3464,6 +3464,7 @@ public class AbilityUtils {
} }
int tot = 0; int tot = 0;
for (final Card c : filteredList) { for (final Card c : filteredList) {
tot += xCount(c, filteredString, ctb); tot += xCount(c, filteredString, ctb);
} }

View File

@@ -704,8 +704,8 @@ public abstract class SpellAbilityEffect {
Card host = sa.getHostCard(); Card host = sa.getHostCard();
final Game game = host.getGame(); final Game game = host.getGame();
final String duration = sa.getParam("Duration"); final String duration = sa.getParam("Duration");
// in case host was LKI // in case host was LKI or still resolving
if (host.isLKI()) { if (host.isLKI() || host.getZone().is(ZoneType.Stack)) {
host = game.getCardState(host); host = game.getCardState(host);
} }

View File

@@ -243,5 +243,4 @@ public class CharmEffect extends SpellAbilityEffect {
} }
} }

View File

@@ -1,5 +1,6 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -62,6 +63,7 @@ public class ChooseCardNameEffect extends SpellAbilityEffect {
boolean randomChoice = sa.hasParam("AtRandom"); boolean randomChoice = sa.hasParam("AtRandom");
boolean chooseFromDefined = sa.hasParam("ChooseFromDefinedCards"); boolean chooseFromDefined = sa.hasParam("ChooseFromDefinedCards");
boolean chooseFromOneTimeList = sa.hasParam("ChooseFromOneTimeList");
if (!randomChoice) { if (!randomChoice) {
if (sa.hasParam("SelectPrompt")) { if (sa.hasParam("SelectPrompt")) {
@@ -100,7 +102,7 @@ public class ChooseCardNameEffect extends SpellAbilityEffect {
} else if (chooseFromDefined) { } else if (chooseFromDefined) {
CardCollection choices = AbilityUtils.getDefinedCards(host, sa.getParam("ChooseFromDefinedCards"), sa); CardCollection choices = AbilityUtils.getDefinedCards(host, sa.getParam("ChooseFromDefinedCards"), sa);
choices = CardLists.getValidCards(choices, valid, host.getController(), host, sa); choices = CardLists.getValidCards(choices, valid, host.getController(), host, sa);
List<ICardFace> faces = Lists.newArrayList(); List<ICardFace> faces = new ArrayList<>();
// get Card // get Card
for (final Card c : choices) { for (final Card c : choices) {
final CardRules rules = c.getRules(); final CardRules rules = c.getRules();
@@ -114,6 +116,22 @@ public class ChooseCardNameEffect extends SpellAbilityEffect {
} }
Collections.sort(faces); Collections.sort(faces);
chosen = p.getController().chooseCardName(sa, faces, message); chosen = p.getController().chooseCardName(sa, faces, message);
} else if (chooseFromOneTimeList) {
String [] names = sa.getParam("ChooseFromOneTimeList").split(",");
List<ICardFace> faces = new ArrayList<>();
for (String name : names) {
faces.add(StaticData.instance().getCommonCards().getFaceByName(name));
}
chosen = p.getController().chooseCardName(sa, faces, message);
// Remove chosen Name from List
StringBuilder sb = new StringBuilder();
for (String name : names) {
if (chosen.equals(name)) continue;
if (sb.length() > 0) sb.append(',');
sb.append(name);
}
sa.putParam("ChooseFromOneTimeList", sb.toString());
} else { } else {
// use CardFace because you might name a alternate names // use CardFace because you might name a alternate names
Predicate<ICardFace> cpp = Predicates.alwaysTrue(); Predicate<ICardFace> cpp = Predicates.alwaysTrue();

View File

@@ -99,9 +99,9 @@ public class CopyPermanentEffect extends TokenEffectBase {
if (sa.hasParam("RandomCopied")) { if (sa.hasParam("RandomCopied")) {
List<PaperCard> copysource = Lists.newArrayList(cards); List<PaperCard> copysource = Lists.newArrayList(cards);
List<Card> choice = Lists.newArrayList(); List<Card> choice = Lists.newArrayList();
final String num = sa.hasParam("RandomNum") ? sa.getParam("RandomNum") : "1"; final String num = sa.getParamOrDefault("RandomNum","1");
int ncopied = AbilityUtils.calculateAmount(host, num, sa); int ncopied = AbilityUtils.calculateAmount(host, num, sa);
while(ncopied > 0 && !copysource.isEmpty()) { while (ncopied > 0 && !copysource.isEmpty()) {
final PaperCard cp = Aggregates.random(copysource); final PaperCard cp = Aggregates.random(copysource);
Card possibleCard = Card.fromPaperCard(cp, activator); // Need to temporarily set the Owner so the Game is set Card possibleCard = Card.fromPaperCard(cp, activator); // Need to temporarily set the Owner so the Game is set

View File

@@ -22,7 +22,6 @@ public class MustAttackEffect extends SpellAbilityEffect {
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
// end standard pre- // end standard pre-
final List<Player> tgtPlayers = getTargetPlayers(sa); final List<Player> tgtPlayers = getTargetPlayers(sa);
@@ -67,7 +66,6 @@ public class MustAttackEffect extends SpellAbilityEffect {
entity = defPWs.getFirst(); entity = defPWs.getFirst();
} }
} }
//System.out.println("Setting mustAttackEntity to: "+entity);
for (final Player p : tgtPlayers) { for (final Player p : tgtPlayers) {
if ((tgt == null) || p.canBeTargetedBy(sa)) { if ((tgt == null) || p.canBeTargetedBy(sa)) {

View File

@@ -35,6 +35,7 @@ import forge.game.zone.Zone;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.item.PaperCard; import forge.item.PaperCard;
import forge.util.Aggregates; import forge.util.Aggregates;
import forge.util.CardTranslation;
import forge.util.Lang; import forge.util.Lang;
import forge.util.Localizer; import forge.util.Localizer;
@@ -93,8 +94,7 @@ public class PlayEffect extends SpellAbilityEffect {
if (sa.hasParam("ShowCards")) { if (sa.hasParam("ShowCards")) {
showCards = new CardCollection(AbilityUtils.filterListByType(game.getCardsIn(zones), sa.getParam("ShowCards"), sa)); showCards = new CardCollection(AbilityUtils.filterListByType(game.getCardsIn(zones), sa.getParam("ShowCards"), sa));
} }
} } else if (sa.hasParam("AnySupportedCard")) {
else if (sa.hasParam("AnySupportedCard")) {
final String valid = sa.getParam("AnySupportedCard"); final String valid = sa.getParam("AnySupportedCard");
List<PaperCard> cards = null; List<PaperCard> cards = null;
if (valid.startsWith("Names:")){ if (valid.startsWith("Names:")){
@@ -139,8 +139,14 @@ public class PlayEffect extends SpellAbilityEffect {
else { else {
return; return;
} }
} } else if (sa.hasParam("CopyFromChosenName")) {
else { String name = source.getChosenName();
if (name.trim().isEmpty()) return;
Card card = Card.fromPaperCard(StaticData.instance().getCommonCards().getUniqueByName(name), controller);
card.setToken(true);
tgtCards = new CardCollection();
tgtCards.add(card);
} else {
tgtCards = new CardCollection(); tgtCards = new CardCollection();
// filter only cards that didn't changed zones // filter only cards that didn't changed zones
for (Card c : getTargetCards(sa)) { for (Card c : getTargetCards(sa)) {
@@ -179,7 +185,16 @@ public class PlayEffect extends SpellAbilityEffect {
while (!tgtCards.isEmpty() && amount > 0) { while (!tgtCards.isEmpty() && amount > 0) {
activator.getController().tempShowCards(showCards); activator.getController().tempShowCards(showCards);
Card tgtCard = controller.getController().chooseSingleEntityForEffect(tgtCards, sa, Localizer.getInstance().getMessage("lblSelectCardToPlay"), optional, null); Card tgtCard = null;
if (tgtCards.size() == 1 && amount == 1 && optional) {
tgtCard = tgtCards.get(0);
if (!controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantPlayCard", CardTranslation.getTranslatedName(tgtCard.getName())))) {
break;
}
} else {
tgtCard = controller.getController().chooseSingleEntityForEffect(tgtCards, sa, Localizer.getInstance().getMessage("lblSelectCardToPlay"), optional, null);
}
activator.getController().endTempShowCards(); activator.getController().endTempShowCards();
if (tgtCard == null) { if (tgtCard == null) {
break; break;

View File

@@ -3,6 +3,7 @@ package forge.game.ability.effects;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.GameCommand; import forge.GameCommand;
@@ -154,20 +155,27 @@ public class RepeatEachEffect extends SpellAbilityEffect {
game.getUntap().addUntil(p, new GameCommand() { game.getUntap().addUntil(p, new GameCommand() {
@Override @Override
public void run() { public void run() {
List<Object> tempRemembered = Lists.newArrayList(Iterables.filter(source.getRemembered(), Player.class));
source.removeRemembered(tempRemembered);
source.addRemembered(p); source.addRemembered(p);
AbilityUtils.resolve(repeat); AbilityUtils.resolve(repeat);
source.removeRemembered(p); source.removeRemembered(p);
source.addRemembered(tempRemembered);
} }
}); });
} else { } else {
// to avoid risk of collision with other abilities swap out other Remembered Player while resolving
List<Object> tempRemembered = Lists.newArrayList(Iterables.filter(source.getRemembered(), Player.class));
source.removeRemembered(tempRemembered);
source.addRemembered(p); source.addRemembered(p);
AbilityUtils.resolve(repeat); AbilityUtils.resolve(repeat);
source.removeRemembered(p); source.removeRemembered(p);
source.addRemembered(tempRemembered);
} }
} }
} }
if(sa.hasParam("DamageMap")) { if (sa.hasParam("DamageMap")) {
game.getAction().dealDamage(false, sa.getDamageMap(), sa.getPreventMap(), sa.getCounterTable(), sa); game.getAction().dealDamage(false, sa.getDamageMap(), sa.getPreventMap(), sa.getCounterTable(), sa);
} }
if (sa.hasParam("ChangeZoneTable")) { if (sa.hasParam("ChangeZoneTable")) {

View File

@@ -82,7 +82,7 @@ public class ReplaceManaEffect extends SpellAbilityEffect {
// need to log Updated events there, or the log is wrong order // need to log Updated events there, or the log is wrong order
String message = sa.getReplacementEffect().toString(); String message = sa.getReplacementEffect().toString();
if ( !StringUtils.isEmpty(message)) { if (!StringUtils.isEmpty(message)) {
message = TextUtil.fastReplace(message, "CARDNAME", card.getName()); message = TextUtil.fastReplace(message, "CARDNAME", card.getName());
game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message); game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message);
} }

View File

@@ -1708,6 +1708,9 @@ public class CardProperty {
} }
} else if (property.startsWith("set")) { } else if (property.startsWith("set")) {
final String setCode = property.substring(3, 6); final String setCode = property.substring(3, 6);
if (card.getName().isEmpty()) {
return false;
}
final PaperCard setCard = StaticData.instance().getCommonCards().getCardFromEdition(card.getName(), CardDb.SetPreference.Earliest); final PaperCard setCard = StaticData.instance().getCommonCards().getCardFromEdition(card.getName(), CardDb.SetPreference.Earliest);
if (setCard != null && !setCard.getEdition().equals(setCode)) { if (setCard != null && !setCard.getEdition().equals(setCode)) {
return false; return false;

View File

@@ -1086,7 +1086,7 @@ public class CardView extends GameEntityView {
} }
public CardTypeView getType() { public CardTypeView getType() {
if (isFaceDown() && !isInZone(EnumSet.of(ZoneType.Battlefield, ZoneType.Stack))) { if (getState() != CardStateName.Original && isFaceDown() && !isInZone(EnumSet.of(ZoneType.Battlefield, ZoneType.Stack))) {
return CardType.EMPTY; return CardType.EMPTY;
} }
return get(TrackableProperty.Type); return get(TrackableProperty.Type);

View File

@@ -31,6 +31,7 @@ import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostParser; import forge.card.mana.ManaCostParser;
import forge.game.CardTraitBase; import forge.game.CardTraitBase;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CounterEnumType;
import forge.game.card.CounterType; import forge.game.card.CounterType;
import forge.game.mana.ManaCostBeingPaid; import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player; import forge.game.player.Player;
@@ -876,15 +877,30 @@ public class Cost implements Serializable {
costParts.add(0, new CostPartMana(oldManaCost.toManaCost(), r)); costParts.add(0, new CostPartMana(oldManaCost.toManaCost(), r));
} }
} else if (part instanceof CostDiscard || part instanceof CostTapType || } else if (part instanceof CostDiscard || part instanceof CostTapType ||
part instanceof CostAddMana || part instanceof CostPayLife) { part instanceof CostAddMana || part instanceof CostPayLife
|| part instanceof CostPutCounter) {
boolean alreadyAdded = false; boolean alreadyAdded = false;
for (final CostPart other : costParts) { for (final CostPart other : costParts) {
if (other.getClass().equals(part.getClass()) && if ((other.getClass().equals(part.getClass()) || (part instanceof CostPutCounter && ((CostPutCounter)part).getCounter().is(CounterEnumType.LOYALTY))) &&
part.getType().equals(other.getType()) && part.getType().equals(other.getType()) &&
StringUtils.isNumeric(part.getAmount()) && StringUtils.isNumeric(part.getAmount()) &&
StringUtils.isNumeric(other.getAmount())) { StringUtils.isNumeric(other.getAmount())) {
final String amount = String.valueOf(Integer.parseInt(part.getAmount()) + Integer.parseInt(other.getAmount())); String amount = String.valueOf(Integer.parseInt(part.getAmount()) + Integer.parseInt(other.getAmount()));
if (part instanceof CostDiscard) { if (part instanceof CostPutCounter) { // path for Carth
if (other instanceof CostPutCounter && ((CostPutCounter)other).getCounter().is(CounterEnumType.LOYALTY)) {
costParts.add(new CostPutCounter(amount, CounterType.get(CounterEnumType.LOYALTY), part.getType(), part.getTypeDescription()));
} else if (other instanceof CostRemoveCounter && ((CostRemoveCounter)other).counter.is(CounterEnumType.LOYALTY)) {
Integer counters = Integer.parseInt(other.getAmount()) - Integer.parseInt(part.getAmount());
// the cost can turn positive if multiple Carth raise it
if (counters < 0) {
costParts.add(new CostPutCounter(String.valueOf(counters *-1), CounterType.get(CounterEnumType.LOYALTY), part.getType(), part.getTypeDescription()));
} else {
costParts.add(new CostRemoveCounter(String.valueOf(counters), CounterType.get(CounterEnumType.LOYALTY), part.getType(), part.getTypeDescription(), ZoneType.Battlefield));
}
} else {
continue;
}
} else if (part instanceof CostDiscard) {
costParts.add(new CostDiscard(amount, part.getType(), part.getTypeDescription())); costParts.add(new CostDiscard(amount, part.getType(), part.getTypeDescription()));
} else if (part instanceof CostTapType) { } else if (part instanceof CostTapType) {
CostTapType tappart = (CostTapType)part; CostTapType tappart = (CostTapType)part;

View File

@@ -86,11 +86,12 @@ public class CostPayment extends ManaConversionMatrix {
* a {@link forge.game.spellability.SpellAbility} object. * a {@link forge.game.spellability.SpellAbility} object.
* @return a boolean. * @return a boolean.
*/ */
public static boolean canPayAdditionalCosts(final Cost cost, final SpellAbility ability) { public static boolean canPayAdditionalCosts(Cost cost, final SpellAbility ability) {
if (cost == null) { if (cost == null) {
return true; return true;
} }
cost = CostAdjustment.adjust(cost, ability);
return cost.canPay(ability); return cost.canPay(ability);
} }

View File

@@ -75,7 +75,7 @@ public class CostPutCounter extends CostPartWithList {
} }
@Override @Override
public int paymentOrder() { return 8; } public int paymentOrder() { return 6; }
@Override @Override
public boolean isReusable() { public boolean isReusable() {

View File

@@ -1762,7 +1762,7 @@ public class Player extends GameEntity implements Comparable<Player> {
public final boolean playLand(final Card land, final boolean ignoreZoneAndTiming) { public final boolean playLand(final Card land, final boolean ignoreZoneAndTiming) {
// Dakkon Blackblade Avatar will use a similar effect // Dakkon Blackblade Avatar will use a similar effect
if (canPlayLand(land, ignoreZoneAndTiming)) { if (canPlayLand(land, ignoreZoneAndTiming)) {
this.playLandNoCheck(land); playLandNoCheck(land);
return true; return true;
} }
@@ -1775,8 +1775,8 @@ public class Player extends GameEntity implements Comparable<Player> {
if (land.isFaceDown()) { if (land.isFaceDown()) {
land.turnFaceUp(null); land.turnFaceUp(null);
} }
final Card c = game.getAction().moveTo(getZone(ZoneType.Battlefield), land, null);
game.copyLastState(); game.copyLastState();
final Card c = game.getAction().moveTo(getZone(ZoneType.Battlefield), land, null);
game.updateLastStateForCard(c); game.updateLastStateForCard(c);
// play a sound // play a sound

View File

@@ -31,7 +31,6 @@ public class ReplaceProduceMana extends ReplacementEffect {
*/ */
@Override @Override
public boolean canReplace(Map<AbilityKey, Object> runParams) { public boolean canReplace(Map<AbilityKey, Object> runParams) {
if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Affected))) { if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Affected))) {
return false; return false;
} }

View File

@@ -765,6 +765,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
resetTriggeringObjects(); resetTriggeringObjects();
resetTriggerRemembered(); resetTriggerRemembered();
if (isActivatedAbility()) {
setXManaCostPaid(null);
}
// reset last state when finished resolving // reset last state when finished resolving
setLastStateBattlefield(CardCollection.EMPTY); setLastStateBattlefield(CardCollection.EMPTY);
setLastStateGraveyard(CardCollection.EMPTY); setLastStateGraveyard(CardCollection.EMPTY);

View File

@@ -195,7 +195,6 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
* @return a boolean. * @return a boolean.
*/ */
public final boolean checkZoneRestrictions(final Card c, final SpellAbility sa) { public final boolean checkZoneRestrictions(final Card c, final SpellAbility sa) {
final Player activator = sa.getActivatingPlayer(); final Player activator = sa.getActivatingPlayer();
final Zone cardZone = c.getLastKnownZone(); final Zone cardZone = c.getLastKnownZone();
Card cp = c; Card cp = c;

View File

@@ -366,7 +366,6 @@ public class TargetRestrictions {
return this.saValidTargeting; return this.saValidTargeting;
} }
/** /**
* <p> * <p>
* canOnlyTgtOpponent. * canOnlyTgtOpponent.

View File

@@ -57,6 +57,9 @@ public class StaticAbilityCantBeCast {
} }
public static boolean cantBeActivatedAbility(final SpellAbility spell, final Card card, final Player activator) { public static boolean cantBeActivatedAbility(final SpellAbility spell, final Card card, final Player activator) {
if (spell.isTrigger()) {
return false;
}
final Game game = activator.getGame(); final Game game = activator.getGame();
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) { for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final StaticAbility stAb : ca.getStaticAbilities()) { for (final StaticAbility stAb : ca.getStaticAbilities()) {
@@ -98,7 +101,6 @@ public class StaticAbilityCantBeCast {
* @return true, if successful * @return true, if successful
*/ */
public static boolean applyCantBeCastAbility(final StaticAbility stAb, final SpellAbility spell, final Card card, final Player activator) { public static boolean applyCantBeCastAbility(final StaticAbility stAb, final SpellAbility spell, final Card card, final Player activator) {
if (!stAb.matchesValidParam("ValidCard", card)) { if (!stAb.matchesValidParam("ValidCard", card)) {
return false; return false;
} }
@@ -153,7 +155,6 @@ public class StaticAbilityCantBeCast {
* @return true, if successful * @return true, if successful
*/ */
public static boolean applyCantBeActivatedAbility(final StaticAbility stAb, final SpellAbility spellAbility, final Card card, final Player activator) { public static boolean applyCantBeActivatedAbility(final StaticAbility stAb, final SpellAbility spellAbility, final Card card, final Player activator) {
if (!stAb.matchesValidParam("ValidCard", card)) { if (!stAb.matchesValidParam("ValidCard", card)) {
return false; return false;
} }

View File

@@ -543,7 +543,6 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
} }
private final void finishResolving(final SpellAbility sa, final boolean fizzle) { private final void finishResolving(final SpellAbility sa, final boolean fizzle) {
// SpellAbility is removed from the stack here // SpellAbility is removed from the stack here
// temporarily removed removing SA after resolution // temporarily removed removing SA after resolution
final SpellAbilityStackInstance si = getInstanceFromSpellAbility(sa); final SpellAbilityStackInstance si = getInstanceFromSpellAbility(sa);

View File

@@ -5,8 +5,8 @@ PT:0/0
K:Menace K:Menace
K:Modular:2 K:Modular:2
T:Mode$ SpellCast | ValidCard$ Card.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigPutCounter | CheckSVar$ YouCastThisTurn | SVarCompare$ GT1 | NoResolvingCheck$ True | TriggerDescription$ Whenever you cast a spell other than your first spell each turn, put a +1/+1 counter on CARDNAME. T:Mode$ SpellCast | ValidCard$ Card.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigPutCounter | CheckSVar$ YouCastThisTurn | SVarCompare$ GT1 | NoResolvingCheck$ True | TriggerDescription$ Whenever you cast a spell other than your first spell each turn, put a +1/+1 counter on CARDNAME.
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBDealDamage SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1
SVar:YouCastThisTurn:Count$ThisTurnCast_Card.YouCtrl SVar:YouCastThisTurn:Count$ThisTurnCast_Card.YouCtrl
DeckHas:Ability$Counters DeckHas:Ability$Counters
DeckHints:Type$Artifact & Ability$Counters DeckHints:Type$Artifact
Oracle:Menace\nModular 2 (This creature enters the battlefield with two +1/+1 counters on it. When it dies, you may put its +1/+1 counters on target artifact creature.)\nWhenever you cast a spell other than your first spell each turn, put a +1/+1 counter on Arcbound Tracker. Oracle:Menace\nModular 2 (This creature enters the battlefield with two +1/+1 counters on it. When it dies, you may put its +1/+1 counters on target artifact creature.)\nWhenever you cast a spell other than your first spell each turn, put a +1/+1 counter on Arcbound Tracker.

View File

@@ -4,7 +4,7 @@ Types:Legendary Planeswalker Basri
Loyalty:4 Loyalty:4
A:AB$ PutCounter | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | CounterNum$ 1 | CounterType$ P1P1 | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Choose target creature | ValidTgts$ Creature | SubAbility$ DBPump | SpellDescription$ Put a +1/+1 counter on up to one target creature. It gains vigilance until end of turn. A:AB$ PutCounter | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | CounterNum$ 1 | CounterType$ P1P1 | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Choose target creature | ValidTgts$ Creature | SubAbility$ DBPump | SpellDescription$ Put a +1/+1 counter on up to one target creature. It gains vigilance until end of turn.
SVar:DBPump:DB$ Pump | Defined$ Targeted | KW$ Vigilance SVar:DBPump:DB$ Pump | Defined$ Targeted | KW$ Vigilance
A:AB$ Effect | Cost$ SubCounter<1/LOYALTY> | Triggers$ TrigAttack | SpellDescription$ Whenever a creature attacks this turn, put a +1/+1 counter on it. A:AB$ Effect | Cost$ SubCounter<1/LOYALTY> | Planeswalker$ True | Triggers$ TrigAttack | SpellDescription$ Whenever a creature attacks this turn, put a +1/+1 counter on it.
SVar:TrigAttack:Mode$ Attacks | ValidCard$ Creature | Execute$ TrigPutCounter | TriggerDescription$ Whenever a creature attacks this turn, put a +1/+1 counter on it. SVar:TrigAttack:Mode$ Attacks | ValidCard$ Creature | Execute$ TrigPutCounter | TriggerDescription$ Whenever a creature attacks this turn, put a +1/+1 counter on it.
SVar:TrigPutCounter:DB$ PutCounter | Defined$ TriggeredAttackerLKICopy | CounterType$ P1P1 | CounterNum$ 1 SVar:TrigPutCounter:DB$ PutCounter | Defined$ TriggeredAttackerLKICopy | CounterType$ P1P1 | CounterNum$ 1
A:AB$ PumpAll | Cost$ SubCounter<6/LOYALTY> | Planeswalker$ True | Ultimate$ True | ValidCards$ Creature.YouCtrl | NumAtt$ +2 | NumDef$ +2 | KW$ Flying | SpellDescription$ Creatures you control get +2/+2 and gain flying until end of turn. A:AB$ PumpAll | Cost$ SubCounter<6/LOYALTY> | Planeswalker$ True | Ultimate$ True | ValidCards$ Creature.YouCtrl | NumAtt$ +2 | NumDef$ +2 | KW$ Flying | SpellDescription$ Creatures you control get +2/+2 and gain flying until end of turn.

View File

@@ -1,7 +1,8 @@
Name:Burning Rootwalla Name:Blazing Rootwalla
ManaCost:R ManaCost:R
Types:Creature Lizard Types:Creature Lizard
PT:1/1 PT:1/1
A:AB$ Pump | Cost$ R | NumAtt$ +2 | ActivationLimit$ 1 | SpellDescription$ CARDNAME gets +2/+0 until end of turn. Activate only once per turn. A:AB$ Pump | Cost$ R | NumAtt$ +2 | ActivationLimit$ 1 | SpellDescription$ CARDNAME gets +2/+0 until end of turn. Activate only once per turn.
K:Madness:0 K:Madness:0
Oracle:{R}: Burning Rootwalla gets +2/+0 until end of turn. Activate only once per turn.\nMadness {0} (If you discard this card, discard it into exile. When you do, cast it for its madness cost or put it into your graveyard.) DeckHints:Ability$Discard
Oracle:{R}: Blazing Rootwalla gets +2/+0 until end of turn. Activate only once per turn.\nMadness {0} (If you discard this card, discard it into exile. When you do, cast it for its madness cost or put it into your graveyard.)

View File

@@ -1,5 +1,6 @@
Name:Bog Rats Name:Bog Rats
ManaCost:B ManaCost:B
Types:Creature Rat Types:Creature Rat
PT:1/1
S:Mode$ CantBlockBy | ValidAttacker$ Creature.Self | ValidBlocker$ Creature.Wall | Description$ CARDNAME can't be blocked by Walls. S:Mode$ CantBlockBy | ValidAttacker$ Creature.Self | ValidBlocker$ Creature.Wall | Description$ CARDNAME can't be blocked by Walls.
Oracle:Bog Rats can't be blocked by Walls. Oracle:Bog Rats can't be blocked by Walls.

View File

@@ -0,0 +1,13 @@
Name:Breathless Knight
ManaCost:1 W B
Types:Creature Spirit Knight
PT:2/2
K:Flying
K:Lifelink
T:Mode$ ChangesZone | Origin$ Graveyard | Destination$ Battlefield | TriggerZones$ Battlefield | ValidCard$ Creature.YouOwn+Other | Execute$ TrigPutCounter | TriggerDescription$ Whenever CARDNAME or another creature enters the battlefield under your control, if it entered from a graveyard or you cast it from a graveyard, put a +1/+1 counter on CARDNAME.
T:Mode$ ChangesZone | Destination$ Battlefield | TriggerZones$ Battlefield | ValidCard$ Creature.YouOwn+Other+wasCastFromGraveyard | Execute$ TrigPutCounter | Secondary$ True | TriggerDescription$ Whenever CARDNAME or another creature enters the battlefield under your control, if it entered from a graveyard or you cast it from a graveyard, put a +1/+1 counter on CARDNAME.
T:Mode$ ChangesZone | Origin$ Graveyard | Destination$ Battlefield | ValidCard$ Creature.Self | Execute$ TrigPutCounter | Secondary$ True | TriggerDescription$ Whenever CARDNAME or another creature enters the battlefield under your control, if it entered from a graveyard or you cast it from a graveyard, put a +1/+1 counter on CARDNAME.
T:Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Creature.Self+wasCastFromGraveyard | Execute$ TrigPutCounter | Secondary$ True | TriggerDescription$ Whenever CARDNAME or another creature enters the battlefield under your control, if it entered from a graveyard or you cast it from a graveyard, put a +1/+1 counter on CARDNAME.
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterNum$ 1 | CounterType$ P1P1
DeckHas:Ability$Counters
Oracle:Flying, lifelink\nWhenever Breathless Knight or another creature enters the battlefield under your control, if it entered from a graveyard or you cast it from a graveyard, put a +1/+1 counter on Breathless Knight.

View File

@@ -0,0 +1,10 @@
Name:Carth the Lion
ManaCost:2 B G
Types:Legendary Creature Human Warrior
PT:3/5
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDig | TriggerDescription$ Whenever CARDNAME enters the battlefield or a planeswalker you control dies, look at the top seven cards of your library. You may reveal a planeswalker card from among them and put it into your hand. Put the rest on the bottom of your library in a random order.
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Planeswalker.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigDig | Secondary$ True | TriggerDescription$ Whenever CARDNAME enters the battlefield or a planeswalker you control dies, look at the top seven cards of your library. You may reveal a planeswalker card from among them and put it into your hand. Put the rest on the bottom of your library in a random order.
SVar:TrigDig:DB$ Dig | DigNum$ 7 | ChangeNum$ 1 | Optional$ True | ChangeValid$ Planeswalker | RestRandomOrder$ True | Reveal$ True
S:Mode$ RaiseCost | ValidCard$ Planeswalker.YouCtrl | Type$ Loyalty | Cost$ AddCounter<1/LOYALTY> | Description$ Planeswalkers' loyalty abilities you activate cost an additional [+1] to activate.
DeckNeeds:Type$Planeswalker
Oracle:Whenever Carth the Lion enters the battlefield or a planeswalker you control dies, look at the top seven cards of your library. You may reveal a planeswalker card from among them and put it into your hand. Put the rest on the bottom of your library in a random order.\nPlaneswalkers' loyalty abilities you activate cost an additional [+1] to activate.

View File

@@ -2,6 +2,6 @@ Name:Diamond Lion
ManaCost:2 ManaCost:2
Types:Artifact Creature Lion Types:Artifact Creature Lion
PT:2/2 PT:2/2
A:AB$ Mana | Cost$ Discard<0/Hand> Sac<1/CARDNAME> | Produced$ Any | Amount$ 3 | InstantSpeed$ True | SpellDescription$ Add three mana of any one color. Activate only as an instant. A:AB$ Mana | Cost$ T Discard<0/Hand> Sac<1/CARDNAME> | Produced$ Any | Amount$ 3 | InstantSpeed$ True | SpellDescription$ Add three mana of any one color. Activate only as an instant.
AI:RemoveDeck:All AI:RemoveDeck:All
Oracle:{T}, Discard your hand, Sacrifice Diamond Lion: Add three mana of any one color. Activate only as an instant. Oracle:{T}, Discard your hand, Sacrifice Diamond Lion: Add three mana of any one color. Activate only as an instant.

View File

@@ -1,5 +1,5 @@
Name:Drake Stone Name:Drake Stone
ManaCost:U ManaCost:3 U B
Types:Planeswalker Stone Types:Planeswalker Stone
Loyalty:4 Loyalty:4
A:AB$ Draw | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | NumCards$ 1 | SubAbility$ DBDiscard | SpellDescription$ Draw a card, then each player discards a card. A:AB$ Draw | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | NumCards$ 1 | SubAbility$ DBDiscard | SpellDescription$ Draw a card, then each player discards a card.

View File

@@ -1,6 +1,5 @@
Name:Emergent Growth Name:Emergent Growth
ManaCost:3 G ManaCost:3 G
Types:Sorcery Types:Sorcery
A:SP$ Pump | Cost$ 3 G | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +5 | NumDef$ +5 | KW$ HIDDEN CARDNAME must be blocked if able. | AILogic$ Pump | SpellDescription$ Target creature gets +5/+5 until end of turn and must be blocked this turn if able. A:SP$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +5 | NumDef$ +5 | KW$ HIDDEN CARDNAME must be blocked if able. | AILogic$ Pump | StackDescription$ {c:Targeted} gets +5/+5 until end of turn and must be blocked this turn if able. | SpellDescription$ Target creature gets +5/+5 until end of turn and must be blocked this turn if able.
SVar:Picture:http://www.wizards.com/global/images/magic/general/emergent_growth.jpg
Oracle:Target creature gets +5/+5 until end of turn and must be blocked this turn if able. Oracle:Target creature gets +5/+5 until end of turn and must be blocked this turn if able.

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