Merge branch 'Card-Forge:master' into master

This commit is contained in:
TabletopGeneral
2023-01-19 00:10:11 -05:00
committed by GitHub
108 changed files with 2503 additions and 1496 deletions

View File

@@ -84,7 +84,7 @@ public class EnemyEdit extends FormPanel {
currentData.speed= ((Double) speed.getValue()).floatValue();
currentData.spawnRate=((Double) spawnRate.getValue()).floatValue();
currentData.difficulty=((Double) difficulty.getValue()).floatValue();
currentData.deck= deck.getEdit().getText();
currentData.deck= deck.getEdit().getText().split(",");
currentData.rewards= rewards.getRewards();
preview.setSpritePath(currentData.sprite);
}
@@ -113,7 +113,7 @@ public class EnemyEdit extends FormPanel {
equipment.setText(String.join(",",currentData.equipment));
else
equipment.setText("");
deck.getEdit().setText(currentData.deck);
deck.getEdit().setText(String.join(",",currentData.deck));
speed.setValue(new Float(currentData.speed).doubleValue());
spawnRate.setValue(new Float(currentData.spawnRate).doubleValue());
difficulty.setValue(new Float(currentData.difficulty).doubleValue());

View File

@@ -179,7 +179,7 @@ public class AiAttackController {
List<Player> opps = Lists.newArrayList(ai.getOpponents());
if (forCombatDmg) {
for (Player p : ai.getOpponents().threadSafeIterable()) {
for (Player p : ai.getOpponents()) {
if (p.isMonarch() && ai.canBecomeMonarch()) {
// just increase the odds for now instead of being fully predictable
// as it could lead to other too complex factors giving this reasoning negative impact

View File

@@ -782,15 +782,9 @@ public class AiController {
return AiPlayDecision.CantAfford;
}
}
if (wardCost.hasSpecificCostType(CostPayLife.class)) {
int lifeToPay = wardCost.getCostPartByType(CostPayLife.class).convertAmount();
if (lifeToPay > player.getLife() || (lifeToPay == player.getLife() && !player.cantLoseForZeroOrLessLife())) {
return AiPlayDecision.CantAfford;
}
}
if (wardCost.hasSpecificCostType(CostDiscard.class)
&& wardCost.getCostPartByType(CostDiscard.class).convertAmount() > player.getCardsIn(ZoneType.Hand).size()) {
return AiPlayDecision.CantAfford;
SpellAbilityAi topAI = new SpellAbilityAi() {};
if (!topAI.willPayCosts(player, sa , wardCost, host)) {
return AiPlayDecision.CostNotAcceptable;
}
}
}
@@ -1473,11 +1467,9 @@ public class AiController {
return singleSpellAbilityList(simPicker.chooseSpellAbilityToPlay(null));
}
CardCollection landsWannaPlay = ComputerUtilAbility.getAvailableLandsToPlay(game, player);
CardCollection playBeforeLand = CardLists.filter(
player.getCardsIn(ZoneType.Hand), CardPredicates.hasSVar("PlayBeforeLandDrop")
);
if (!playBeforeLand.isEmpty()) {
SpellAbility wantToPlayBeforeLand = chooseSpellAbilityToPlayFromList(
ComputerUtilAbility.getSpellAbilities(playBeforeLand, player), false
@@ -1487,6 +1479,7 @@ public class AiController {
}
}
CardCollection landsWannaPlay = ComputerUtilAbility.getAvailableLandsToPlay(game, player);
if (landsWannaPlay != null) {
landsWannaPlay = filterLandsToPlay(landsWannaPlay);
Log.debug("Computer " + game.getPhaseHandler().getPhase().nameForUi);

View File

@@ -448,7 +448,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
CardCollectionView list = aic.chooseSacrificeType(cost.getType(), ability, isEffect(), c, null);
return PaymentDecision.card(list);
return list == null ? null : PaymentDecision.card(list);
}
@Override

View File

@@ -33,6 +33,7 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import forge.ai.AiCardMemory.MemorySet;
import forge.ai.ability.ChooseGenericEffectAi;
import forge.ai.ability.ProtectAi;
import forge.ai.ability.TokenAi;
@@ -449,14 +450,27 @@ public class ComputerUtil {
}
// try everything when about to die
if (game.getPhaseHandler().getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS)
&& ComputerUtilCombat.lifeInSeriousDanger(ai, game.getCombat())) {
final CardCollection nonCreatures = CardLists.getNotType(typeList, "Creature");
if (!nonCreatures.isEmpty()) {
return ComputerUtilCard.getWorstAI(nonCreatures);
} else if (!typeList.isEmpty()) {
// TODO make sure survival is possible in case the creature blocks a trampler
return ComputerUtilCard.getWorstAI(typeList);
if (game.getPhaseHandler().getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
// in some rare situations the call to lifeInDanger could lead us back here, this will prevent an overflow
boolean preventReturn = sa != null && sa.isManaAbility();
if (preventReturn) {
AiCardMemory.rememberCard(ai, sa.getHostCard(), MemorySet.HELD_MANA_SOURCES_FOR_NEXT_SPELL);
}
boolean danger = ComputerUtilCombat.lifeInSeriousDanger(ai, game.getCombat());
if (preventReturn) {
AiCardMemory.forgetCard(ai, sa.getHostCard(), MemorySet.HELD_MANA_SOURCES_FOR_NEXT_SPELL);
}
if (danger) {
final CardCollection nonCreatures = CardLists.getNotType(typeList, "Creature");
if (!nonCreatures.isEmpty()) {
return ComputerUtilCard.getWorstAI(nonCreatures);
} else if (!typeList.isEmpty()) {
// TODO make sure survival is possible in case the creature blocks a trampler
return ComputerUtilCard.getWorstAI(typeList);
}
}
}
}
@@ -609,7 +623,7 @@ public class ComputerUtil {
int count = 0;
while (count < amount) {
Card prefCard = getCardPreference(ai, source, "SacCost", typeList);
Card prefCard = getCardPreference(ai, source, "SacCost", typeList, ability);
if (prefCard == null) {
prefCard = ComputerUtilCard.getWorstAI(typeList);
}

View File

@@ -45,6 +45,7 @@ import forge.game.combat.CombatUtil;
import forge.game.cost.Cost;
import forge.game.cost.CostPayEnergy;
import forge.game.cost.CostRemoveCounter;
import forge.game.cost.CostUntap;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordCollection;
import forge.game.keyword.KeywordInterface;
@@ -1417,12 +1418,14 @@ public class ComputerUtilCard {
double nonCombatChance = 0.0f;
double combatChance = 0.0f;
// non-combat Haste: has an activated ability with tap cost
for (SpellAbility ab : c.getSpellAbilities()) {
Cost abCost = ab.getPayCosts();
if (abCost != null && abCost.hasTapCost()
&& (!abCost.hasManaCost() || ComputerUtilMana.canPayManaCost(ab, ai, 0, false))) {
nonCombatChance += 0.5f;
break;
if (c.isAbilitySick()) {
for (SpellAbility ab : c.getSpellAbilities()) {
Cost abCost = ab.getPayCosts();
if (abCost != null && (abCost.hasTapCost() || abCost.hasSpecificCostType(CostUntap.class))
&& (!abCost.hasManaCost() || ComputerUtilMana.canPayManaCost(ab, ai, sa.getPayCosts().getTotalMana().getCMC(), false))) {
nonCombatChance += 0.5f;
break;
}
}
}
// combat Haste: only grant it if the creature will attack
@@ -1730,6 +1733,7 @@ public class ComputerUtilCard {
}
final long timestamp2 = c.getGame().getNextTimestamp(); //is this necessary or can the timestamp be re-used?
pumped.addChangedCardKeywordsInternal(toCopy, null, false, timestamp2, 0, false);
pumped.updateKeywordsCache(pumped.getCurrentState());
applyStaticContPT(ai.getGame(), pumped, new CardCollection(c));
return pumped;
}
@@ -1916,7 +1920,7 @@ public class ComputerUtilCard {
}
}
if (!canBeBlocked) {
boolean threat = atk.getNetCombatDamage() >= ai.getLife() - lifeInDanger;
boolean threat = ComputerUtilCombat.getAttack(atk) >= ai.getLife() - lifeInDanger;
if (!priorityRemovalOnlyInDanger || threat) {
priorityCards.add(atk);
}

View File

@@ -328,10 +328,10 @@ public class ComputerUtilCombat {
if (blockers.size() == 0
|| StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker)) {
unblocked.add(attacker);
} else if (attacker.hasKeyword(Keyword.TRAMPLE)
&& getAttack(attacker) > totalShieldDamage(attacker, blockers)) {
if (!attacker.hasKeyword(Keyword.INFECT)) {
damage += getAttack(attacker) - totalShieldDamage(attacker, blockers);
} else if (attacker.hasKeyword(Keyword.TRAMPLE) && !attacker.hasKeyword(Keyword.INFECT)) {
int dmgAfterShielding = getAttack(attacker) - totalShieldDamage(attacker, blockers);
if (dmgAfterShielding > 0) {
damage += dmgAfterShielding;
}
}
}
@@ -369,13 +369,14 @@ public class ComputerUtilCombat {
if (blockers.size() == 0
|| StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker)) {
unblocked.add(attacker);
} else if (attacker.hasKeyword(Keyword.TRAMPLE)
&& getAttack(attacker) > totalShieldDamage(attacker, blockers)) {
} else if (attacker.hasKeyword(Keyword.TRAMPLE)) {
int trampleDamage = getAttack(attacker) - totalShieldDamage(attacker, blockers);
if (attacker.hasKeyword(Keyword.INFECT)) {
poison += trampleDamage;
if (trampleDamage > 0) {
if (attacker.hasKeyword(Keyword.INFECT)) {
poison += trampleDamage;
}
poison += predictPoisonFromTriggers(attacker, ai, trampleDamage);
}
poison += predictPoisonFromTriggers(attacker, ai, trampleDamage);
}
}
@@ -686,7 +687,7 @@ public class ComputerUtilCombat {
final int defenderDefense = blocker.getLethalDamage() - flankingMagnitude + defBushidoMagnitude;
return defenderDefense;
} // shieldDamage
}
// For AI safety measures like Regeneration
/**
@@ -2053,7 +2054,6 @@ public class ComputerUtilCombat {
if (block.size() == 1) {
final Card blocker = block.getFirst();
// trample
if (hasTrample) {
int dmgToKill = getEnoughDamageToKill(blocker, dmgCanDeal, attacker, true);
@@ -2109,7 +2109,7 @@ public class ComputerUtilCombat {
}
}
return damageMap;
} // setAssignedDamage()
}
// how much damage is enough to kill the creature (for AI)
/**

View File

@@ -234,7 +234,7 @@ public class ComputerUtilCost {
return true;
}
public static boolean checkForManaSacrificeCost(final Player ai, final Cost cost, final Card source, final SpellAbility sourceAbility, final boolean effect) {
public static boolean checkForManaSacrificeCost(final Player ai, final Cost cost, final SpellAbility sourceAbility, final boolean effect) {
// TODO cheating via autopay can still happen, need to get the real ai player from controlledBy
if (cost == null || !ai.isAI()) {
return true;
@@ -247,18 +247,17 @@ public class ComputerUtilCost {
exclude.addAll(AiCardMemory.getMemorySet(ai, MemorySet.PAYS_SAC_COST));
}
if (part.payCostFromSource()) {
list.add(source);
list.add(sourceAbility.getHostCard());
} else if (part.getType().equals("OriginalHost")) {
list.add(sourceAbility.getOriginalHost());
} else if (part.getAmount().equals("All")) {
// Does the AI want to use Sacrifice All?
return false;
} else {
final String amount = part.getAmount();
Integer c = part.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, amount, sourceAbility);
c = part.getAbilityAmount(sourceAbility);
}
final AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
CardCollectionView choices = aic.chooseSacrificeType(part.getType(), sourceAbility, effect, c, exclude);
@@ -636,7 +635,7 @@ public class ComputerUtilCost {
return ComputerUtilMana.canPayManaCost(sa, player, extraManaNeeded, effect)
&& CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa);
} // canPayCost()
}
public static boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView<Player> payers) {
final Card source = sa.getHostCard();

View File

@@ -316,10 +316,6 @@ public class ComputerUtilMana {
continue;
}
if (!ComputerUtilCost.checkForManaSacrificeCost(ai, ma.getPayCosts(), ma.getHostCard(), ma, ma.isTrigger())) {
continue;
}
if (sa.getApi() == ApiType.Animate) {
// For abilities like Genju of the Cedars, make sure that we're not activating the aura ability by tapping the enchanted card for mana
if (saHost.isAura() && "Enchanted".equals(sa.getParam("Defined"))
@@ -381,9 +377,15 @@ public class ComputerUtilMana {
continue;
}
if (canPayShardWithSpellAbility(toPay, ai, paymentChoice, sa, checkCosts, cost.getXManaCostPaidByColor())) {
return paymentChoice;
if (!canPayShardWithSpellAbility(toPay, ai, paymentChoice, sa, checkCosts, cost.getXManaCostPaidByColor())) {
continue;
}
if (!ComputerUtilCost.checkForManaSacrificeCost(ai, ma.getPayCosts(), ma, ma.isTrigger())) {
continue;
}
return paymentChoice;
}
return null;
}

View File

@@ -620,6 +620,9 @@ public abstract class GameState {
game.getAction().checkStateEffects(true); //ensure state based effects and triggers are updated
// prevent interactions with objects from old state
game.copyLastState();
// Set negative or zero life after state effects if need be, important for some puzzles that rely on
// pre-setting negative life (e.g. PS_NEO4).
for (int i = 0; i < playerStates.size(); i++) {

View File

@@ -43,8 +43,6 @@ import forge.game.card.CardView;
import forge.game.card.CounterType;
import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.cost.CostAdjustment;
import forge.game.cost.CostExile;
import forge.game.cost.CostPart;
import forge.game.cost.CostPartMana;
import forge.game.keyword.KeywordInterface;
@@ -698,24 +696,6 @@ public class PlayerControllerAi extends PlayerController {
ability.setActivatingPlayer(c.getController(), true);
ability.setCardState(sa.getCardState());
// FIXME: This is a hack to check if the AI can play the "exile from library" pay costs (Cumulative Upkeep,
// e.g. Thought Lash). We have to do it and bail early if the AI can't pay, because otherwise the AI will
// pay the cost partially, which should not be possible
int nExileLib = 0;
List<CostPart> parts = CostAdjustment.adjust(cost, sa).getCostParts();
for (final CostPart part : parts) {
if (part instanceof CostExile) {
CostExile exile = (CostExile) part;
if (exile.from == ZoneType.Library) {
nExileLib += exile.convertAmount();
}
}
}
if (nExileLib > c.getController().getCardsIn(ZoneType.Library).size()) {
return false;
}
// - End of hack for Exile a card from library Cumulative Upkeep -
if (ComputerUtilCost.canPayCost(ability, c.getController(), true)) {
ComputerUtil.playNoStack(c.getController(), ability, getGame(), true);
// transfer this info for Balduvian Fallen

View File

@@ -1580,7 +1580,7 @@ public class AttachAi extends SpellAbilityAi {
&& canBeBlocked
&& ComputerUtilCombat.canAttackNextTurn(card);
} else if (keyword.equals("Haste")) {
return card.hasSickness() && ph.isPlayerTurn(sa.getActivatingPlayer()) && !card.isTapped()
return card.hasSickness() && ph.isPlayerTurn(ai) && !card.isTapped()
&& card.getNetCombatDamage() + powerBonus > 0
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& ComputerUtilCombat.canAttackNextTurn(card);

View File

@@ -113,9 +113,9 @@ public abstract class PumpAiBase extends SpellAbilityAi {
return CombatUtil.canAttack(card, ai) || CombatUtil.canBlock(card, true);
}
if (!ph.isPlayerTurn(ai)) {
return CombatUtil.canAttack(card, ai)
&& (card.getNetCombatDamage() > 0)
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS);
return card.getNetCombatDamage() > 0
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& CombatUtil.canAttack(card, ai);
} else {
if (ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| ph.getPhase().isBefore(PhaseType.MAIN1)) {
@@ -129,7 +129,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
&& (combat == null || !combat.isAttacking(c))) {
return false;
}
return CombatUtil.canAttack(c, card.getController()) || (combat != null && combat.isAttacking(c));
return (combat != null && combat.isAttacking(c)) || CombatUtil.canAttack(c, card.getController());
}
});
return CombatUtil.canBlockAtLeastOne(card, attackers);
@@ -148,8 +148,8 @@ public abstract class PumpAiBase extends SpellAbilityAi {
return false;
}
// the cards controller needs to be the one attacked
return CombatUtil.canAttack(c, card.getController()) || (combat != null && combat.isAttacking(c)
&& card.getController().equals(combat.getDefenderPlayerByAttacker(c)));
return (combat != null && combat.isAttacking(c) && card.getController().equals(combat.getDefenderPlayerByAttacker(c))) ||
CombatUtil.canAttack(c, card.getController());
}
});
return CombatUtil.canBlockAtLeastOne(card, attackers);
@@ -199,7 +199,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
final boolean evasive = keyword.endsWith("Shadow");
// give evasive keywords to creatures that can or do attack
if (evasive) {
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& newPower > 0
&& Iterables.any(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card));
@@ -231,7 +231,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
}
}
}
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& newPower > 0
&& Iterables.any(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)),
@@ -244,25 +244,25 @@ public abstract class PumpAiBase extends SpellAbilityAi {
&& ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
return true;
}
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& newPower > 0
&& !CardLists.getNotKeyword(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)),
Keyword.HORSEMANSHIP).isEmpty();
} else if (keyword.endsWith("Intimidate")) {
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& newPower > 0
&& !CardLists.getNotType(CardLists.filter(
opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)), "Artifact").isEmpty();
} else if (keyword.endsWith("Fear")) {
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& newPower > 0
&& !CardLists.getNotColor(CardLists.getNotType(CardLists.filter(
opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)), "Artifact"), MagicColor.BLACK).isEmpty();
} else if (keyword.endsWith("Haste")) {
return card.hasSickness() && !ph.isPlayerTurn(opp) && !card.isTapped()
return CombatUtil.isAttackerSick(card, opp) && !ph.isPlayerTurn(opp) && !card.isTapped()
&& newPower > 0
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& ComputerUtilCombat.canAttackNextTurn(card);
@@ -293,7 +293,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
}
return false;
} else if (keyword.startsWith("Bushido")) {
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
&& !opp.getCreaturesInPlay().isEmpty()
&& Iterables.any(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card));
@@ -320,22 +320,22 @@ public abstract class PumpAiBase extends SpellAbilityAi {
}
return false;
} else if (keyword.equals("Double Strike")) {
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
&& newPower > 0
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS);
} else if (keyword.startsWith("Rampage")) {
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
&& newPower > 0
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).size() >= 2;
} else if (keyword.startsWith("Flanking")) {
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
&& newPower > 0
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& !CardLists.getNotKeyword(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)),
Keyword.FLANKING).isEmpty();
} else if (keyword.startsWith("Trample")) {
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
&& CombatUtil.canBeBlocked(card, null, opp)
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& newPower > 1
@@ -347,8 +347,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
if (combat != null && combat.isBlocking(card) && !card.hasKeyword(Keyword.WITHER)) {
return true;
}
return (!ph.isPlayerTurn(opp))
&& (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS);
} else if (keyword.endsWith("Wither")) {
if (newPower <= 0 || card.hasKeyword(Keyword.INFECT)) {
@@ -376,25 +375,25 @@ public abstract class PumpAiBase extends SpellAbilityAi {
} else if (keyword.equals("Persist")) {
return card.getBaseToughness() > 1 && !card.hasKeyword(Keyword.UNDYING);
} else if (keyword.equals("Islandwalk")) {
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& newPower > 0
&& !CardLists.getType(opp.getLandsInPlay(), "Island").isEmpty()
&& Iterables.any(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card));
} else if (keyword.equals("Swampwalk")) {
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& newPower > 0
&& !CardLists.getType(opp.getLandsInPlay(), "Swamp").isEmpty()
&& Iterables.any(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card));
} else if (keyword.equals("Mountainwalk")) {
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& newPower > 0
&& !CardLists.getType(opp.getLandsInPlay(), "Mountain").isEmpty()
&& Iterables.any(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card));
} else if (keyword.equals("Forestwalk")) {
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& newPower > 0
&& !CardLists.getType(opp.getLandsInPlay(), "Forest").isEmpty()

View File

@@ -206,12 +206,7 @@ public class GameCopier {
Card newCard = map.map(origHostCard);
SpellAbility newSa = null;
if (origSa.isSpell()) {
for (SpellAbility sa : newCard.getAllSpellAbilities()) {
if (sa.getDescription().equals(origSa.getDescription())) {
newSa = sa;
break;
}
}
newSa = findSAInCard(origSa, newCard);
}
if (newSa != null) {
newSa.setActivatingPlayer(map.map(origSa.getActivatingPlayer()), true);

View File

@@ -11,6 +11,10 @@ public class MultiTargetSelector {
public static class Targets {
private ArrayList<PossibleTargetSelector.Targets> targets;
public int size() {
return targets.size();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
@@ -24,8 +28,8 @@ public class MultiTargetSelector {
}
}
private List<PossibleTargetSelector> selectors;
private List<SpellAbility> targetingSAs;
private final List<PossibleTargetSelector> selectors;
private final List<SpellAbility> targetingSAs;
private int currentIndex;
public MultiTargetSelector(SpellAbility sa, List<AbilitySub> plannedSubs) {
@@ -52,8 +56,8 @@ public class MultiTargetSelector {
public Targets getLastSelectedTargets() {
Targets targets = new Targets();
targets.targets = new ArrayList<>(selectors.size());
for (int i = 0; i < selectors.size(); i++) {
targets.targets.add(selectors.get(i).getLastSelectedTargets());
for (PossibleTargetSelector selector : selectors) {
targets.targets.add(selector.getLastSelectedTargets());
}
return targets;
}
@@ -78,34 +82,62 @@ public class MultiTargetSelector {
currentIndex = -1;
}
public void selectTargetsByIndex(int i) {
public boolean selectTargetsByIndex(int i) {
// The caller is telling us to select the i-th possible set of targets.
if (i < currentIndex) {
reset();
}
while (currentIndex < i) {
selectNextTargets();
if (!selectNextTargets()) {
return false;
}
}
return true;
}
public boolean selectNextTargets() {
if (currentIndex == -1) {
for (PossibleTargetSelector selector : selectors) {
if (!selector.selectNextTargets()) {
private boolean selectTargetsStartingFrom(int selectorIndex) {
// Don't reset the current selector, as it still has the correct list of targets set and has
// to remember its current/next target index. Subsequent selectors need a reset since their
// possible targets may change based on what was chosen for earlier ones.
if (selectors.get(selectorIndex).selectNextTargets()) {
for (int i = selectorIndex + 1; i < selectors.size(); i++) {
selectors.get(i).reset();
if (!selectors.get(i).selectNextTargets()) {
return false;
}
}
currentIndex = 0;
return true;
}
for (int i = selectors.size() - 1; i >= 0; i--) {
if (selectors.get(i).selectNextTargets()) {
currentIndex++;
return false;
}
public boolean selectNextTargets() {
if (selectors.size() == 0) {
return false;
}
if (currentIndex == -1) {
// Select the first set of targets (calls selectNextTargets() on each selector).
if (selectTargetsStartingFrom(0)) {
currentIndex = 0;
return true;
}
selectors.get(i).reset();
selectors.get(i).selectNextTargets();
// No possible targets.
return false;
}
return false;
// Subsequent call, first try selecting a new target for the last selector. If that doesn't
// work, backtrack (decrement selector index) and try selecting targets from there.
// This approach ensures that leaf selectors (end of list) are advanced first, before
// previous ones, so that we get an AA,AB,BA,BB ordering.
int selectorIndex = selectors.size() - 1;
while (!selectTargetsStartingFrom(selectorIndex)) {
if (selectorIndex == 0) {
// No more possible targets.
return false;
}
selectorIndex--;
}
currentIndex++;
return true;
}
private static boolean conditionsAreMet(SpellAbility saOrSubSa) {

View File

@@ -17,12 +17,11 @@ import forge.game.spellability.TargetRestrictions;
public class PossibleTargetSelector {
private final SpellAbility sa;
private SpellAbility targetingSa;
private int targetingSaIndex;
private final SpellAbility targetingSa;
private final int targetingSaIndex;
private int maxTargets;
private TargetRestrictions tgt;
private int targetIndex;
private List<GameObject> validTargets;
private int nextTargetIndex;
private final List<GameObject> validTargets = new ArrayList<>();
public static class Targets {
final int targetingSaIndex;
@@ -36,7 +35,7 @@ public class PossibleTargetSelector {
this.targetIndex = targetIndex;
this.description = description;
if (targetIndex < 0 || targetIndex >= originalTargetCount) {
if (targetIndex != -1 && (targetIndex < 0 || targetIndex >= originalTargetCount)) {
throw new IllegalArgumentException("Invalid targetIndex=" + targetIndex);
}
}
@@ -51,12 +50,11 @@ public class PossibleTargetSelector {
this.sa = sa;
this.targetingSa = targetingSa;
this.targetingSaIndex = targetingSaIndex;
this.validTargets = new ArrayList<>();
generateValidTargets(sa.getHostCard().getController());
reset();
}
public void reset() {
targetIndex = 0;
nextTargetIndex = 0;
validTargets.clear();
generateValidTargets(sa.getHostCard().getController());
}
@@ -67,7 +65,7 @@ public class PossibleTargetSelector {
}
sa.setActivatingPlayer(player, true);
targetingSa.resetTargets();
tgt = targetingSa.getTargetRestrictions();
TargetRestrictions tgt = targetingSa.getTargetRestrictions();
maxTargets = tgt.getMaxTargets(sa.getHostCard(), targetingSa);
SimilarTargetSkipper skipper = new SimilarTargetSkipper();
@@ -80,8 +78,8 @@ public class PossibleTargetSelector {
}
private static class SimilarTargetSkipper {
private ArrayListMultimap<String, Card> validTargetsMap = ArrayListMultimap.create();
private HashMap<Card, String> cardTypeStrings = new HashMap<>();
private final ArrayListMultimap<String, Card> validTargetsMap = ArrayListMultimap.create();
private final HashMap<Card, String> cardTypeStrings = new HashMap<>();
private HashMap<Card, Integer> creatureScores;
private int getCreatureScore(Card c) {
@@ -190,16 +188,7 @@ public class PossibleTargetSelector {
}
public Targets getLastSelectedTargets() {
return new Targets(targetingSaIndex, validTargets.size(), targetIndex - 1, targetingSa.getTargets().toString());
}
public boolean selectTargetsByIndex(int targetIndex) {
if (targetIndex >= validTargets.size()) {
return false;
}
selectTargetsByIndexImpl(targetIndex);
this.targetIndex = targetIndex + 1;
return true;
return new Targets(targetingSaIndex, validTargets.size(), nextTargetIndex - 1, targetingSa.getTargets().toString());
}
public boolean selectTargets(Targets targets) {
@@ -208,16 +197,16 @@ public class PossibleTargetSelector {
return false;
}
selectTargetsByIndexImpl(targets.targetIndex);
this.targetIndex = targets.targetIndex + 1;
this.nextTargetIndex = targets.targetIndex + 1;
return true;
}
public boolean selectNextTargets() {
if (targetIndex >= validTargets.size()) {
if (nextTargetIndex >= validTargets.size()) {
return false;
}
selectTargetsByIndexImpl(targetIndex);
targetIndex++;
selectTargetsByIndexImpl(nextTargetIndex);
nextTargetIndex++;
return true;
}
}

View File

@@ -81,9 +81,11 @@ public class SimulationController {
}
public void doneEvaluating(Score score) {
if (score.value > bestScore.value) {
// if we're here during a deeper level this hasn't been called for the level above yet
// in such case we need to check that this decision has really lead to the improvement in score
if (getLastDecision().initialScore.value < score.value && score.value > bestScore.value) {
bestScore = score;
bestSequence = currentStack.get(currentStack.size() - 1);
bestSequence = getLastDecision();
}
currentStack.remove(currentStack.size() - 1);
}

View File

@@ -184,7 +184,7 @@ public class GameAction {
// if to Battlefield and it is caused by an replacement effect,
// try to get previous LKI if able
ReplacementEffect re = cause.getReplacementEffect();
if (ReplacementType.Moved.equals(re.getMode())) {
if (ReplacementType.Moved.equals(re.getMode()) && cause.getReplacingObject(AbilityKey.CardLKI).equals(c)) {
lastKnownInfo = (Card) cause.getReplacingObject(AbilityKey.CardLKI);
}
}

View File

@@ -768,7 +768,7 @@ public final class GameActionUtil {
final Game game = sourceCard.getGame();
final Card eff = new Card(game.nextCardId(), game);
eff.setTimestamp(game.getNextTimestamp());
eff.setName(sourceCard.getName() + "'s Effect");
eff.setName(sourceCard + "'s Effect");
eff.setOwner(controller);
eff.setImageKey(sourceCard.getImageKey());

View File

@@ -185,7 +185,8 @@ public final class AbilityFactory {
String cost = mapParams.get("Cost");
if (cost == null) {
if (type == AbilityRecordType.Spell) {
if (state.getFirstAbility() != null && state.getFirstAbility().isSpell()) {
SpellAbility firstAbility = state.getFirstAbility();
if (firstAbility != null && firstAbility.isSpell()) {
// TODO might remove when Enchant Keyword is refactored
System.err.println(state.getName() + " already has Spell using mana cost");
}

View File

@@ -2843,11 +2843,7 @@ public class AbilityUtils {
if (sq[0].startsWith("ColorsCtrl")) {
final String restriction = l[0].substring(11);
final CardCollection list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb);
byte n = 0;
for (final Card card : list) {
n |= card.getColor().getColor();
}
return doXMath(ColorSet.fromMask(n).countColors(), expr, c, ctb);
return doXMath(CardUtil.getColorsFromCards(list).countColors(), expr, c, ctb);
}
// TODO move below to handlePaid

View File

@@ -118,12 +118,10 @@ public abstract class SpellAbilityEffect {
int amount = AbilityUtils.calculateAmount(sa.getHostCard(), svar, sa);
sb.append(" ");
sb.append(TextUtil.enclosedParen(TextUtil.concatNoSpace(svar,"=",String.valueOf(amount))));
} else {
if (sa.costHasManaX()) {
int amount = sa.getXManaCostPaid() == null ? 0 : sa.getXManaCostPaid();
sb.append(" ");
sb.append(TextUtil.enclosedParen(TextUtil.concatNoSpace("X","=",String.valueOf(amount))));
}
} else if (sa.costHasManaX()) {
int amount = sa.getXManaCostPaid() == null ? 0 : sa.getXManaCostPaid();
sb.append(" ");
sb.append(TextUtil.enclosedParen(TextUtil.concatNoSpace("X","=",String.valueOf(amount))));
}
String currentName = CardTranslation.getTranslatedName(sa.getHostCard().getName());

View File

@@ -64,16 +64,13 @@ public class SacrificeEffect extends SpellAbilityEffect {
table.replaceCounterEffect(game, sa, true);
Cost cumCost = new Cost(sa.getParam("CumulativeUpkeep"), true);
Cost payCost = new Cost(ManaCost.ZERO, true);
int n = card.getCounters(CounterEnumType.AGE);
// multiply cost
for (int i = 0; i < n; ++i) {
payCost.add(cumCost);
if (n > 0) {
Cost cumCost = new Cost(sa.getParam("CumulativeUpkeep"), true);
payCost.mergeTo(cumCost, n);
}
sa.setCumulativeupkeep(true);
game.updateLastStateForCard(card);
StringBuilder sb = new StringBuilder();

View File

@@ -2322,7 +2322,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|| keyword.startsWith("Cycling") || keyword.startsWith("TypeCycling")
|| keyword.startsWith("Encore") || keyword.startsWith("Mutate") || keyword.startsWith("Dungeon")
|| keyword.startsWith("Class") || keyword.startsWith("Blitz")
|| keyword.startsWith("Specialize") || keyword.equals("Ravenous")) {
|| keyword.startsWith("Specialize") || keyword.equals("Ravenous")
|| keyword.equals("For Mirrodin")) {
// keyword parsing takes care of adding a proper description
} else if(keyword.startsWith("Read ahead")) {
sb.append(Localizer.getInstance().getMessage("lblReadAhead")).append(" (").append(Localizer.getInstance().getMessage("lblReadAheadDesc"));
@@ -2441,14 +2442,14 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
// add As an additional cost to Permanent spells
if (state.getFirstAbility() != null && type.isPermanent()) {
SpellAbility first = state.getFirstAbility();
SpellAbility first = state.getFirstAbility();
if (first != null && type.isPermanent()) {
if (first.isSpell()) {
Cost cost = first.getPayCosts();
if (cost != null && !cost.isOnlyManaCost()) {
String additionalDesc = "";
if (state.getFirstAbility().hasParam("AdditionalDesc")) {
additionalDesc = state.getFirstAbility().getParam("AdditionalDesc");
if (first.hasParam("AdditionalDesc")) {
additionalDesc = first.getParam("AdditionalDesc");
}
sb.append(cost.toString().replace("\n", "")).append(" ").append(additionalDesc);
sb.append(linebreak);
@@ -2681,13 +2682,14 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
final StringBuilder sb = new StringBuilder();
// Give spellText line breaks for easier reading
sb.append(text.replaceAll("\\\\r\\\\n", "\r\n"));
String spellText = text.replaceAll("\\\\r\\\\n", "\r\n");
sb.append(spellText);
// NOTE:
if (sb.toString().contains(" (NOTE: ")) {
if (spellText.contains(" (NOTE: ")) {
sb.insert(sb.indexOf("(NOTE: "), "\r\n");
}
if (sb.toString().contains("(NOTE: ") && sb.toString().endsWith(".)") && !sb.toString().endsWith("\r\n")) {
if (spellText.contains("(NOTE: ") && spellText.endsWith(".)") && !spellText.endsWith("\r\n")) {
sb.append("\r\n");
}
@@ -3756,14 +3758,19 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public final CardTypeView getOriginalType() {
return getOriginalType(currentState);
}
public final CardTypeView getOriginalType(CardState state) {
public final CardTypeView getOriginalType(CardState state) {
return state.getType();
}
// TODO add changed type by card text
public Iterable<CardChangedType> getChangedCardTypes() {
// If there are no changed types, just return an empty immutable list, which actually
// produces a surprisingly large speedup by avoid lots of temp objects and making iteration
// over the result much faster. (This function gets called a lot!)
if (changedCardTypesByText.isEmpty() && changedTypeByText == null && changedCardTypesCharacterDefining.isEmpty() && changedCardTypes.isEmpty()) {
return ImmutableList.of();
}
Iterable<CardChangedType> byText = changedTypeByText == null ? ImmutableList.of() : ImmutableList.of(this.changedTypeByText);
return Iterables.unmodifiableIterable(Iterables.concat(
changedCardTypesByText.values(), // Layer 3
byText, // Layer 3 by Word Changes,
@@ -7287,4 +7294,11 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public boolean attackVigilance() {
return StaticAbilityAttackVigilance.attackVigilance(this);
}
public boolean isAbilitySick() {
if (!isSick()) {
return false;
}
return !StaticAbilityActivateAbilityAsIfHaste.canActivate(this);
}
}

View File

@@ -1211,6 +1211,31 @@ public class CardFactoryUtil {
trigger.setOverridingAbility(AbilityFactory.getAbility(effect, card));
inst.addTrigger(trigger);
} else if (keyword.equals("For Mirrodin")) {
final StringBuilder sbTrig = new StringBuilder();
sbTrig.append("Mode$ ChangesZone | Destination$ Battlefield | ");
sbTrig.append("ValidCard$ Card.Self | TriggerDescription$ ");
sbTrig.append("For Mirrodin! (").append(inst.getReminderText()).append(")");
final String sbRebel = "DB$ Token | TokenScript$ r_2_2_rebel | TokenOwner$ You | RememberTokens$ True";
final SpellAbility saRebel= AbilityFactory.getAbility(sbRebel, card);
final String sbAttach = "DB$ Attach | Defined$ Remembered";
final AbilitySub saAttach = (AbilitySub) AbilityFactory.getAbility(sbAttach, card);
saRebel.setSubAbility(saAttach);
final String sbClear = "DB$ Cleanup | ClearRemembered$ True";
final AbilitySub saClear = (AbilitySub) AbilityFactory.getAbility(sbClear, card);
saAttach.setSubAbility(saClear);
final Trigger etbTrigger = TriggerHandler.parseTrigger(sbTrig.toString(), card, intrinsic);
etbTrigger.setOverridingAbility(saRebel);
saRebel.setIntrinsic(intrinsic);
inst.addTrigger(etbTrigger);
} else if (keyword.startsWith("Graft")) {
final StringBuilder sb = new StringBuilder();
sb.append("DB$ MoveCounter | Source$ Self | Defined$ TriggeredCardLKICopy");
@@ -3655,10 +3680,8 @@ public class CardFactoryUtil {
String effect = "Mode$ CantTransform | ValidCard$ Creature.Self | ExceptCause$ SpellAbility.Daybound | Secondary$ True | Description$ This permanent can't be transformed except by its daybound ability.";
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
} else if (keyword.equals("Decayed")) {
String effect = "Mode$ Continuous | Affected$ Card.Self | AddHiddenKeyword$ CARDNAME can't block. | " +
"Secondary$ True";
String effect = "Mode$ CantBlockBy | ValidBlocker$ Creature.Self | Secondary$ True | Description$ CARDNAME can't block.";
StaticAbility st = StaticAbility.create(effect, state.getCard(), state, intrinsic);
st.setSVar("SacrificeEndCombat", "True");
inst.addStaticAbility(st);
} else if (keyword.equals("Defender")) {
String effect = "Mode$ CantAttack | ValidCard$ Card.Self | DefenderKeyword$ True | Secondary$ True";
@@ -3777,7 +3800,7 @@ public class CardFactoryUtil {
" | Description$ Strive - " + inst.getReminderText();
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
} else if (keyword.equals("Unleash")) {
String effect = "Mode$ Continuous | Affected$ Card.Self+counters_GE1_P1P1 | AddHiddenKeyword$ CARDNAME can't block.";
String effect = "Mode$ CantBlockBy | ValidBlocker$ Creature.Self+counters_GE1_P1P1 | Secondary$ True | Description$ CARDNAME can't block.";
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
} else if (keyword.equals("Undaunted")) {
String effect = "Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Secondary$ True"

View File

@@ -241,6 +241,8 @@ public final class CardUtil {
newCopy.setColor(in.getColor().getColor());
newCopy.setPhasedOut(in.getPhasedOut());
newCopy.setTapped(in.isTapped());
newCopy.setDamageHistory(in.getDamageHistory());
newCopy.setDamageReceivedThisTurn(in.getDamageReceivedThisTurn());
@@ -356,9 +358,9 @@ public final class CardUtil {
return res;
}
public static ColorSet getColorsYouCtrl(final Player p) {
public static ColorSet getColorsFromCards(Iterable<Card> list) {
byte b = 0;
for (Card c : p.getCardsIn(ZoneType.Battlefield)) {
for (Card c : list) {
b |= c.getColor().getColor();
}
return ColorSet.fromMask(b);

View File

@@ -1132,9 +1132,6 @@ public class CombatUtil {
if (!canBlock(blocker, nextTurn)) {
return false;
}
if (!canBeBlocked(attacker, blocker.getController())) {
return false;
}
if (isUnblockableFromLandwalk(attacker, blocker.getController())
&& !blocker.hasKeyword("CARDNAME can block creatures with landwalk abilities as though they didn't have those abilities.")) {

View File

@@ -254,7 +254,6 @@ public class Cost implements Serializable {
else
manaParts.append(part).append(" ");
}
}
if (parsedMana == null && (manaParts.length() > 0 || xCantBe0)) {
@@ -889,7 +888,24 @@ public class Cost implements Serializable {
return sb.toString();
}
public void mergeTo(Cost source, int amt) {
// multiply to create the full cost
if (amt > 1) {
// to double itself we need to work on a copy
Cost sourceCpy = source.copy();
for (int i = 1; i < amt; ++i) {
// in theory setAmount could be used instead but it depends on the cost complexity (probably not worth trying to determine that first)
source.add(sourceCpy);
}
}
// combine costs (these shouldn't mix together)
this.add(source, false);
}
public Cost add(Cost cost1) {
return add(cost1, true);
}
public Cost add(Cost cost1, boolean mergeAdditional) {
CostPartMana costPart2 = this.getCostMana();
List<CostPart> toRemove = Lists.newArrayList();
for (final CostPart part : cost1.getCostParts()) {
@@ -912,9 +928,10 @@ public class Cost implements Serializable {
} else {
costParts.add(0, new CostPartMana(oldManaCost.toManaCost(), r));
}
} else if (part instanceof CostDiscard || part instanceof CostDraw ||
part instanceof CostAddMana || part instanceof CostPayLife
|| part instanceof CostPutCounter || part instanceof CostTapType) {
} else if (part instanceof CostPutCounter || (mergeAdditional && // below usually not desired because they're from different causes
(part instanceof CostDiscard || part instanceof CostDraw ||
part instanceof CostAddMana || part instanceof CostPayLife ||
part instanceof CostSacrifice || part instanceof CostTapType))) {
boolean alreadyAdded = false;
for (final CostPart other : costParts) {
if ((other.getClass().equals(part.getClass()) || (part instanceof CostPutCounter && ((CostPutCounter)part).getCounter().is(CounterEnumType.LOYALTY))) &&
@@ -922,7 +939,7 @@ public class Cost implements Serializable {
StringUtils.isNumeric(part.getAmount()) &&
StringUtils.isNumeric(other.getAmount())) {
String amount = String.valueOf(part.convertAmount() + other.convertAmount());
if (part instanceof CostPutCounter) { // path for Carth & Cumulative Upkeep
if (part instanceof CostPutCounter) { // CR 606.5 path for Carth
if (other instanceof CostPutCounter && ((CostPutCounter)other).getCounter().equals(((CostPutCounter) part).getCounter())) {
costParts.add(new CostPutCounter(amount, ((CostPutCounter) part).getCounter(), part.getType(), part.getTypeDescription()));
} else if (other instanceof CostRemoveCounter && ((CostRemoveCounter)other).counter.is(CounterEnumType.LOYALTY)) {
@@ -936,6 +953,8 @@ public class Cost implements Serializable {
} else {
continue;
}
} else if (part instanceof CostSacrifice) {
costParts.add(new CostSacrifice(amount, part.getType(), part.getTypeDescription()));
} else if (part instanceof CostDiscard) {
costParts.add(new CostDiscard(amount, part.getType(), part.getTypeDescription()));
} else if (part instanceof CostDraw) {

View File

@@ -112,17 +112,14 @@ public class CostAdjustment {
}
final String scost = st.getParamOrDefault("Cost", "1");
Cost part = new Cost(scost, sa.isAbility());
int count = 0;
if (st.hasParam("ForEachShard")) {
CostPartMana mc = cost.getCostMana();
if (mc != null) {
byte atom = ManaAtom.fromName(st.getParam("ForEachShard").toLowerCase());
for (ManaCostShard shard : mc.getManaCostFor(sa)) {
if ((shard.getColorMask() & atom) != 0) {
++count;
}
ManaCost mc = sa.getHostCard().getManaCost();
byte atom = ManaAtom.fromName(st.getParam("ForEachShard").toLowerCase());
for (ManaCostShard shard : mc) {
if ((shard.getColorMask() & atom) != 0) {
++count;
}
}
} else if (st.hasParam("Amount")) {
@@ -157,8 +154,9 @@ public class CostAdjustment {
// Amount 1 as default
count = 1;
}
for (int i = 0; i < count; ++i) {
cost.add(part);
if (count > 0) {
Cost part = new Cost(scost, sa.isAbility());
cost.mergeTo(part, count);
}
}
@@ -179,7 +177,7 @@ public class CostAdjustment {
originalCard.turnFaceDownNoUpdate();
isStateChangeToFaceDown = true;
}
} // isSpell
}
CardCollection cardsOnBattlefield = new CardCollection(game.getCardsIn(ZoneType.Battlefield));
cardsOnBattlefield.addAll(game.getCardsIn(ZoneType.Stack));

View File

@@ -88,7 +88,6 @@ public class CostExert extends CostPartWithList {
typeList = CardLists.getValidCards(typeList, this.getType().split(";"), payer, source, ability);
final int amount = this.getAbilityAmount(ability);
return needsAnnoucement || (typeList.size() >= amount);
}

View File

@@ -37,7 +37,6 @@ import forge.game.mana.ManaCostBeingPaid;
import forge.game.mana.ManaPool;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
/**
* <p>
@@ -200,12 +199,9 @@ public class CostPayment extends ManaConversionMatrix {
PaymentDecision decision = part.accept(decisionMaker);
if (null == decision) return false;
// the AI will try to exile the same card repeatedly unless it does it immediately
final boolean payImmediately = part instanceof CostExile && ((CostExile) part).from == ZoneType.Library;
// wrap the payment and push onto the cost stack
game.costPaymentStack.push(part, this);
if ((decisionMaker.paysRightAfterDecision() || payImmediately) && !part.payAsDecided(decisionMaker.getPlayer(), decision, ability, decisionMaker.isEffect())) {
if (decisionMaker.paysRightAfterDecision() && !part.payAsDecided(decisionMaker.getPlayer(), decision, ability, decisionMaker.isEffect())) {
game.costPaymentStack.pop(); // cost is resolved
return false;
}

View File

@@ -82,7 +82,6 @@ public class CostPutCardToLib extends CostPartWithList {
sb.append(Cost.convertAmountTypeToWords(i, getAmount(), desc));
}
if (sameZone) {
sb.append(" from the same ").append(from);
} else if (!this.payCostFromSource()) {

View File

@@ -20,7 +20,6 @@ package forge.game.cost;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbilityActivateAbilityAsIfHaste;
/**
* The Class CostTap.
@@ -62,7 +61,7 @@ public class CostTap extends CostPart {
@Override
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
final Card source = ability.getHostCard();
return source.isUntapped() && !isAbilitySick(source);
return source.isUntapped() && !source.isAbilitySick();
}
@Override
@@ -74,11 +73,4 @@ public class CostTap extends CostPart {
public <T> T accept(ICostVisitor<T> visitor) {
return visitor.visit(this);
}
public boolean isAbilitySick(final Card source) {
if (!source.isSick()) {
return false;
}
return !StaticAbilityActivateAbilityAsIfHaste.canActivate(source);
}
}

View File

@@ -20,7 +20,6 @@ package forge.game.cost;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbilityActivateAbilityAsIfHaste;
/**
* The Class CostUntap.
@@ -73,7 +72,7 @@ public class CostUntap extends CostPart {
@Override
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
final Card source = ability.getHostCard();
return source.isTapped() && !isAbilitySick(source);
return source.isTapped() && !source.isAbilitySick();
}
@Override
@@ -85,11 +84,4 @@ public class CostUntap extends CostPart {
public <T> T accept(ICostVisitor<T> visitor) {
return visitor.visit(this);
}
public boolean isAbilitySick(final Card source) {
if (!source.isSick()) {
return false;
}
return !StaticAbilityActivateAbilityAsIfHaste.canActivate(source);
}
}

View File

@@ -86,6 +86,7 @@ public enum Keyword {
FLASH("Flash", SimpleKeyword.class, true, "You may cast this spell any time you could cast an instant."),
FLASHBACK("Flashback", KeywordWithCost.class, false, "You may cast this card from your graveyard for its flashback cost. Then exile it."),
FLYING("Flying", SimpleKeyword.class, true, "This creature can't be blocked except by creatures with flying or reach."),
FOR_MIRRODIN("For Mirrodin", SimpleKeyword.class, false, "When this Equipment enters the battlefield, create a 2/2 red Rebel creature token, then attach this to it."),
FORETELL("Foretell", KeywordWithCost.class, false, "During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost."),
FORTIFY("Fortify", KeywordWithCost.class, false, "%s: Attach to target land you control. Fortify only as a sorcery."),
FRENZY("Frenzy", KeywordWithAmount.class, false, "Whenever this creature attacks and isn't blocked, it gets +%d/+0 until end of turn."),

View File

@@ -286,7 +286,7 @@ public class AbilityManaPart implements java.io.Serializable {
return;
TriggerHandler handler = card.getGame().getTriggerHandler();
Trigger trig = TriggerHandler.parseTrigger(sVarHolder.getSVar(this.triggersWhenSpent), sourceCard, false);
Trigger trig = TriggerHandler.parseTrigger(sVarHolder.getSVar(this.triggersWhenSpent), sourceCard, false, sVarHolder);
handler.registerOneTrigger(trig);
}

View File

@@ -17,8 +17,6 @@
*/
package forge.game.spellability;
import org.apache.commons.lang3.ObjectUtils;
import forge.card.CardStateName;
import forge.card.mana.ManaCost;
import forge.game.card.Card;
@@ -26,6 +24,10 @@ import forge.game.card.CardUtil;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.staticability.StaticAbility;
import forge.util.CardTranslation;
import forge.util.Localizer;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
public class LandAbility extends Ability {
@@ -85,10 +87,12 @@ public class LandAbility extends Ability {
@Override
public String toUnsuppressedString() {
StringBuilder sb = new StringBuilder("Play land");
Localizer localizer = Localizer.getInstance();
StringBuilder sb = new StringBuilder(StringUtils.capitalize(localizer.getMessage("lblPlayLand")));
if (getHostCard().isModal()) {
sb.append(" (").append(getHostCard().getName(ObjectUtils.firstNonNull(getCardStateName(), CardStateName.Original))).append(")");
sb.append(" (").append(CardTranslation.getTranslatedName(getHostCard().getName(ObjectUtils.firstNonNull(getCardStateName(), CardStateName.Original)))).append(")");
}
StaticAbility sta = getMayPlay();

View File

@@ -127,7 +127,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
private boolean aftermath = false;
private boolean cumulativeupkeep = false;
private boolean blessing = false;
private Integer chapter = null;
private boolean lastChapter = false;
@@ -516,6 +515,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return this.hasParam("Ninjutsu");
}
public boolean isCumulativeupkeep() {
return hasParam("CumulativeUpkeep");
}
public boolean isEpic() {
AbilitySub sub = this.getSubAbility();
while (sub != null && !sub.hasParam("Epic")) {
@@ -1119,7 +1122,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
clone.setPayCosts(getPayCosts().copy());
if (manaPart != null) {
clone.manaPart = new AbilityManaPart(this, mapParams);
clone.manaPart = new AbilityManaPart(clone, mapParams);
}
// need to copy the damage tables
@@ -1353,17 +1356,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
}
}
if (tr.isSameController()) {
Player newController;
if (entity instanceof Card) {
newController = ((Card) entity).getController();
for (final Card c : targetChosen.getTargetCards()) {
if (entity != c && !c.getController().equals(newController))
return false;
}
}
}
if (hasParam("MaxTotalTargetPower") && entity instanceof Card) {
int soFar = Aggregates.sum(getTargets().getTargetCards(), CardPredicates.Accessors.fnGetNetPower);
// only add if it isn't already targeting
@@ -1377,6 +1369,17 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
}
}
if (tr.isSameController()) {
Player newController;
if (entity instanceof Card) {
newController = ((Card) entity).getController();
for (final Card c : targetChosen.getTargetCards()) {
if (entity != c && !c.getController().equals(newController))
return false;
}
}
}
if (tr.isDifferentControllers()) {
Player newController;
if (entity instanceof Card) {
@@ -2140,13 +2143,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return ForgeScript.spellAbilityHasProperty(this, property, sourceController, source, spellAbility);
}
public boolean isCumulativeupkeep() {
return cumulativeupkeep;
}
public void setCumulativeupkeep(boolean cumulativeupkeep0) {
cumulativeupkeep = cumulativeupkeep0;
}
// Return whether this spell tracks what color mana is spent to cast it for the sake of the effect
public boolean tracksManaSpent() {
if (hostCard == null || hostCard.getRules() == null) { return false; }

View File

@@ -223,11 +223,6 @@ public class StaticAbilityCantAttackBlock {
if (!stAb.matchesValidParam("ValidTarget", target)) {
return false;
}
final Player defender = target instanceof Card ? ((Card) target).getController() : (Player) target;
if (!stAb.matchesValidParam("ValidDefender", defender)) {
return false;
}
return true;
}

View File

@@ -72,7 +72,7 @@ public class StaticAbilityCantGainLosePayLife {
}
if (!stAb.matchesValidParam("ValidCause", cause)) {
return false;
continue;
}
if (applyCommonAbility(stAb, player)) {

View File

@@ -17,8 +17,14 @@
*/
package forge.game.staticability;
import java.util.Iterator;
import java.util.List;
import com.google.common.collect.Lists;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.keyword.Keyword;
import forge.game.player.Player;
@@ -149,15 +155,27 @@ public class StaticAbilityCantTarget {
return false;
}
if (spellAbility.hasParam("ValidTgts") &&
(stAb.hasParam("SourceCanOnlyTarget")
&& (!spellAbility.getParam("ValidTgts").contains(stAb.getParam("SourceCanOnlyTarget"))
|| spellAbility.getParam("ValidTgts").contains(","))
|| spellAbility.getParam("ValidTgts").contains("non" + stAb.getParam("SourceCanOnlyTarget")
)
)
){
return false;
if (stAb.hasParam("SourceCanOnlyTarget")) {
SpellAbility root = spellAbility.getRootAbility();
List<SpellAbility> choices = null;
if (root.getApi() == ApiType.Charm) {
choices = Lists.newArrayList(root.getAdditionalAbilityList("Choices"));
} else {
choices = Lists.newArrayList(root);
}
Iterator<SpellAbility> it = choices.iterator();
SpellAbility next = it.next();
while (next != null) {
if (next.usesTargeting() && (!next.getParam("ValidTgts").contains(stAb.getParam("SourceCanOnlyTarget"))
|| next.getParam("ValidTgts").contains(","))
|| next.getParam("ValidTgts").contains("non" + stAb.getParam("SourceCanOnlyTarget"))) {
return false;
}
next = next.getSubAbility();
if (next == null && it.hasNext()) {
next = it.next();
}
}
}
return true;

View File

@@ -249,7 +249,7 @@ public final class StaticAbilityContinuous {
}
// two variants for Red vs. red in keyword
if (input.contains("ColorsYouCtrl") || input.contains("colorsYouCtrl")) {
final ColorSet colorsYouCtrl = CardUtil.getColorsYouCtrl(controller);
final ColorSet colorsYouCtrl = CardUtil.getColorsFromCards(controller.getCardsIn(ZoneType.Battlefield));
for (byte color : colorsYouCtrl) {
final String colorWord = MagicColor.toLongString(color);

View File

@@ -341,7 +341,7 @@ public abstract class Trigger extends TriggerReplacementBase {
}
// host controller will be null when adding card in a simulation game
if (this.getHostCard().getController() == null || game.getAge() != GameStage.Play || !meetsCommonRequirements(this.mapParams)) {
if (this.getHostCard().getController() == null || (game.getAge() != GameStage.Play && game.getAge() != GameStage.RestartedByKarn) || !meetsCommonRequirements(this.mapParams)) {
return false;
}

View File

@@ -109,6 +109,10 @@ public class TriggerChangesZone extends Trigger {
if (leavesLKIZone) {
moved = (Card) runParams.get(AbilityKey.CardLKI);
}
if ("Battlefield".equals(runParams.get(AbilityKey.Destination))) {
List<Card> etbLKI = moved.getController().getZone(ZoneType.Battlefield).getCardsAddedThisTurn(null);
moved = etbLKI.get(etbLKI.lastIndexOf(moved));
}
if (!matchesValid(moved, getParam("ValidCard").split(","))) {
return false;

View File

@@ -28,9 +28,7 @@ public class GameSimulationTest extends SimulationTest {
Game game = initAndCreateGame();
Player p = game.getPlayers().get(1);
addCard("Plains", p);
addCard("Plains", p);
addCard("Plains", p);
addCards("Plains", 3, p);
String heraldCardName = "Herald of Anafenza";
Card herald = addCard(heraldCardName, p);
herald.setSickness(false);
@@ -69,9 +67,7 @@ public class GameSimulationTest extends SimulationTest {
sliver.setSickness(false);
Card herald = addCard(heraldCardName, p);
herald.setSickness(false);
addCard("Plains", p);
addCard("Plains", p);
addCard("Plains", p);
addCards("Plains", 3, p);
addCard("Spear of Heliod", p);
game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p);
@@ -160,8 +156,7 @@ public class GameSimulationTest extends SimulationTest {
Game game = initAndCreateGame();
Player p = game.getPlayers().get(1);
addCard("Black Knight", p);
for (int i = 0; i < 5; i++)
addCard("Swamp", p);
addCards("Swamp", 5, p);
String merchantCardName = "Gray Merchant of Asphodel";
Card c = addCardToZone(merchantCardName, p, ZoneType.Hand);
@@ -614,9 +609,7 @@ public class GameSimulationTest extends SimulationTest {
Game game = initAndCreateGame();
Player p = game.getPlayers().get(1);
addCard("Forest", p);
addCard("Forest", p);
addCard("Forest", p);
addCards("Forest", 3, p);
Card callTheScionsCard = addCardToZone("Call the Scions", p, ZoneType.Hand);
game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p);
@@ -692,9 +685,7 @@ public class GameSimulationTest extends SimulationTest {
String broodName = "Brood Monitor";
// enough to cast Chandra's Ignition
for (int i = 0; i < 5; ++i) {
addCard("Mountain", p1);
}
addCards("Mountain", 5, p1);
Card kalitas = addCard(kalitasName, p1);
Card pridemate = addCard(pridemateName, p1);
@@ -749,9 +740,7 @@ public class GameSimulationTest extends SimulationTest {
String broodName = "Brood Monitor";
// enough to cast Chandra's Ignition
for (int i = 0; i < 5; ++i) {
addCard("Mountain", p1);
}
addCards("Mountain", 5, p1);
Card kalitas = addCard(kalitasName, p1);
addCard(pridemateName, p1);
@@ -808,9 +797,7 @@ public class GameSimulationTest extends SimulationTest {
String palisadeName = "Palisade Giant";
// enough to cast Chandra's Ignition
for (int i = 0; i < 5; ++i) {
addCard("Mountain", p1);
}
addCards("Mountain", 5, p1);
Card kalitas = addCard(kalitasName, p1);
Card pridemate = addCard(pridemateName, p1);
@@ -875,9 +862,7 @@ public class GameSimulationTest extends SimulationTest {
String meliraName = "Melira, Sylvok Outcast";
// enough to cast Cone of Flame
for (int i = 0; i < 5; ++i) {
addCard("Mountain", p1);
}
addCards("Mountain", 5, p1);
addCard(soulfireName, p1);
addCard(pridemateName, p1);
@@ -890,10 +875,7 @@ public class GameSimulationTest extends SimulationTest {
coneSA.setTargetCard(bearCard); // one damage to bear
coneSA.getSubAbility().setTargetCard(giantCard); // two damage to giant
coneSA.getSubAbility().getSubAbility().getTargets().add(p2); // three
// damage
// to
// player
coneSA.getSubAbility().getSubAbility().getTargets().add(p2); // three damage to player
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p1);
game.getAction().checkStateEffects(true);
@@ -906,8 +888,7 @@ public class GameSimulationTest extends SimulationTest {
Card simGiant = findCardWithName(simGame, giantCardName);
Card simPridemate = findCardWithName(simGame, pridemateName);
// spell deals multiple damages to multiple targets, only one cause of
// lifegain
// spell deals multiple damages to multiple targets, only one cause of lifegain
AssertJUnit.assertNotNull(simPridemate);
AssertJUnit.assertTrue(simPridemate.hasCounters());
AssertJUnit.assertEquals(1, simPridemate.getCounters(CounterEnumType.P1P1));
@@ -1178,9 +1159,7 @@ public class GameSimulationTest extends SimulationTest {
String bearName = "Runeclaw Bear";
String greetingName = "Alchemist's Greeting";
for (int i = 0; i < 5; ++i) {
addCard("Mountain", p);
}
addCards("Mountain", 5, p);
addCard(soulfireName, p);
addCard(pridemateName, p);
@@ -1243,9 +1222,7 @@ public class GameSimulationTest extends SimulationTest {
String elementalName = "Air Elemental";
String shockName = "Shock";
for (int i = 0; i < 2; ++i) {
addCard("Mountain", p);
}
addCards("Mountain", 3, p);
addCard(soulfireName, p);
addCard(pridemateName, p);
@@ -1340,9 +1317,7 @@ public class GameSimulationTest extends SimulationTest {
addCardToZone("Kalitas, Traitor of Ghet", p, ZoneType.Battlefield);
addCardToZone("Anointed Procession", p, ZoneType.Battlefield);
addCardToZone("Swamp", p, ZoneType.Battlefield);
for (int i = 0; i < 4; i++) {
addCardToZone("Mountain", p, ZoneType.Battlefield);
}
addCards("Mountain", 4, p);
Card goblin = addCardToZone("Raging Goblin", opp, ZoneType.Battlefield);
Card goblin2 = addCardToZone("Raging Goblin", opp, ZoneType.Battlefield);
@@ -1599,9 +1574,7 @@ public class GameSimulationTest extends SimulationTest {
addCard("Teysa Karlov", p);
addCard("Xathrid Necromancer", p);
for (int i = 0; i < 4; i++) {
addCardToZone("Plains", p, ZoneType.Battlefield);
}
addCards("Plains", 4, p);
Card wrathOfGod = addCardToZone("Wrath of God", p, ZoneType.Hand);
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
@@ -1780,10 +1753,7 @@ public class GameSimulationTest extends SimulationTest {
AssertJUnit.assertFalse(giant.isDoubleFaced());
AssertJUnit.assertFalse(giant.canTransform(null));
addCard("Forest", p);
addCard("Forest", p);
addCard("Forest", p);
addCard("Forest", p);
addCards("Forest", 4, p);
addCard("Island", p);
Card cytoCard = addCardToZone("Cytoshape", p, ZoneType.Hand);
@@ -1895,12 +1865,8 @@ public class GameSimulationTest extends SimulationTest {
Player p = game.getPlayers().get(0);
game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p);
for (int i = 0; i < 7; i++) {
addCardToZone("Plains", p, ZoneType.Battlefield);
}
for (int i = 0; i < 7; i++) {
addCardToZone("Island", p, ZoneType.Battlefield);
}
addCards("Plains", 7, p);
addCards("Island", 7, p);
Card gideon = addCardToZone("Gideon Blackblade", p, ZoneType.Hand);
Card sparkDouble = addCardToZone("Spark Double", p, ZoneType.Hand);
@@ -1926,15 +1892,9 @@ public class GameSimulationTest extends SimulationTest {
Player p = game.getPlayers().get(0);
game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p);
for (int i = 0; i < 7; i++) {
addCardToZone("Plains", p, ZoneType.Battlefield);
}
for (int i = 0; i < 7; i++) {
addCardToZone("Island", p, ZoneType.Battlefield);
}
for (int i = 0; i < 7; i++) {
addCardToZone("Forest", p, ZoneType.Battlefield);
}
addCards("Plains", 7, p);
addCards("Island", 7, p);
addCards("Forest", 7, p);
Card tgtLand = addCardToZone("Wastes", p, ZoneType.Battlefield);
@@ -1967,12 +1927,8 @@ public class GameSimulationTest extends SimulationTest {
Player p = game.getPlayers().get(0);
game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p);
for (int i = 0; i < 7; i++) {
addCardToZone("Swamp", p, ZoneType.Battlefield);
}
for (int i = 0; i < 7; i++) {
addCardToZone("Forest", p, ZoneType.Battlefield);
}
addCards("Swamp", 7, p);
addCards("Forest", 7, p);
addCardToZone("Basking Rootwalla", p, ZoneType.Graveyard);
Card ooze = addCardToZone("Necrotic Ooze", p, ZoneType.Hand);
@@ -1996,9 +1952,7 @@ public class GameSimulationTest extends SimulationTest {
Player p = game.getPlayers().get(0);
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
for (int i = 0; i < 7; i++) {
addCardToZone("Swamp", p, ZoneType.Battlefield);
}
addCards("Swamp", 7, p);
Card epo = addCardToZone("Epochrasite", p, ZoneType.Graveyard);
Card animate = addCardToZone("Animate Dead", p, ZoneType.Hand);
@@ -2145,9 +2099,7 @@ public class GameSimulationTest extends SimulationTest {
Card serraAngel = addCardToZone("Serra Angel", p1, ZoneType.Battlefield);
Card actOfTreason = addCardToZone("Act of Treason", p0, ZoneType.Hand);
Card pathToExile = addCardToZone("Path to Exile", p0, ZoneType.Hand);
for (int i = 0; i < 4; i++) {
addCardToZone("Plateau", p0, ZoneType.Battlefield);
}
addCards("Plateau", 4, p0);
addCardToZone("Island", p1, ZoneType.Library);
addCardToZone("Forest", p0, ZoneType.Library);
@@ -2182,8 +2134,7 @@ public class GameSimulationTest extends SimulationTest {
Player p = game.getPlayers().get(0);
String WCname = "Woodland Champion";
addCard(WCname, p);
for (int i = 0; i < 5; i++)
addCard("Island", p);
addCards("Island", 5, p);
String CardName = "Eternal Skylord";
Card c = addCardToZone(CardName, p, ZoneType.Hand);
@@ -2214,8 +2165,7 @@ public class GameSimulationTest extends SimulationTest {
String waywardServant = "Wayward Servant";
String goblin = "Raging Goblin";
for (int i = 0; i < 8; i++)
addCard("Swamp", p);
addCards("Swamp", 8, p);
Card cardEverAfter = addCardToZone(everAfter, p, ZoneType.Hand);
Card cardWaywardServant = addCardToZone(waywardServant, p, ZoneType.Graveyard);
@@ -2296,9 +2246,7 @@ public class GameSimulationTest extends SimulationTest {
String alphaBrawlName = "Alpha Brawl";
// enough to cast Alpha Brawl
for (int i = 0; i < 8; ++i) {
addCard("Mountain", p2);
}
addCards("Mountain", 8, p2);
Card nishoba = addCard(nishobaName, p1);
nishoba.addCounterInternal(CounterEnumType.P1P1, 7, p1, false, null, null);
@@ -2366,10 +2314,9 @@ public class GameSimulationTest extends SimulationTest {
Card glarecaster = addCard(glarecasterName, p);
// enough to activate Glarecaster and cast Inferno
for (int i = 0; i < 7; ++i) {
addCard("Plains", p);
addCard("Mountain", p);
}
addCards("Plains", 7, p);
addCards("Mountain", 7, p);
Card infernoCard = addCardToZone("Inferno", p, ZoneType.Hand);
SpellAbility infernoSA = infernoCard.getFirstSpellAbility();
@@ -2407,9 +2354,7 @@ public class GameSimulationTest extends SimulationTest {
addCard(grumName, p);
Card mowu = addCardToZone(mowuName, p, ZoneType.Hand);
for (int i = 0; i < 7; ++i) {
addCard("Forest", p);
}
addCards("Forest", 7, p);
SpellAbility mowuSA = mowu.getFirstSpellAbility();
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
@@ -2437,10 +2382,8 @@ public class GameSimulationTest extends SimulationTest {
addCard(grumName, p);
Card corpsejack = addCardToZone(corpsejackName, p, ZoneType.Hand);
for (int i = 0; i < 7; ++i) {
addCard("Forest", p);
addCard("Swamp", p);
}
addCards("Forest", 7, p);
addCards("Swamp", 7, p);
SpellAbility corpsejackSA = corpsejack.getFirstSpellAbility();
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
@@ -2473,9 +2416,7 @@ public class GameSimulationTest extends SimulationTest {
Card everAfter = addCardToZone(everAfterName, p, ZoneType.Hand);
for (int i = 0; i < 7; ++i) {
addCard("Swamp", p);
}
addCards("Swamp", 7, p);
SpellAbility everSA = everAfter.getFirstSpellAbility();
everSA.getTargets().add(corpsejack);
everSA.getTargets().add(mentor);

View File

@@ -137,4 +137,10 @@ public class SimulationTest {
protected Card addCard(String name, Player p) {
return addCardToZone(name, p, ZoneType.Battlefield);
}
protected void addCards(String name, int count, Player p) {
for (int i = 0; i < count; i++) {
addCard(name, p);
}
}
}

View File

@@ -95,11 +95,8 @@ public class SpellAbilityPickerSimulationTest extends SimulationTest {
Game game = initAndCreateGame();
Player p = game.getPlayers().get(1);
addCard("Island", p);
addCard("Island", p);
addCard("Forest", p);
addCard("Forest", p);
addCard("Forest", p);
addCards("Island", 2, p);
addCards("Forest", 3, p);
Card tatyova = addCardToZone("Tatyova, Benthic Druid", p, ZoneType.Hand);
addCardToZone("Forest", p, ZoneType.Hand);
@@ -169,10 +166,7 @@ public class SpellAbilityPickerSimulationTest extends SimulationTest {
Game game = initAndCreateGame();
Player p = game.getPlayers().get(1);
addCard("Mountain", p);
addCard("Mountain", p);
addCard("Mountain", p);
addCard("Mountain", p);
addCards("Mountain", 4, p);
Card spell = addCardToZone("Fiery Confluence", p, ZoneType.Hand);
Player opponent = game.getPlayers().get(0);
@@ -198,10 +192,7 @@ public class SpellAbilityPickerSimulationTest extends SimulationTest {
Game game = initAndCreateGame();
Player p = game.getPlayers().get(1);
addCard("Mountain", p);
addCard("Mountain", p);
addCard("Mountain", p);
addCard("Mountain", p);
addCards("Mountain", 4, p);
Card spell = addCardToZone("Fiery Confluence", p, ZoneType.Hand);
Player opponent = game.getPlayers().get(0);
@@ -226,8 +217,7 @@ public class SpellAbilityPickerSimulationTest extends SimulationTest {
Game game = initAndCreateGame();
Player p = game.getPlayers().get(1);
addCard("Mountain", p);
addCard("Mountain", p);
addCards("Mountain", 2, p);
Card spell = addCardToZone("Arc Trail", p, ZoneType.Hand);
Player opponent = game.getPlayers().get(0);
@@ -289,8 +279,7 @@ public class SpellAbilityPickerSimulationTest extends SimulationTest {
Game game = initAndCreateGame();
Player p = game.getPlayers().get(1);
addCard("Mountain", p);
addCard("Mountain", p);
addCards("Mountain", 2, p);
Card abbot = addCardToZone("Abbot of Keral Keep", p, ZoneType.Hand);
addCardToZone("Lightning Bolt", p, ZoneType.Hand);
// Note: This assumes the top of library is revealed. If the AI is made
@@ -321,9 +310,7 @@ public class SpellAbilityPickerSimulationTest extends SimulationTest {
Game game = initAndCreateGame();
Player p = game.getPlayers().get(1);
addCard("Mountain", p);
addCard("Mountain", p);
addCard("Mountain", p);
addCards("Mountain", 3, p);
Card abbot = addCardToZone("Abbot of Keral Keep", p, ZoneType.Hand);
// Note: This assumes the top of library is revealed. If the AI is made
// smarter to not assume that, then this test can be updated to have
@@ -426,8 +413,7 @@ public class SpellAbilityPickerSimulationTest extends SimulationTest {
Card blocker = addCard("Fugitive Wizard", opponent);
Card attacker1 = addCard("Dwarven Trader", p);
attacker1.setSickness(false);
addCard("Swamp", p);
addCard("Swamp", p);
addCards("Swamp", 2, p);
addCardToZone("Doom Blade", p, ZoneType.Hand);
game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p);
@@ -455,9 +441,7 @@ public class SpellAbilityPickerSimulationTest extends SimulationTest {
Player opponent = game.getPlayers().get(0);
addCardToZone("Chaos Warp", p, ZoneType.Hand);
addCard("Mountain", p);
addCard("Mountain", p);
addCard("Mountain", p);
addCards("Mountain", 3, p);
addCard("Plains", opponent);
addCard("Mountain", opponent);
@@ -489,8 +473,7 @@ public class SpellAbilityPickerSimulationTest extends SimulationTest {
Game game = initAndCreateGame();
Player p = game.getPlayers().get(1);
addCard("Island", p);
addCard("Island", p);
addCards("Forest", 2, p);
addCardToZone("Counterspell", p, ZoneType.Hand);
addCardToZone("Unsummon", p, ZoneType.Hand);
@@ -605,4 +588,74 @@ public class SpellAbilityPickerSimulationTest extends SimulationTest {
// Still, this test case exercises the code path and ensures we don't crash in this case.
AssertJUnit.assertEquals(1, picker.getNumSimulations());
}
@Test
public void threeDistinctTargetSpell() {
Game game = initAndCreateGame();
Player p = game.getPlayers().get(1);
Player opponent = game.getPlayers().get(0);
addCardToZone("Incremental Growth", p, ZoneType.Hand);
addCards("Forest", 5, p);
addCard("Forest Bear", p);
addCard("Flying Men", opponent);
addCard("Runeclaw Bear", p);
addCard("Water Elemental", opponent);
addCard("Grizzly Bears", p);
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
game.getAction().checkStateEffects(true);
SpellAbilityPicker picker = new SpellAbilityPicker(game, p);
SpellAbility sa = picker.chooseSpellAbilityToPlay(null);
AssertJUnit.assertNotNull(sa);
MultiTargetSelector.Targets targets = picker.getPlan().getSelectedDecision().targets;
AssertJUnit.assertEquals(3, targets.size());
AssertJUnit.assertTrue(targets.toString().contains("Forest Bear"));
AssertJUnit.assertTrue(targets.toString().contains("Runeclaw Bear"));
AssertJUnit.assertTrue(targets.toString().contains("Grizzly Bear"));
// Expected 5*4*3=60 iterations (5 choices for first target, 4 for next, 3 for last.)
AssertJUnit.assertEquals(60, picker.getNumSimulations());
}
@Test
public void threeDistinctTargetSpellCantBeCast() {
Game game = initAndCreateGame();
Player p = game.getPlayers().get(1);
Player opponent = game.getPlayers().get(0);
addCardToZone("Incremental Growth", p, ZoneType.Hand);
addCards("Forest", 5, p);
addCard("Forest Bear", p);
addCard("Flying Men", opponent);
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
game.getAction().checkStateEffects(true);
SpellAbilityPicker picker = new SpellAbilityPicker(game, p);
SpellAbility sa = picker.chooseSpellAbilityToPlay(null);
AssertJUnit.assertNull(sa);
}
@Test
public void correctTargetChoicesWithTwoTargetSpell() {
Game game = initAndCreateGame();
Player p = game.getPlayers().get(1);
Player opponent = game.getPlayers().get(0);
addCardToZone("Rites of Reaping", p, ZoneType.Hand);
addCard("Swamp", p);
addCards("Forest", 5, p);
addCard("Flying Men", opponent);
addCard("Forest Bear", p);
addCard("Water Elemental", opponent);
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
game.getAction().checkStateEffects(true);
SpellAbilityPicker picker = new SpellAbilityPicker(game, p);
SpellAbility sa = picker.chooseSpellAbilityToPlay(null);
AssertJUnit.assertNotNull(sa);
MultiTargetSelector.Targets targets = picker.getPlan().getSelectedDecision().targets;
AssertJUnit.assertEquals(2, targets.size());
AssertJUnit.assertTrue(targets.toString().contains("Forest Bear"));
AssertJUnit.assertTrue(targets.toString().contains("Flying Men"));
}
}

View File

@@ -1,6 +1,6 @@
package forge.adventure.data;
import forge.adventure.util.CardUtil;
import forge.adventure.util.*;
import forge.deck.Deck;
/**
@@ -11,7 +11,7 @@ import forge.deck.Deck;
public class EnemyData {
public String name;
public String sprite;
public String deck;
public String[] deck;
public boolean copyPlayerDeck = false;
public String ai;
public boolean boss = false;
@@ -54,6 +54,6 @@ public class EnemyData {
}
public Deck generateDeck(boolean isFantasyMode, boolean useGeneticAI) {
return CardUtil.getDeck(deck, true, isFantasyMode, colors, life > 13, life > 16 && useGeneticAI);
return CardUtil.getDeck(deck[Current.player().getEnemyDeckNumber(this.name, deck.length)], true, isFantasyMode, colors, life > 13, life > 16 && useGeneticAI);
}
}

View File

@@ -693,4 +693,23 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
questFlags.clear();
}
public int getEnemyDeckNumber(String enemyName, int maxDecks){
int deckNumber = 0;
if (statistic.getWinLossRecord().get(enemyName)!=null)
{
int playerWins = statistic.getWinLossRecord().get(enemyName).getKey();
int enemyWins = statistic.getWinLossRecord().get(enemyName).getValue();
if (playerWins > enemyWins){
int deckNumberAfterAlgorithmOutput = (int)((playerWins-enemyWins) * (difficultyData.enemyLifeFactor / 3));
if (deckNumberAfterAlgorithmOutput < maxDecks){
deckNumber = deckNumberAfterAlgorithmOutput;
}
else {
deckNumber = maxDecks-1;
}
}
}
return deckNumber;
}
}

View File

@@ -0,0 +1,9 @@
{
"name":"Red bad",
"template":
{
"count":70,
"colors":["Red"],
"rares":0.3
}
}

View File

@@ -0,0 +1,9 @@
{
"name":"Red bad",
"template":
{
"count":60,
"colors":["Red"],
"rares":0.5
}
}

View File

@@ -0,0 +1,9 @@
{
"name":"Red bad",
"template":
{
"count":60,
"colors":["Red"],
"rares":0.8
}
}

View File

@@ -0,0 +1,9 @@
{
"name":"Red bad",
"template":
{
"count":50,
"colors":["Red"],
"rares":1
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@ ManaCost:5 R
Types:Creature Devil
PT:4/2
K:Undying
T:Mode$ ChangesZone | Origin$ Graveyard | Destination$ Battlefield | TriggerZones$ Battlefield | ValidCard$ Card.Self,Creature.YouOwn+Other | Execute$ ReanimateDmg | TriggerDescription$ Whenever CARDNAME or another creature enters the battlefield from your graveyard, that creature deals damage equal to its power to any target.
T:Mode$ ChangesZone | Origin$ Graveyard | Destination$ Battlefield | TriggerZones$ Battlefield | ValidCard$ Card.Self+YouOwn,Creature.YouOwn+Other | Execute$ ReanimateDmg | TriggerDescription$ Whenever CARDNAME or another creature enters the battlefield from your graveyard, that creature deals damage equal to its power to any target.
SVar:ReanimateDmg:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | DamageSource$ TriggeredCard | NumDmg$ Damage
SVar:Damage:TriggeredCard$CardPower
Oracle:Undying (When this creature dies, if it had no +1/+1 counters on it, return it to the battlefield under its owner's control with a +1/+1 counter on it.)\nWhenever Flayer of the Hatebound or another creature enters the battlefield from your graveyard, that creature deals damage equal to its power to any target.

View File

@@ -3,6 +3,6 @@ ManaCost:4 R
Types:Creature Orc Warrior
PT:5/4
K:Haste
S:Mode$ CanAttackIfHaste | ValidDefender$ Opponent | Description$ All creatures can attack your opponents and planeswalkers your opponents control as though those creatures had haste.
S:Mode$ CanAttackIfHaste | ValidTarget$ Opponent,Planeswalker.OppCtrl | Description$ All creatures can attack your opponents and planeswalkers your opponents control as though those creatures had haste.
SVar:PlayMain1:TRUE
Oracle:Haste\nAll creatures can attack your opponents and planeswalkers your opponents control as though those creatures had haste.

View File

@@ -3,7 +3,7 @@ ManaCost:1 B B
Types:Legendary Creature Zombie Knight
PT:3/3
S:Mode$ Continuous | Affected$ Card.Self | MayPlay$ True | AffectedZone$ Graveyard | EffectZone$ Graveyard
S:Mode$ CantBeCast | ValidCard$ Card.Self | Origin$ Exile,Hand,Library,Command | EffectZone$ Graveyard,Hand,Library,Command | Description$ You may cast CARDNAME from your graveyard, but not from anywhere else.
S:Mode$ CantBeCast | ValidCard$ Card.Self | Origin$ Exile,Hand,Library,Command | EffectZone$ Graveyard,Hand,Library,Command,Stack | Description$ You may cast CARDNAME from your graveyard, but not from anywhere else.
S:Mode$ Continuous | Affected$ Knight.YouCtrl | MayPlay$ True | EffectZone$ Battlefield | AffectedZone$ Graveyard | Description$ As long as CARDNAME is on the battlefield, you may play Knight cards from your graveyard.
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigLose | TriggerDescription$ When CARDNAME dies, you lose 2 life.
SVar:TrigLose:DB$ LoseLife | Defined$ You | LifeAmount$ 2

View File

@@ -1,7 +1,7 @@
Name:Land Grant
ManaCost:1 G
Types:Sorcery
S:Mode$ Continuous | CharacteristicDefining$ True | AddKeyword$ Alternative Cost:Reveal<1/Hand> | CheckSVar$ X | SVarCompare$ EQ0 | Description$ If you have no land cards in hand, you may reveal your hand rather than pay Land Grant's mana cost.
S:Mode$ Continuous | CharacteristicDefining$ True | AddKeyword$ Alternative Cost:Reveal<1/Hand> | CheckSVar$ X | SVarCompare$ EQ0 | Description$ If you have no land cards in hand, you may reveal your hand rather than pay this spell's mana cost.
SVar:X:Count$TypeInYourHand.Land
A:SP$ ChangeZone | Cost$ 1 G | Origin$ Library | Destination$ Hand | ChangeType$ Forest | ChangeNum$ 1 | SpellDescription$ Search your library for a Forest card, reveal that card, put it into your hand, then shuffle.
Oracle:If you have no land cards in hand, you may reveal your hand rather than pay this spell's mana cost.\nSearch your library for a Forest card, reveal that card, put it into your hand, then shuffle.

View File

@@ -3,7 +3,7 @@ ManaCost:3
Types:Artifact Creature Phyrexian Dragon
PT:2/2
K:Double Strike
T:Mode$ ChangesZone | Origin$ Graveyard | Destination$ Battlefield | OptionalDecider$ You | TriggerZones$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDiscard | TriggerDescription$ When CARDNAME enters the battlefield from your graveyard, you may discard your hand. If you do, draw three cards.
T:Mode$ ChangesZone | Origin$ Graveyard | Destination$ Battlefield | OptionalDecider$ You | TriggerZones$ Battlefield | ValidCard$ Card.Self+YouOwn | Execute$ TrigDiscard | TriggerDescription$ When CARDNAME enters the battlefield from your graveyard, you may discard your hand. If you do, draw three cards.
SVar:TrigDiscard:DB$ Discard | Mode$ Hand | Defined$ You | SubAbility$ DBDraw
SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 3
K:Unearth:3 R R

View File

@@ -2,7 +2,7 @@ Name:Thaumatic Compass
ManaCost:2
Types:Artifact
A:AB$ ChangeZone | Cost$ 3 T | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card and put that card into your hand, then shuffle.
T:Mode$ Phase | Phase$ End of Turn | TriggerZones$ Battlefield | IsPresent$ Land.YouCtrl | PresentCompare$ GE7 | Execute$ DBTransform | TriggerDescription$ At the beginning of your end step, if you control seven or more lands, transform Thaumatic Compass.
T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | IsPresent$ Land.YouCtrl | PresentCompare$ GE7 | Execute$ DBTransform | TriggerDescription$ At the beginning of your end step, if you control seven or more lands, transform Thaumatic Compass.
SVar:DBTransform:DB$ SetState | Defined$ Self | Mode$ Transform
AlternateMode:DoubleFaced
Oracle:{3}, {T}: Search your library for a basic land card, reveal it, put it into your hand, then shuffle.\nAt the beginning of your end step, if you control seven or more lands, transform Thaumatic Compass.

View File

@@ -2,7 +2,7 @@ Name:The Lady of Otaria
ManaCost:3 R G
Types:Legendary Creature Avatar
PT:5/5
SVar:AltCost:Cost$ tapXType<3/Creature.Dwarf> | Description$ You may tap three untapped Dwarves you control rather than pay this spell's mana cost.
SVar:AltCost:Cost$ tapXType<3/Dwarf> | Description$ You may tap three untapped Dwarves you control rather than pay this spell's mana cost.
T:Mode$ Phase | Phase$ End of Turn | TriggerZones$ Battlefield | Execute$ TrigDig | CheckSVar$ X | TriggerDescription$ At the beginning of each end step, if a land you controlled was put into your graveyard from the battlefield this turn, reveal the top four cards of your library. Put any number of Dwarf cards from among them into your hand and the rest on the bottom of your library in a random order.
SVar:TrigDig:DB$ Dig | DigNum$ 4 | ChangeValid$ Dwarf | DestinationZone$ Hand | RestRandomOrder$ True | AnyNumber$ True
SVar:X:Count$ThisTurnEntered_Graveyard_from_Battlefield_Land.YouCtrl+YouOwn

View File

@@ -0,0 +1,14 @@
Name:Archfiend of the Dross
ManaCost:2 B
Types:Creature Phyrexian Demon
PT:6/6
K:Flying
K:etbCounter:OIL:4
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigRemoveCtr | TriggerDescription$ At the beginning of your upkeep, remove an oil counter from CARDNAME. Then if it has no oil counters on it, you lose the game.
SVar:TrigRemoveCtr:DB$ RemoveCounter | Defined$ Self | CounterType$ OIL | CounterNum$ 1 | SubAbility$ LoseGame
SVar:LoseGame:DB$ LosesGame | Defined$ You | ConditionDefined$ Self | ConditionPresent$ Card.counters_EQ0_OIL
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.OppCtrl | TriggerZones$ Battlefield | Execute$ TrigLoseLife | TriggerDescription$ Whenever a creature an opponent controls dies, its controller loses 2 life.
SVar:TrigLoseLife:DB$ LoseLife | LifeAmount$ 2 | Defined$ TriggeredCardController
DeckHas:Ability$Counters
AI:RemoveDeck:Random
Oracle:Flying\nArchfiend of the Dross enters the battlefield with four oil counters on it.\nAt the beginning of your upkeep, remove an oil counter from Archfiend of the Dross. Then if it has no oil counters on it, you lose the game.\nWhenever a creature an opponent controls dies, its controller loses 2 life.

View File

@@ -0,0 +1,8 @@
Name:Black Sun's Twilight
ManaCost:X B
Types:Instant
A:SP$ Pump | ValidTgts$ Creature | TgtPrompt$ Select up to one target creature | TargetMin$ 0 | TargetMax$ 1 | NumAtt$ -X | NumDef$ -X | IsCurse$ True | SubAbility$ DBReanimate | SpellDescription$ Up to one target creature gets -X/-X until end of turn. If X is 5 or more, return a creature card with mana value X or less from your graveyard to the battlefield tapped.
SVar:DBReanimate:DB$ ChangeZone | Origin$ Graveyard | Chooser$ You | ChangeNum$ 1 | ConditionCheckSVar$ X | ConditionSVarCompare$ GE5 | Destination$ Battlefield | Hidden$ True | Tapped$ True | ChangeType$ Creature.YouOwn+cmcLEX
SVar:X:Count$xPaid
DeckHas:Ability$Graveyard
Oracle:Up to one target creature gets -X/-X until end of turn. If X is 5 or more, return a creature card with mana value X or less from your graveyard to the battlefield tapped.

View File

@@ -0,0 +1,9 @@
Name:Blade of Shared Souls
ManaCost:2 U
Types:Artifact Equipment
K:For Mirrodin
T:Mode$ Attached | ValidSource$ Card.Self | TriggerZones$ Battlefield | ValidTarget$ Creature | Execute$ TrigCopy | TriggerDescription$ Whenever CARDNAME becomes attached to a creature, for as long as CARDNAME remains attached to it, you may have that creature become a copy of another target creature you control.
SVar:TrigCopy:DB$ Clone | ValidTgts$ Creature.YouCtrl+NotTriggeredTarget | TgtPrompt$ Choose another target creature you control | CloneTarget$ TriggeredTargetLKICopy | Duration$ UntilUnattached
K:Equip:2
DeckHas:Ability$Token & Type$Rebel & Color$Red
Oracle:For Mirrodin! (When this Equipment enters the battlefield, create a 2/2 red Rebel creature token, then attach this to it.)\nWhenever Blade of Shared Souls becomes attached to a creature, for as long as Blade of Shared Souls remains attached to it, you may have that creature become a copy of another target creature you control.\nEquip {2}

View File

@@ -0,0 +1,11 @@
Name:Bloated Contaminator
ManaCost:2 G
Types:Creature Phyrexian Beast
PT:4/4
K:Trample
K:Toxic:1
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigProliferate | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, proliferate.
SVar:TrigProliferate:DB$ Proliferate
DeckHas:Ability$Proliferate
DeckHints:Type$Planeswalker & Ability$Counters
Oracle:Trample\nToxic 1\nWhenever Bloated Contaminator deals combat damage to a player, proliferate.

View File

@@ -0,0 +1,11 @@
Name:Conduit of Worlds
ManaCost:2 G G
Types:Artifact
S:Mode$ Continuous | Affected$ Land.YouOwn | MayPlay$ True | AffectedZone$ Graveyard | Description$ You may play lands from your graveyard.
A:AB$ Play | Cost$ T | ValidSA$ Spell | SorcerySpeed$ True | TgtPrompt$ Choose target nonland permanent card in your graveyard | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ0 | TgtZone$ Graveyard | ValidTgts$ Permanent.nonLand+YouOwn | RememberPlayed$ True | Optional$ True | SubAbility$ DBEffect | SpellDescription$ Choose target nonland permanent card in your graveyard. If you haven't cast a spell this turn, you may cast that card. If you do, you can't cast additional spells this turn. Activate only as a sorcery.
SVar:DBEffect:DB$ Effect | Name$ Conduit of Worlds's Effect | ConditionDefined$ Remembered | ConditionPresent$ Card | StaticAbilities$ STCantBeCast | SubAbility$ DBCleanup
SVar:STCantBeCast:Mode$ CantBeCast | EffectZone$ Command | ValidCard$ Card | Caster$ You | Description$ You can't cast additional spells this turn.
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Count$ThisTurnCast_Spell.YouCtrl
DeckHints:Ability$Graveyard|Mill|Dredge
Oracle:You may play lands from your graveyard.\n{T}: Choose target nonland permanent card in your graveyard. If you haven't cast a spell this turn, you may cast that card. If you do, you can't cast additional spells this turn. Activate only as a sorcery.

View File

@@ -0,0 +1,8 @@
Name:Dragonwing Glider
ManaCost:3 R R
Types:Artifact Equipment
K:For Mirrodin
S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 2 | AddToughness$ 2 | AddKeyword$ Flying & Haste | Description$ Equipped creature gets +2/+2 and has flying and haste.
K:Equip:3 R R
DeckHas:Ability$Token & Type$Rebel
Oracle:For Mirrodin! (When this Equipment enters the battlefield, create a 2/2 red Rebel creature token, then attach this to it.)\nEquipped creature gets +2/+2 and has flying and haste.\nEquip {3}{R}{R}

View File

@@ -0,0 +1,5 @@
Name:Encroaching Mycosynth
ManaCost:3 U
Types:Artifact
S:Mode$ Continuous | Affected$ Permanent.nonLand+YouCtrl | AffectedZone$ Battlefield,Hand,Graveyard,Exile,Stack,Library,Command | AddType$ Artifact | Description$ Nonland permanents you control are artifacts in addition to their other types. The same is true for permanent spells you control and nonland permanent cards you own that aren't on the battlefield.
Oracle:Nonland permanents you control are artifacts in addition to their other types. The same is true for permanent spells you control and nonland permanent cards you own that aren't on the battlefield.

View File

@@ -0,0 +1,12 @@
Name:Evolved Spinoderm
ManaCost:2 G G
Types:Creature Phyrexian Beast
PT:5/5
K:etbCounter:OIL:4
S:Mode$ Continuous | Affected$ Creature.Self+counters_LE2_OIL | AddKeyword$ Trample | Description$ CARDNAME has trample as long as it has two or fewer oil counters on it. Otherwise, it has hexproof.
S:Mode$ Continuous | Affected$ Creature.Self+counters_GT2_OIL | AddKeyword$ Hexproof
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigRemoveCtr | TriggerDescription$ At the beginning of your upkeep, remove an oil counter from CARDNAME. Then if it has no oil counters on it, sacrifice it.
SVar:TrigRemoveCtr:DB$ RemoveCounter | Defined$ Self | CounterType$ OIL | CounterNum$ 1 | SubAbility$ DBSacrifice
SVar:DBSacrifice:DB$ Sacrifice | ConditionDefined$ Self | ConditionPresent$ Card.counters_EQ0_OIL
DeckHas:Ability$Counters
Oracle:Evolved Spinoderm enters the battlefield with four oil counters on it.\nEvolved Spinoderm has trample as long as it has two or fewer oil counters on it. Otherwise, it has hexproof.\nAt the beginning of your upkeep, remove an oil counter from Evolved Spinoderm. Then if it has no oil counters on it, sacrifice it.

View File

@@ -0,0 +1,12 @@
Name:Geth, Thane of Contracts
ManaCost:1 B B
Types:Legendary Creature Phyrexian Zombie
PT:3/4
S:Mode$ Continuous | Affected$ Creature.Other+YouCtrl | AddPower$ -1 | AddToughness$ -1 | Description$ Other creatures you control get -1/-1.
A:AB$ ChangeZone | Cost$ 1 B B T | Origin$ Graveyard | SorcerySpeed$ True | Destination$ Battlefield | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature in your graveyard | SubAbility$ DBAnimate | SpellDescription$ Return target creature card from your graveyard to the battlefield. It gains "If this creature would leave the battlefield, exile it instead of putting it anywhere else." Activate only as a sorcery.
SVar:DBAnimate:DB$ Animate | Replacements$ ReplaceLeaves | Defined$ Targeted | Duration$ Permanent
SVar:ReplaceLeaves:Event$ Moved | ActiveZones$ Battlefield | Origin$ Battlefield | ValidCard$ Card.Self | ReplaceWith$ Exile | Description$ If this creature would leave the battlefield, exile it instead.
SVar:Exile:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | Defined$ ReplacedCard
DeckHints:Ability$Graveyard|Mill|Dredge|Sacrifice
DeckHas:Ability$Graveyard
Oracle:Other creatures you control get -1/-1.\n{1}{B}{B}, {T}: Return target creature card from your graveyard to the battlefield. It gains "If this creature would leave the battlefield, exile it instead of putting it anywhere else." Activate only as a sorcery.

View File

@@ -0,0 +1,13 @@
Name:Glissa Sunslayer
ManaCost:1 B G
Types:Legendary Creature Phyrexian Zombie Elf
PT:3/3
K:First strike
K:Deathtouch
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigCharm | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, ABILITY
SVar:TrigCharm:DB$ Charm | Choices$ DBDraw,DBDestroy,DBRemove
SVar:DBDraw:DB$ Draw | SubAbility$ DBLoseLife | SpellDescription$ You draw a card and you lose 1 life.
SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 1
SVar:DBDestroy:DB$ Destroy | ValidTgts$ Enchantment | SpellDescription$ Destroy target enchantment.
SVar:DBRemove:DB$ RemoveCounter | ValidTgts$ Permanent | TgtPrompt$ Select target permanent | CounterType$ Any | CounterNum$ 3 | SpellDescription$ Remove up to three counters from target permanent.
Oracle:First strike, deathtouch\nWhenever Glissa Sunslayer deals combat damage to a player, choose one —\n• You draw a card and you lose 1 life.\n• Destroy target enchantment.\n• Remove up to three counters from target permanent.

View File

@@ -0,0 +1,8 @@
Name:Graaz, Unstoppable Juggernaut
ManaCost:8
Types:Legendary Artifact Creature Juggernaut
PT:7/5
S:Mode$ MustAttack | ValidCreature$ Juggernaut.YouCtrl | Description$ Juggernauts you control attack each combat if able.
S:Mode$ CantBlockBy | ValidAttacker$ Juggernaut.YouCtrl | ValidBlocker$ Creature.Wall | Description$ Juggernauts you control can't be blocked by Walls.
S:Mode$ Continuous | Affected$ Creature.Other+YouCtrl | AffectedZone$ Battlefield | SetPower$ 5 | SetToughness$ 3 | AddType$ Juggernaut | Description$ Other creatures you control have base power and toughness 5/3 and are Juggernauts in addition to their other creature types.
Oracle:Juggernauts you control attack each combat if able.\nJuggernauts you control can't be blocked by Walls.\nOther creatures you control have base power and toughness 5/3 and are Juggernauts in addition to their other creature types.

View File

@@ -0,0 +1,16 @@
Name:Jace, the Perfected Mind
ManaCost:2 PU U
Types:Legendary Planeswalker Jace
Loyalty:5
K:Compleated
A:AB$ Pump | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | NumAtt$ -3 | IsCurse$ True | Duration$ UntilYourNextTurn | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Creature | TgtPrompt$ Select up to one target creature | SpellDescription$ Until your next turn, up to one target creature gets -3/-0.
A:AB$ Mill | Cost$ SubCounter<2/LOYALTY> | NumCards$ 3 | ValidTgts$ Player | TgtPrompt$ Select target player | SubAbility$ DBDraw | SpellDescription$ Target player mills three cards.
SVar:DBDraw:DB$ Draw | NumCards$ Y | SpellDescription$ Then if a graveyard has twenty or more cards in it, you draw three cards. Otherwise, you draw a card.
A:AB$ Mill | Cost$ SubCounter<X/LOYALTY> | NumCards$ Z | ValidTgts$ Player | TgtPrompt$ Select target player | SpellDescription$ Target player mills three times X cards.
SVar:Y:Count$Compare CheckGrave GE20.3.1
SVar:CheckGrave:PlayerCountPlayers$HighestValidGraveyard Card.YouOwn
SVar:X:Count$xPaid
SVar:Z:SVar$X/Times.3
DeckHas:Ability$Mill
DeckHints:Ability$Mill
Oracle:Compleated ({U/P} can be paid with {U} or 2 life. If life was paid, this planeswalker enters with two fewer loyalty counters.)\n[+1]: Until your next turn, up to one target creature gets -3/-0.\n[2]: Target player mills three cards. Then if a graveyard has twenty or more cards in it, you draw three cards. Otherwise, you draw a card.\n[X]: Target player mills three times X cards.

View File

@@ -0,0 +1,10 @@
Name:Karumonix, the Rat King
ManaCost:1 B B
Types:Legendary Creature Phyrexian Rat
PT:3/3
K:Toxic:1
S:Mode$ Continuous | Affected$ Rat.YouCtrl+Other | AddKeyword$ Toxic:1 | Description$ Other Rats you control have toxic 1.
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDig | TriggerDescription$ When NICKNAME enters the battlefield, look at the top five cards of your library. You may reveal any number of Rat cards from among them and put the revealed cards into your hand. Put the rest on the bottom of your library in a random order.
SVar:TrigDig:DB$ Dig | DigNum$ 5 | AnyNumber$ True | ChangeValid$ Rat | RestRandomOrder$ True
DeckNeeds:Type$Rat
Oracle:Toxic 1\nOther Rats you control have toxic 1.\nWhen Karumonix enters the battlefield, look at the top five cards of your library. You may reveal any number of Rat cards from among them and put the revealed cards into your hand. Put the rest on the bottom of your library in a random order.

View File

@@ -0,0 +1,11 @@
Name:Kemba, Kha Enduring
ManaCost:1 W
Types:Creature Cat Cleric
PT:2/2
T:Mode$ ChangesZone | ValidCard$ Card.Self,Creature.Other+Cat+YouCtrl | Origin$ Any | Destination$ Battlefield | TriggerZones$ Battlefield | Execute$ TrigAttach | TriggerDescription$ Whenever CARDNAME or another Cat enters the battlefield under your control, attach up to one target Equipment you control to that creature.
SVar:TrigAttach:DB$ Attach | Defined$ TriggeredCard | Object$ Targeted | ValidTgts$ Equipment.YouCtrl | TgtPrompt$ Select up to one target Equipment you control | TargetMin$ 0 | TargetMax$ 1
S:Mode$ Continuous | Affected$ Creature.YouCtrl+equipped | AddPower$ 1 | AddToughness$ 1 | Description$ Equipped creatures you control get +1/+1.
A:AB$ Token | Cost$ 3 W W | TokenScript$ w_2_2_cat | TokenAmount$ 1 | TokenOwner$ You | SpellDescription$ Create a 2/2 white Cat creature token.
DeckNeeds:Type$Equipment
DeckHas:Ability$Token
Oracle:Whenever Kemba, Kha Enduring or another Cat enters the battlefield under your control, attach up to one target Equipment you control to that creature.\nEquipped creatures you control get +1/+1.\n{3}{W}{W}: Create a 2/2 white Cat creature token.

View File

@@ -0,0 +1,11 @@
Name:Malcator, Purity Overseer
ManaCost:1 W U
Types:Creature Phyrexian Elephant Wizard
PT:1/1
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ TriggerDescription$ When CARDNAME enters the battlefield, create a 3/3 colorless Phyrexian Golem artifact creature token.
T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | CheckSVar$ X | SVarCompare$ GE3 | Execute$ TrigToken | TriggerDescription$ At the beginning of your end step, if three or more artifacts entered the battlefield under your control this turn, create a 3/3 colorless Phyrexian Golem artifact creature token.
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_3_3_a_phyrexian_golem | TokenOwner$ You
SVar:X:Count$ThisTurnEntered_Battlefield_Artifact.YouCtrl
DeckHas:Ability$Token & Type$Golem|Artifact
DeckHints:Type$Artifact
Oracle:When Malcator, Purity Overseer enters the battlefield, create a 3/3 colorless Phyrexian Golem artifact creature token.\nAt the beginning of your end step, if three or more artifacts entered the battlefield under your control this turn, create a 3/3 colorless Phyrexian Golem artifact creature token.

View File

@@ -0,0 +1,15 @@
Name:Mercurial Spelldancer
ManaCost:1 U
Types:Creature Phyrexian Rogue
PT:2/1
S:Mode$ CantBlockBy | ValidAttacker$ Creature.Self | Description$ CARDNAME can't be blocked.
T:Mode$ SpellCast | ValidCard$ Card.nonCreature | ValidActivatingPlayer$ You | Execute$ TrigPutCounter | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast a noncreature spell, put an oil counter on CARDNAME.
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ OIL | CounterNum$ 1
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | Execute$ TrigDelayTrig | CombatDamage$ True | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, you may remove two oil counters from it. If you do, when you cast your next instant or sorcery spell this turn, copy that spell. You may choose new targets for the copy.
SVar:TrigDelayTrig:AB$ DelayedTrigger| Cost$ SubCounter<2/OIL> | AILogic$ SpellCopy | Execute$ EffTrigCopy | ThisTurn$ True | Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | SpellDescription$ When you cast your next instant or sorcery spell this turn, copy that spell. You may choose new targets for the copy.
SVar:EffTrigCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | MayChooseTarget$ True
SVar:HasAttackEffect:TRUE
SVar:BuffedBy:Card.nonCreature
DeckHas:Ability$Counters
DeckHints:Type$Instant|Sorcery
Oracle:Mercurial Spelldancer can't be blocked.\nWhenever you cast a noncreature spell, put an oil counter on Mercurial Spelldancer.\nWhenever Mercurial Spelldancer deals combat damage to a player, you may remove two oil counters from it. If you do, when you cast your next instant or sorcery spell this turn, copy that spell. You may choose new targets for the copy.

View File

@@ -0,0 +1,6 @@
Name:Mirran Safehouse
ManaCost:3
Types:Artifact
S:Mode$ Continuous | Affected$ Card.Self | EffectZone$ Battlefield | GainsAbilitiesOf$ Land | GainsAbilitiesOfZones$ Graveyard | Description$ As long as CARDNAME is on the battlefield, it has all activated abilities of all land cards in all graveyards.
DeckHints:Ability$Graveyard
Oracle:As long as Mirran Safehouse is on the battlefield, it has all activated abilities of all land cards in all graveyards.

View File

@@ -0,0 +1,15 @@
Name:Nahiri, the Unforgiving
ManaCost:1 R RW W
Types:Legendary Planeswalker Nahiri
Loyalty:5
K:Compleated
A:AB$ Effect | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | ValidTgts$ Creature | TargetMin$ 0 | TargetMax$ 1 | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | Duration$ UntilYourNextTurn | StaticAbilities$ MustAttack | SpellDescription$ Until your next turn, up to one target creature attacks a player each combat if able.
SVar:MustAttack:Mode$ MustAttack | MustAttack$ Player | ValidCreature$ Card.IsRemembered | Description$ This creature attacks this turn if able.
A:AB$ Draw | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | SubAbility$ DBDiscard | SpellDescription$ Draw a card, then discard a card.
SVar:DBDiscard:DB$ Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose
A:AB$ ChangeZone | Cost$ AddCounter<0/LOYALTY> | Planeswalker$ True | SubAbility$ DBCopy | Origin$ Graveyard | RememberChanged$ True | Destination$ Exile | TgtPrompt$ Select target creature or equipment in your graveyard | ValidTgts$ Creature.YouCtrl+cmcLTX,Equipment.YouCtrl+cmcLTX | SpellDescription$ Exile target creature or Equipment card with mana value less than NICKNAME's loyalty from your graveyard. Create a token that's a copy of it. That token gains haste. Exile it at the beginning of the next end step.
SVar:DBCopy:DB$ CopyPermanent | Defined$ Remembered | PumpKeywords$ Haste | AtEOT$ Exile | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
DeckHas:Ability$Discard|Token
SVar:X:Count$CardCounters.LOYALTY
Oracle:Compleated ({R/W} can be paid with {R}, {W}, or 2 life. If life was paid, this planeswalker enters with two fewer loyalty counters.)\n[+1]: Until your next turn, up to one target creature attacks a player each combat if able.\n[+1]: Discard a card, then draw a card.\n[+0]:Exile target creature or Equipment card with mana value less than Nahiri's loyalty from your graveyard. Create a token that's a copy of it. That token gains haste. Exile it at the beginning of the next end step.

View File

@@ -0,0 +1,10 @@
Name:Norn's Wellspring
ManaCost:1 W
Types:Artifact
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigScry | TriggerDescription$ Whenever a creature you control dies, scry 1 and put an oil counter on CARDNAME.
SVar:TrigScry:DB$ Scry | ScryNum$ 1 | SubAbility$ DBCounter
SVar:DBCounter:DB$ PutCounter | Defined$ Self | CounterType$ OIL | CounterNum$ 1
A:AB$ Draw | Cost$ 1 T SubCounter<2/OIL> | SpellDescription$ Draw a card.
DeckHas:Ability$Counters
DeckHints:Ability$Sacrifice
Oracle:Whenever a creature you control dies, scry 1 and put an oil counter on Norn's Wellspring.\n{1}, {T}, Remove two oil counters from Norn's Wellspring: Draw a card.

View File

@@ -0,0 +1,12 @@
Name:Ovika, Enigma Goliath
ManaCost:5 U R
Types:Legendary Creature Phyrexian Nightmare
PT:6/6
K:Flying
K:Ward:3 PayLife<3>
T:Mode$ SpellCast | ValidCard$ Card.nonCreature | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever you cast a noncreature spell, create X 1/1 red Phyrexian Goblin creature tokens, where X is the mana value of that spell. They gain haste until end of turn.
SVar:TrigToken:DB$ Token | TokenAmount$ X | TokenScript$ r_1_1_phyrexian_goblin | TokenOwner$ You | PumpKeywords$ Haste | PumpDuration$ EOT
SVar:X:TriggeredStackInstance$CardManaCostLKI
SVar:BuffedBy:Card.nonCreature
DeckHas:Ability$Token & Type$Goblin
Oracle:Flying\nWard — {3}, Pay 3 life.\nWhenever you cast a noncreature spell, create X 1/1 red Phyrexian Goblin creature tokens, where X is the mana value of that spell. They gain haste until end of turn.

View File

@@ -0,0 +1,9 @@
Name:Red Sun's Twilight
ManaCost:X R R
Types:Sorcery
A:SP$ Destroy | TargetMin$ 0 | TargetMax$ X | ValidTgts$ Artifact | TgtPrompt$ Select X target artifacts | RememberDestroyed$ True | SubAbility$ DBCopy | SpellDescription$ Destroy up to X target artifacts.
SVar:DBCopy:DB$ CopyPermanent | Defined$ Remembered | ConditionCheckSVar$ X | ConditionSVarCompare$ GE5 | PumpKeywords$ Haste | SubAbility$ DBCleanup | AtEOT$ Exile | AILogic$ BeforeCombat | SpellDescription$ If X is 5 or more, for each artifact destroyed this way, create a token that's a copy of it. Those tokens gain haste. Exile them at the beginning of the next end step.
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Count$xPaid
DeckHas:Ability$Token
Oracle:Destroy up to X target artifacts. If X is 5 or more, for each artifact destroyed this way, create a token that's a copy of it. Those tokens gain haste. Exile them at the beginning of the next end step.

View File

@@ -0,0 +1,13 @@
Name:Ria Ivor, Bane of Bladehold
ManaCost:2 W B
Types:Legendary Creature Phyrexian Knight
PT:3/4
K:Battle cry
T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigEffect | TriggerDescription$ At the beginning of combat on your turn, the next time target creature would deal combat damage to one or more players this combat, prevent that damage. If damage is prevented this way, create that many 1/1 colorless Phyrexian Mite artifact creature tokens with toxic 1 and "This creature can't block."
SVar:TrigEffect:DB$ Effect | ValidTgts$ Creature | Duration$ UntilEndOfCombat | ReplacementEffects$ StrikeWithAwe | ExileOnMoved$ Battlefield | RememberObjects$ Targeted | AILogic$ Fog
SVar:StrikeWithAwe:Event$ DamageDone | IsCombat$ True | ValidTarget$ Player | ValidSource$ Card.IsRemembered | ReplaceWith$ CreateTokensInstead | PreventionEffect$ True | Description$ The next time target creature would deal combat damage to one or more players this combat, prevent that damage. If damage is prevented this way, create that many 1/1 colorless Phyrexian Mite artifact creature tokens with toxic 1 and "This creature can't block."
SVar:CreateTokensInstead:DB$ Token | TokenAmount$ X | TokenScript$ c_1_1_a_phyrexian_mite_toxic_noblock | TokenOwner$ You | SubAbility$ ExileEffect
SVar:ExileEffect:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile
SVar:X:ReplaceCount$DamageAmount
DeckHas:Ability$Token & Type$Mite|Artifact
Oracle:Battle cry\nAt the beginning of combat on your turn, the next time target creature would deal combat damage to one or more players this combat, prevent that damage. If damage is prevented this way, create that many 1/1 colorless Phyrexian Mite artifact creature tokens with toxic 1 and "This creature can't block."

View File

@@ -0,0 +1,12 @@
Name:Skrelv, Defector Mite
ManaCost:W
Types:Legendary Artifact Creature Phyrexian Mite
PT:1/1
K:Toxic:1
S:Mode$ CantBlockBy | ValidBlocker$ Creature.Self | Description$ CARDNAME can't block.
A:AB$ ChooseColor | Cost$ PW T | Defined$ You | AILogic$ MostProminentInHumanDeck | SubAbility$ DBPump | SpellDescription$ Choose a color. Another target creature you control gains toxic 1 and hexproof from that color until end of turn.
SVar:DBPump:DB$ Pump | Defined$ Targeted | ValidTgts$ Creature.YouCtrl+Other | TgtPrompt$ Select another target creature you control | KW$ Hexproof:Card.ChosenColor:chosen & Toxic:1 | StackDescription$ {c:Targeted} gains toxic 1 and hexproof from that color until end of turn. | DefinedKW$ ChosenColor | SubAbility$ DBEffect
SVar:DBEffect:DB$ Effect | RememberObjects$ Targeted | StaticAbilities$ CantBlockBy | SubAbility$ DBCleanup | SpellDescription$ It can't be blocked by creatures of that color this turn.
SVar:DBCleanup:DB$ Cleanup | ClearChosenColor$ True
SVar:CantBlockBy:Mode$ CantBlockBy | ValidAttacker$ Creature.IsRemembered | ValidBlocker$ Creature.ChosenColor | Description$ This creature can't be blocked by creatures of the chosen color this turn.
Oracle:Toxic 1\nSkrelv, Defector Mite can't block.\n{PW}, {T}: Choose a color. Another target creature you control gains toxic 1 and hexproof from that color until end of turn. It can't be blocked by creatures of that color this turn. ({PW} may be paid for with either {W} or 2 life.)

View File

@@ -0,0 +1,9 @@
Name:Soulless Jailer
ManaCost:2
Types:Artifact Creature Phyrexian Golem
PT:0/4
R:Event$ Moved | Layer$ CantHappen | ActiveZones$ Battlefield | Origin$ Graveyard | Destination$ Battlefield | ValidLKI$ Permanent | Prevent$ True | Description$ Permanent cards in graveyards can't enter the battlefield.
S:Mode$ CantBeCast | Origin$ Graveyard,Exile | Description$ Players can't cast spells from graveyards or exile.
SVar:NonStackingEffect:True
AI:RemoveDeck:Random
Oracle:Permanent cards in graveyards can't enter the battlefield.\nPlayers can't cast noncreature spells from graveyards or exile.

View File

@@ -0,0 +1,9 @@
Name:Tablet of Compleation
ManaCost:2
Types:Artifact
A:AB$ PutCounter | Cost$ T | CounterType$ OIL | CounterNum$ 1 | SpellDescription$ Put an oil counter on CARDNAME.
A:AB$ Mana | Cost$ T | CheckSVar$ X | Produced$ C | SVarCompare$ GE2 | SpellDescription$ Add {C}. Activate only if CARDNAME has two or more oil counters on it.
A:AB$ Draw | Cost$ 1 T | CheckSVar$ X | SVarCompare$ GE5 | SpellDescription$ Draw a card. Activate only if CARDNAME has five or more oil counters on it.
SVar:X:Count$CardCounters.OIL
DeckHas:Ability$Counters
Oracle:{T}: Put an oil counter on Tablet of Compleation.\n{T}: Add {C}. Activate only if Tablet of Compleation has two or more oil counters on it.\n{1}, {T}: Draw a card. Activate only if Tablet of Compleation has five or more oil counters on it.

View File

@@ -0,0 +1,9 @@
Name:The Filigree Sylex
ManaCost:2
Types:Legendary Artifact
A:AB$ PutCounter | Cost$ T | CounterType$ OIL | CounterNum$ 1 | SpellDescription$ Put an oil counter on CARDNAME.
A:AB$ DestroyAll | Cost$ T Sac<1/CARDNAME> | ValidCards$ Permanent.nonLand+cmcEQX | SpellDescription$ Destroy each nonland permanent with mana value equal to the number of oil counters on CARDNAME.
A:AB$ DealDamage | Cost$ T RemoveAnyCounter<10/OIL/Permanent.YouCtrl/among permanents you control> Sac<1/CARDNAME> | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 10 | SpellDescription$ It deals 10 damage to any target.
SVar:X:Count$CardCounters.OIL
DeckHas:Ability$Counters|Sacrifice
Oracle:{T}: Put an oil counter on The Filigree Sylex.\n{T}, Sacrifice The Filigree Sylex: Destroy each nonland permanent with mana value equal to the number of oil counters on The Filigree Sylex.\n{T}, Remove ten oil counters from among permanents you control and sacrifice The Filigree Sylex: It deals 10 damage to any target.

View File

@@ -0,0 +1,9 @@
Name:Thrun, Breaker of Silence
ManaCost:3 G G
Types:Legendary Creature Troll Shaman
PT:5/5
K:This spell can't be countered.
K:Trample
S:Mode$ CantTarget | ValidCard$ Card.Self | ValidSource$ Card.nonGreen+OppCtrl | Description$ CARDNAME can't be the target of nongreen spells your opponents control or abilities from nongreen sources your opponents control.
S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ Indestructible | Condition$ PlayerTurn | Description$ As long as it's your turn, NICKNAME has indestructible.
Oracle:This spell can't be countered.\nTrample\nThrun, Breaker of Silence can't be the target of nongreen spells your opponents control or abilities from nongreen sources your opponents control.\nAs long as it's your turn, Thrun has indestructible.

View File

@@ -0,0 +1,11 @@
Name:Tyvar, Jubilant Brawler
ManaCost:1 B G
Types:Legendary Planeswalker Tyvar
Loyalty:3
S:Mode$ ActivateAbilityAsIfHaste | ValidCard$ Creature.YouCtrl | Description$ You may activate abilities of creatures you control as though those creatures had haste.
A:AB$ Untap | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | ValidTgts$ Creature | TargetMin$ 0 | TargetMax$ 1 | SpellDescription$ Untap up to one target creature.
A:AB$ Mill | Cost$ SubCounter<1/LOYALTY> | Planeswalker$ True | NumCards$ 3 | Defined$ You | SubAbility$ DBChange | SpellDescription$ Mill three cards, then you may return a creature card with mana value 2 or less from your graveyard to the battlefield.
SVar:DBChange:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | SelectPrompt$ Choose a creature card with mana value 2 or less | Hidden$ True | ChangeType$ Creature.YouOwn+cmcLE2
DeckHas:Ability$Graveyard|Mill
DeckHints:Ability$Graveyard
Oracle:You may activate abilities of creatures you control as though those creatures had haste.\n[+1]: Untap up to one target creature.\n[2]: Mill three cards, then you may return a creature card with mana value 2 or less from your graveyard to the battlefield.

View File

@@ -0,0 +1,13 @@
Name:Unctus, Grand Metatect
ManaCost:1 U U
Types:Legendary Artifact Creature Phyrexian Vedalken
PT:2/4
S:Mode$ Continuous | Affected$ Creature.Other+Blue+YouCtrl | AddTrigger$ LootTrig | Description$ Other blue creatures you control have "Whenever this creature becomes tapped, draw a card, then discard a card."
SVar:LootTrig:Mode$ Taps | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$ Whenever this creature becomes tapped, draw a card, then discard a card.
SVar:TrigDraw:DB$ Draw | SubAbility$ DBDiscard
SVar:DBDiscard:DB$ Discard | Mode$ TgtChoose
S:Mode$ Continuous | Affected$ Creature.Artifact+Other+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Other artifact creatures you control get +1/+1.
A:AB$ Animate | Cost$ PU | ValidTgts$ Creature.YouCtrl | Colors$ Blue | Types$ Artifact | SorcerySpeed$ True | SpellDescription$ Until end of turn, target creature you control becomes a blue artifact in addition to its other colors and types. Activate only as a sorcery. ({PU} can be paid with either {U} or 2 life.)
DeckHints:Type$Artifact
DeckHas:Ability$Discard
Oracle:Other blue creatures you control have "Whenever this creature becomes tapped, draw a card, then discard a card."\nOther artifact creatures you control get + 1/+1.\n{P/U}: Until end of turn, target creature you control becomes a blue artifact in addition to its other colors and types. Activate only as a sorcery. ({U/P} can be paid with either {U} or 2 life.)

View File

@@ -0,0 +1,9 @@
Name:Urabrask's Forge
ManaCost:2 R
Types:Artifact
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ At the beginning of combat on your turn, put an oil counter on CARDNAME, then create an X/1 red Phyrexian Horror creature token with trample and haste, where X is the number of oil counters on Urabrask's Forge. Sacrifice that token at the beginning of the next end step.
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ OIL | CounterNum$ 1 | SubAbility$ DBToken
SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ r_x_1_phyrexian_horror_trample_haste | TokenOwner$ You | AtEOT$ Sacrifice | TokenPower$ X
SVar:X:Count$CardCounters.OIL
DeckHas:Ability$Token|Counters|Sacrifice & Type$Phyrexian|Horror
Oracle:At the beginning of combat on your turn, put an oil counter on Urabrask's Forge, then create an X/1 red Phyrexian Horror creature token with trample and haste, where X is the number of oil counters on Urabrask's Forge. Sacrifice that token at the beginning of the next end step.

View File

@@ -0,0 +1,10 @@
Name:Venerated Rotpriest
ManaCost:G
Types:Creature Phyrexian Druid
PT:1/2
K:Toxic:1
T:Mode$ BecomesTarget | ValidTarget$ Creature.YouCtrl | ValidSource$ Spell | TriggerZones$ Battlefield | Execute$ TrigPoison | TriggerDescription$ Whenever a creature you control becomes the target of a spell, target opponent gets a poison counter.
SVar:TrigPoison:DB$ Poison | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | Num$ 1
DeckHas:Ability$Counters
DeckHints:Type$Aura
Oracle:Toxic 1\nWhenever a creature you control becomes the target of a spell, target opponent gets a poison counter.

View File

@@ -0,0 +1,12 @@
Name:Vindictive Flamestoker
ManaCost:R
Types:Creature Phyrexian Wizard
PT:1/2
T:Mode$ SpellCast | ValidCard$ Card.nonCreature | ValidActivatingPlayer$ You | Execute$ TrigPutCounter | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast a noncreature spell, put an oil counter on CARDNAME.
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ OIL | CounterNum$ 1
A:AB$ Discard | Cost$ 6 R Sac<1/CARDNAME> | Defined$ You | Mode$ Hand | ReduceCost$ X | SubAbility$ DBDraw | SpellDescription$ Discard your hand, then draw four cards. This ability costs {1} less to activate for each oil counter on CARDNAME.
SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 4
SVar:X:Count$CardCounters.OIL
DeckHints:Type$Instant|Sorcery
DeckHas:Ability$Counters|Sacrifice|Discard
Oracle:Whenever you cast a noncreature spell, put an oil counter on Vindictive Flamestoker.\n{6}{R}, Sacrifice Vindictive Flamestoker: Discard your hand, then draw four cards. This ability costs {1} less to activate for each oil counter on Vindictive Flamestoker.

View File

@@ -0,0 +1,15 @@
Name:Vraska, Betrayal's Sting
ManaCost:4 B PB
Types:Legendary Planeswalker Vraska
Loyalty:6
K:Compleated
A:AB$ Draw | Cost$ AddCounter<0/LOYALTY> | Planeswalker$ True | SubAbility$ DBLoseLife | SpellDescription$ You draw a card and you lose 1 life. Proliferate.
SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 1 | SubAbility$ DBProliferate
SVar:DBProliferate:DB$ Proliferate
A:AB$ Animate | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | ValidTgts$ Creature | TgtPrompt$ Select target creature | RemoveAllAbilities$ True | Colors$ Green | Types$ Artifact,Treasure | Abilities$ TreasureSac | RemoveCardTypes$ True | Duration$ Permanent | SpellDescription$ Target creature becomes a Treasure artifact with "{T}, Sacrifice this artifact: Add one mana of any color" and loses all other card types and abilities.
SVar:TreasureSac:AB$ Mana | Cost$ T Sac<1/CARDNAME> | Produced$ Any | SpellDescription$ Add one mana of any color.
A:AB$ Poison | Cost$ SubCounter<9/LOYALTY> | ValidTgts$ Player | TgtPrompt$ Select target player | ConditionCheckSVar$ X | ConditionSVarCompare$ LT9 | Num$ Difference | SpellDescription$ If target player has fewer than nine poison counters, they get a number of poison counters equal to the difference.
SVar:X:TargetedPlayer$PoisonCounters
SVar:Difference:Number$9/Minus.X
DeckHints:Ability$Counters & Keyword$Infect|Toxic|Poisonous
Oracle:Compleated ({PB} can be paid with {B} or 2 life. If life was paid, this planeswalker enters with two fewer loyalty counters.)\n[0]: You draw a card and you lose 1 life. Proliferate.\n[2]: Target creature becomes a Treasure artifact with "{T}, Sacrifice this artifact: Add one mana of any color" and loses all other card types and abilities.\n[9]: If target player has fewer than nine poison counters, they get a number of poison counters equal to the difference.

View File

@@ -0,0 +1,10 @@
Name:White Sun's Twilight
ManaCost:X W W
Types:Sorcery
A:SP$ GainLife | LifeAmount$ X | SubAbility$ DBToken | SpellDescription$ You gain X life.
SVar:DBToken:DB$ Token | RememberTokens$ True | TokenAmount$ X | TokenScript$ c_1_1_a_phyrexian_mite_toxic_noblock | TokenOwner$ You | SubAbility$ DBWrath | SpellDescription$ Create X 1/1 colorless Phyrexian Mite artifact creature tokens with toxic 1 and "This creature can't block."
SVar:DBWrath:DB$ DestroyAll | ValidCards$ Creature.IsNotRemembered | ConditionCheckSVar$ X | ConditionSVarCompare$ GE5 | SubAbility$ DBCleanup | SpellDescription$ If X is 5 or more, Destroy all other creatures.
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Count$xPaid
DeckHas:Ability$Token|LifeGain & Type$Phyrexian|Mite|Artifact
Oracle:You gain X life. Create X 1/1 colorless Phyrexian Mite artifact creature tokens with toxic 1 and "This creature can't block." If X is 5 or more, destroy all other creatures. (Players dealt combat damage by a creature with toxic 1 also get a poison counter.)

View File

@@ -289,6 +289,7 @@ ScryfallCode=IKO
[buy a box]
275 M Zilortha, Strength Incarnate @Antonio José Manzanedo
275a M Zilortha, Strength Incarnate @Chase Stone
[borderless]
276 M Lukka, Coppercoat Outcast @Kieran Yanner

View File

@@ -6,12 +6,80 @@ Type=Expansion
ScryfallCode=ONE
[cards]
2 U Annex Sentry @David Astruga
10 M Elesh Norn, Mother of Machines @Martina Fackova
11 R The Eternal Wanderer @Alix Branwyn
19 R Kemba, Kha Enduring @Zoltan Boros
23 M Mondrak, Glory Dominus @Jason A. Engle
24 R Norn's Wellspring @Jonas De Ro
26 U Ossification @Nino Vecia
31 U Resistance Reunited @Aurore Folny
33 R Skrelv, Defector Mite @Brian Valeza
34 R Skrelv's Hive @Heonhwa Choe
38 R White Sun's Twilight @Julian Kok Joon Wen
42 R Blade of Shared Souls @Volkan Baǵa
43 R Blue Sun's Twilight @Piotr Dura
47 R Encroaching Mycosynth @Martin de Diego Sádaba
49 C Experimental Augury @Donato Giancola
57 M Jace, the Perfected Mind @Chase Stone
61 R Mercurial Spelldancer @Marcela Bolívar
63 R Mindsplice Apparatus @Ovidio Cartagena
75 R Unctus, Grand Metatect @Andrew Mar
82 R Archfiend of the Dross @Lie Setiawan
84 R Black Sun's Twilight @Jonas De Ro
95 R Geth, Thane of Contracts @Martin de Diego Sádaba
98 R Karumonix, the Rat King @Jason A. Engle
104 R Phyrexian Arena @Martina Fackova
105 M Phyrexian Obliterator @Maxim Kostin
108 U Sheoldred's Edict @Helge C. Balzer
115 M Vraska, Betrayal's Sting @Chase Stone
126 R Dragonwing Glider @Andreas Zafiratos
138 R Koth, Fire of Resistance @Eric Wilkerson
145 R Red Sun's Twilight @Julian Kok Joon Wen
149 R Slobad, Iron Goblin @Chris Seaman
153 R Urabrask's Forge @Lie Setiawan
154 R Vindictive Flamestoker @Xavier Ribeiro
159 R Bloated Contaminator @Johann Bodin
163 R Conduit of Worlds @Jokubas Uogintas
166 R Evolved Spinoderm @Svetlin Velinov
169 R Green Sun's Twilight @Yeong-Hao Han
175 M Nissa, Ascended Animist @Chase Stone
178 U Paladin of Predation @Lorenzo Mastroianni
186 R Thrun, Breaker of Silence @Simon Dominic
192 R Venerated Rotpriest @Brian Valeza
197 U Bladehold War-Whip @Tony Foti
201 R Ezuri, Stalker of Spheres @Fariba Khamseh
202 R Glissa Sunslayer @Gaboleps
203 R Jor Kadeen, First Goldwarden @Jeremy Wilson
204 R Kaito, Dancing Shadow @Daarken
205 R Kaya, Intangible Slayer @Marta Nael
206 R Kethek, Crucible Goliath @Zoltan Boros
207 M Lukka, Bound to Ruin @Chase Stone
209 R Melira, the Living Cure @Miranda Meeks
210 R Migloz, Maze Crusher @Zezhou Chen
211 R Nahiri, the Unforgiving @Chase Stone
213 R Ovika, Enigma Goliath @Antonio José Manzanedo
214 R Ria Ivor, Bane of Bladehold @Andreas Zafiratos
216 U Slaughter Singer @Adam Burn
218 R Tyvar, Jubilant Brawler @Victor Adame Minguez
219 R Venser, Corpse Puppet @Igor Kieryluk
222 R Argentum Masticore @Zack Stella
225 R Graaz, Unstoppable Juggernaut @Nestor Ossandon Leal
227 R The Filigree Sylex @
233 R Monument to Perfection @Igor Kieryluk
234 U Myr Convert @José Parodi
241 R Soulless Jailer @Donato Giancola
242 M Staff of Compleation @Igor Krstic
245 R Tablet of Compleation @Martin de Diego Sádaba
246 R Zenith Chronicler @Johann Bodin
248 R Blackcleave Cliffs @Yeong-Hao Han
249 R Copperline Gorge @Yeong-Hao Han
254 R Mirrex @
255 R The Monumental Facade @Bruce Brenneise
256 R The Mycosynth Gardens @Andrew Mar
257 R Razorverge Thicket @Randy Gallegos
258 R Seachrome Coast @Mauricio Calle
259 R The Seedcore @Kasia 'Kafis' Zielińska
262 L Plains @Alayna Danner
263 L Island @Alayna Danner
264 L Swamp @Alayna Danner
@@ -23,22 +91,91 @@ ScryfallCode=ONE
270 L Mountain @Mark Riddick
271 L Forest @Mark Riddick
272 L Plains @Sergey Glushakov
273 L Island @David Alvarez
273 L Island @David Álvarez
274 L Swamp @Julian Kok Joon Wen
275 L Mountain @Muhammad Firdaus
276 L Forest @Nadia Hurianova
277 U Ossification @Nino Vecia
278 C Experimental Augury @Donato Giancola
279 U Sheoldred's Edict @Helge C. Balzer
280 U Bladehold War-Whip @Tony Foti
281 U Slaughter Singer @Adam Burn
283 R Phyrexian Arena @Martina Fackova
284 R Green Sun's Twilight @Piotr Dura
292 R Karumonix, the Rat King @Jason A. Engle
297 U Myr Convert @JungShan
298 M Elesh Norn, Mother of Machines @Dominik Mayer
299 M Mondrak, Glory Dominus @Flavio Girón
301 R Skrelv, Defector Mite @Sidharth Chaturvedi
304 R Archfiend of the Dross @Sidharth Chaturvedi
306 R Geth, Thane of Contracts @Flavio Girón
307 R Karumonix, the Rat King @Flavio Girón
308 M Phyrexian Obliterator @Yu-ki Nishimoto
311 R Slobad, Iron Goblin @Dominik Mayer
317 R Ezuri, Stalker of Spheres @JungShan
318 R Glissa Sunslayer @Ravenna Tran
322 R Ovika, Enigma Goliath @Dominik Mayer
324 R Venser, Corpse Puppet @Dominik Mayer
325 M Jace, the Perfected Mind @Chase Stone
330 R Kemba, Kha Enduring @Izumi Tomoki
332 R Jor Kadeen, First Goldwarden @Sansyu
333 R Melira, the Living Cure @Miyuki Aramaki
334 R Graaz, Unstoppable Juggernaut @Kutat
335 R The Eternal Wanderer @Kento Matsuura
336 M Jace, the Perfected Mind @Showichi Furumi
337 M Vraska, Betrayal's Sting @Sansyu
338 R Koth, Fire of Resistance @Ai Nanahira
339 M Nissa, Ascended Animist @Gou Tanabe
340 R Kaito, Dancing Shadow @Kento Matsuura
341 R Kaya, Intangible Slayer @Showichi Furumi
342 M Lukka, Bound to Ruin @Yuki Fujisawa
343 R Nahiri, the Unforgiving @Hiro Usuda
344 R Tyvar, Jubilant Brawler @Iinuma Yuuki
345 M Elesh Norn, Mother of Machines @Pedro Potier
346 M Mondrak, Glory Dominus @rishxxv
351 M Phyrexian Obliterator @Pedro Potier
358 M Staff of Compleation @Joshua Alvarado
360 M Jace, the Perfected Mind @Joshua Alvarado
361 M Vraska, Betrayal's Sting @Scott M. Fischer
362 M Nissa, Ascended Animist @Cabrol
363 M Lukka, Bound to Ruin @DZO
364 R Nahiri, the Unforgiving @Scott M. Fischer
365 L Plains @Dan Mumford
366 L Island @Evan Cagle
367 L Swamp @Daria Khlebnikova
368 L Mountain @Indra Nugroho
369 L Forest @Alayna Danner
370 R Blackcleave Cliffs @Daniel Ljunggren
371 R Copperline Gorge @Sergey Glushakov
372 M Darkslick Shores @Daria Khlebnikova
373 R Razorverge Thicket @Jokubas Uogintas
374 R Seachrome Coast @Jokubas Uogintas
375 R Norn's Wellspring @Jonas De Ro
376 R Skrelv's Hive @Heonhwa Choe
377 R White Sun's Twilight @Julian Kok Joon Wen
378 R Blade of Shared Souls @Volkan Baǵa
379 R Blue Sun's Twilight @Piotr Dura
380 R Encroaching Mycosynth @Martin de Diego Sádaba
381 R Mercurial Spelldancer @Marcela Bolívar
383 R Black Sun's Twilight @Jonas De Ro
387 R Urabrask's Forge @Lie Setiawan
388 R Vindictive Flamestoker @Xavier Ribeiro
389 R Bloated Contaminator @Johann Bodin
390 R Conduit of Worlds @Jokubas Uogintas
391 R Green Sun's Twilight @Yeong-Hao Han
392 R Venerated Rotpriest @Brian Valeza
401 R The Monumental Facade @Bruce Brenneise
403 R The Seedcore @Kasia 'Kafis' Zielińska
404 R Mite Overseer @Nestor Ossandon Leal
405 R Serum Sovereign @Chris Rallis
406 R Kinzu of the Bleak Coven @Andreas Zafiratos
407 R Rhuk, Hexgold Nabber @Andrea De Dominicis
408 R Goliath Hatchery @Simon Dominic
409 R Mite Overseer @Nestor Ossandon Leal
410 R Serum Sovereign @Chris Rallis
411 R Kinzu of the Bleak Coven @Andreas Zafiratos
412 R Rhuk, Hexgold Nabber @Andrea De Dominicis
413 R Goliath Hatchery @Simon Dominic
414 M Elesh Norn, Mother of Machines @Martina Fackova
415 M Elesh Norn, Mother of Machines @Junji Ito
416 M Elesh Norn, Mother of Machines @Richard Whitters
@@ -46,7 +183,26 @@ ScryfallCode=ONE
419 M Elesh Norn, Mother of Machines @Junji Ito
420 M Elesh Norn, Mother of Machines @Dominik Mayer
421 M Elesh Norn, Mother of Machines @Richard Whitters
422 R The Eternal Wanderer @Kento Matsuura
423 R Kemba, Kha Enduring @Izumi Tomoki
424 M Mondrak, Glory Dominus @Flavio Girón
427 R Skrelv, Defector Mite @Sidharth Chaturvedi
428 M Jace, the Perfected Mind @Showichi Furumi
434 R Archfiend of the Dross @Sidharth Chaturvedi
438 R Geth, Thane of Contracts @Flavio Girón
439 R Karumonix, the Rat King @Flavio Girón
440 M Phyrexian Obliterator @Yu-ki Nishimoto
442 M Vraska, Betrayal's Sting @Sansyu
446 R Koth, Fire of Resistance @Ai Nanahira
448 R Slobad, Iron Goblin @Dominik Mayer
454 M Nissa, Ascended Animist @Gou Tanabe
461 R Glissa Sunslayer @Ravenna Tran
462 R Jor Kadeen, First Goldwarden @Sansyu
463 R Kaito, Dancing Shadow @Kento Matsuura
464 R Kaya, Intangible Slayer @Showichi Furumi
467 M Lukka, Bound to Ruin @Yuki Fujisawa
469 R Melira, the Living Cure @Miyuki Aramaki
471 R Nahiri, the Unforgiving @Hiro Usuda
476 R Tyvar, Jubilant Brawler @Iinuma Yuuki
477 R Venser, Corpse Puppet @Dominik Mayer
478 R Graaz, Unstoppable Juggernaut @Kutat

View File

@@ -11,224 +11,258 @@ ScryfallCode=POR
[cards]
1 R Alabaster Dragon @Ted Naifeh
157 R Alluring Scent @Ted Naifeh
158 U Anaconda @Andrew Robinson
158† U Anaconda @Andrew Robinson
40 R Ancestral Memories @Dan Frazier
2 C Angelic Blessing @DiTerlizzi
3 R Archangel @Quinton Hoover
4 U Ardent Militia @Mike Raabe
5 R Armageddon @John Avon
6 C Armored Pegasus @Andrew Robinson
79 U Arrogant Vampire @Zina Saunders
80 U Assassin's Blade @John Matson
41 R Balance of Power @Adam Rex
42 U Baleful Stare @John Coulthart
159 U Bee Sting @Phil Foglio
118 U Blaze @Gerry Grace
118s U Blaze @David A. Cherry
6d C Armored Pegasus @Andrew Robinson
7 R Blessed Reversal @Zina Saunders
8 R Blinding Light @John Coulthart
81 C Bog Imp @Christopher Rush
82 C Bog Raiders @Steve Luke
83 U Bog Wraith @Ted Naifeh
119 U Boiling Seas @Tom Wänerstrand
9 C Border Guard @Kev Walker
10 C Breath of Life @DiTerlizzi
160 U Bull Hippo @Roger Raupp
120 C Burning Cloak @Scott M. Fischer
43 R Capricious Sorcerer @Zina Saunders
84 U Charging Bandits @Dermot Power
11 U Charging Paladin @Kev Walker
161 R Charging Rhino @Una Fricker
12 U Defiant Stand @Hannibal King
13 C Devoted Hero @DiTerlizzi
14 C False Peace @Zina Saunders
15 C Fleet-Footed Monk @D. Alexander Gregory
16 C Foot Soldiers @Kev Walker
17 R Gift of Estates @Kaja Foglio
18 R Harsh Justice @John Coulthart
19 C Keen-Eyed Archers @Alan Rabinowitz
20 C Knight Errant @Dan Frazier
21 C Path of Peace @Pete Venters
22 C Regal Unicorn @Zina Saunders
23 U Renewing Dawn @John Avon
24 C Sacred Knight @Donato Giancola
25 C Sacred Nectar @Janine Johnston
26 U Seasoned Marshal @Zina Saunders
27 R Spiritual Guardian @Terese Nielsen
28 C Spotted Griffin @William Simpson
29 U Starlight @John Avon
30 U Starlit Angel @Rebecca Guay
30s U Starlit Angel @徐晓鸣
31 C Steadfastness @Kev Walker
32 R Stern Marshal @D. Alexander Gregory
33 R Temporary Truce @Mike Raabe
34 U Valorous Charge @Douglas Shuler
35 U Venerable Monk @D. Alexander Gregory
36 U Vengeance @Andrew Robinson
37 U Wall of Swords @Douglas Shuler
38 C Warrior's Charge @Ted Naifeh
38† C Warrior's Charge @Ted Naifeh
39 R Wrath of God @Mike Raabe
40 R Ancestral Memories @Dan Frazier
41 R Balance of Power @Adam Rex
42 U Baleful Stare @John Coulthart
43 R Capricious Sorcerer @Zina Saunders
44 C Cloak of Feathers @Rebecca Guay
45 R Cloud Dragon @John Avon
46 C Cloud Pirates @Phil Foglio
46d C Cloud Pirates @Phil Foglio
47 U Cloud Spirit @DiTerlizzi
48 U Command of Unsummoning @Phil Foglio
49 C Coral Eel @Una Fricker
121 C Craven Giant @Ron Spencer
50 R Cruel Fate @Adrian Smith
51 U Deep-Sea Serpent @Scott M. Fischer
52 R Djinn of the Lamp @DiTerlizzi
53 C Deja Vu @Hannibal King
54 R Exhaustion @DiTerlizzi
55 U Flux @Ted Naifeh
56 C Giant Octopus @John Matson
57 C Horned Turtle @Adrian Smith
57s C Horned Turtle @王玉群
58 U Ingenious Thief @Dan Frazier
59 U Man-o'-War @Una Fricker
60 C Merfolk of the Pearl Trident @DiTerlizzi
61 U Mystic Denial @Hannibal King
62 C Omen @Eric Peterson
63 C Owl Familiar @Janine Johnston
64 U Personal Tutor @D. Alexander Gregory
65 R Phantom Warrior @Dan Frazier
66 R Prosperity @Phil Foglio
66s R Prosperity @李有良
67 C Snapping Drake @Christopher Rush
67d C Snapping Drake @Christopher Rush
68 C Sorcerous Sight @Kaja Foglio
69 C Storm Crow @Una Fricker
69d C Storm Crow @Una Fricker
70 C Symbol of Unsummoning @Adam Rex
71 R Taunt @Phil Foglio
71s R Taunt @杨钊
72 U Theft of Dreams @Adam Rex
73 R Thing from the Deep @Paolo Parente
74 C Tidal Surge @Douglas Shuler
75 C Time Ebb @Alan Rabinowitz
76 C Touch of Brilliance @John Coulthart
77 C Wind Drake @Zina Saunders
78 U Withering Gaze @Scott M. Fischer
79 U Arrogant Vampire @Zina Saunders
80 U Assassin's Blade @John Matson
80s U Assassin's Blade @徐晓鸣
81 C Bog Imp @Christopher Rush
82 C Bog Raiders @Steve Luke
83 U Bog Wraith @Ted Naifeh
84 U Charging Bandits @Dermot Power
85 C Craven Knight @Charles Gillespie
86 R Cruel Bargain @Adrian Smith
50 R Cruel Fate @Adrian Smith
87 R Cruel Tutor @Kev Walker
51 U Deep-Sea Serpent @Scott M. Fischer
162 U Deep Wood @Paolo Parente
12 U Defiant Stand @Hannibal King
53 C Deja Vu @Hannibal King
122 U Desert Drake @Gerry Grace
123 R Devastation @Steve Luke
13 C Devoted Hero @DiTerlizzi
52 R Djinn of the Lamp @DiTerlizzi
88 R Dread Charge @Ted Naifeh
89 R Dread Reaper @Christopher Rush
90 U Dry Spell @Roger Raupp
124 R Earthquake @Adrian Smith
91 R Ebon Dragon @Donato Giancola
163 C Elite Cat Warrior @Eric Peterson
163† C Elite Cat Warrior @Eric Peterson
164 C Elven Cache @Rebecca Guay
165 C Elvish Ranger @DiTerlizzi
92 R Endless Cockroaches @Ron Spencer
54 R Exhaustion @DiTerlizzi
14 C False Peace @Zina Saunders
93 C Feral Shadow @Colin MacNeil
93d C Feral Shadow @Colin MacNeil
94 R Final Strike @John Coulthart
95 U Gravedigger @Scott M. Fischer
96 C Hand of Death @John Coulthart
96† C Hand of Death @John Coulthart
97 C Howling Fury @Mike Dringenberg
98 R King's Assassin @Zina Saunders
99 R Mercenary Knight @Adrian Smith
100 C Mind Knives @Rebecca Guay
101 C Mind Rot @Steve Luke
102 C Muck Rats @Colin MacNeil
103 U Nature's Ruin @Mike Dringenberg
104 U Noxious Toad @Adrian Smith
105 C Python @Alan Rabinowitz
106 U Rain of Tears @Eric Peterson
107 C Raise Dead @Charles Gillespie
107s C Raise Dead @李尤松
108 R Serpent Assassin @Roger Raupp
109 C Serpent Warrior @Roger Raupp
110 C Skeletal Crocodile @Mike Dringenberg
111 C Skeletal Snake @John Matson
112 C Soul Shred @Alan Rabinowitz
113 C Undying Beast @Steve Luke
114 U Vampiric Feast @D. Alexander Gregory
114s U Vampiric Feast @王峰
115 C Vampiric Touch @Zina Saunders
116 U Virtue's Ruin @Mike Dringenberg
117 R Wicked Pact @Adam Rex
117s R Wicked Pact @杨光恒
118 U Blaze @Gerry Grace
118† U Blaze @Gerry Grace
118s U Blaze @David A. Cherry
119 U Boiling Seas @Tom Wänerstrand
120 C Burning Cloak @Scott M. Fischer
121 C Craven Giant @Ron Spencer
122 U Desert Drake @Gerry Grace
123 R Devastation @Steve Luke
124 R Earthquake @Adrian Smith
125 R Fire Dragon @William Simpson
126 U Fire Imp @DiTerlizzi
127 C Fire Snake @Steve Luke
128 R Fire Tempest @Mike Dringenberg
129 U Flashfires @Randy Gallegos
15 C Fleet-Footed Monk @D. Alexander Gregory
55 U Flux @Ted Naifeh
16 C Foot Soldiers @Kev Walker
212 L Forest @John Avon
212s L Forest @李铁
213 L Forest @John Avon
213s L Forest @李铁
130 R Forked Lightning @Ted Naifeh
166 C Fruition @Steve Luke
56 C Giant Octopus @John Matson
167 C Giant Spider @Randy Gallegos
17 R Gift of Estates @Kaja Foglio
131 C Goblin Bully @Pete Venters
168 C Gorilla Warrior @John Matson
95 U Gravedigger @Scott M. Fischer
169 C Grizzly Bears @Zina Saunders
96 C Hand of Death @John Coulthart
96† C Hand of Death @John Coulthart
18 R Harsh Justice @John Coulthart
132 C Highland Giant @Ron Spencer
133 C Hill Giant @Randy Gallegos
57 C Horned Turtle @Adrian Smith
97 C Howling Fury @Mike Dringenberg
134 U Hulking Cyclops @Paolo Parente
134s U Hulking Cyclops @Lin Yan
135 C Hulking Goblin @Pete Venters
170 R Hurricane @Andrew Robinson
58 U Ingenious Thief @Dan Frazier
200 L Island @Eric Peterson
200s L Island @库雪明
201 L Island @Eric Peterson
201s L Island @库雪明
171 C Jungle Lion @Janine Johnston
19 C Keen-Eyed Archers @Alan Rabinowitz
98 R King's Assassin @Zina Saunders
20 C Knight Errant @Dan Frazier
136 R Last Chance @Hannibal King
137 C Lava Axe @Adrian Smith
138 U Lava Flow @Mike Dringenberg
139 C Lizard Warrior @Roger Raupp
59 U Man-o'-War @Una Fricker
99 R Mercenary Knight @Adrian Smith
60 C Merfolk of the Pearl Trident @DiTerlizzi
100 C Mind Knives @Rebecca Guay
101 C Mind Rot @Steve Luke
140 C Minotaur Warrior @Scott M. Fischer
172 C Mobilize @Rebecca Guay
173 C Monstrous Growth @Dan Frazier
173† C Monstrous Growth @Dan Frazier
174 U Moon Sprite @Terese Nielsen
208 L Mountain @Brian Durfee
208s L Mountain @康羽
209 L Mountain @Brian Durfee
209s L Mountain @康羽
141 U Mountain Goat @Una Fricker
102 C Muck Rats @Colin MacNeil
61 U Mystic Denial @Hannibal King
175 R Natural Order @Alan Rabinowitz
176 U Natural Spring @Janine Johnston
177 R Nature's Cloak @Rebecca Guay
178 C Nature's Lore @Terese Nielsen
103 U Nature's Ruin @Mike Dringenberg
179 U Needle Storm @Charles Gillespie
104 U Noxious Toad @Adrian Smith
62 C Omen @Eric Peterson
63 C Owl Familiar @Janine Johnston
180 C Panther Warriors @Eric Peterson
21 C Path of Peace @Pete Venters
64 U Personal Tutor @D. Alexander Gregory
65 R Phantom Warrior @Dan Frazier
142 R Pillaging Horde @Kev Walker
196 L Plains @Douglas Shuler
196s L Plains @Jack Wei
197 L Plains @Douglas Shuler
197s L Plains @Jack Wei
181 U Plant Elemental @Ted Naifeh
182 R Primeval Force @Randy Gallegos
66 R Prosperity @Phil Foglio
143 R Pyroclasm @John Matson
105 C Python @Alan Rabinowitz
144 C Raging Cougar @Terese Nielsen
145 C Raging Goblin @Pete Venters
145† C Raging Goblin @Pete Venters
146 C Raging Minotaur @Scott M. Fischer
147 U Rain of Salt @Charles Gillespie
106 U Rain of Tears @Eric Peterson
107 C Raise Dead @Charles Gillespie
183 C Redwood Treefolk @Steve Luke
22 C Regal Unicorn @Zina Saunders
23 U Renewing Dawn @John Avon
184 C Rowan Treefolk @Gerry Grace
24 C Sacred Knight @Donato Giancola
25 C Sacred Nectar @Janine Johnston
148 C Scorching Spear @Mike Raabe
149 U Scorching Winds @D. Alexander Gregory
26 U Seasoned Marshal @Zina Saunders
108 R Serpent Assassin @Roger Raupp
109 C Serpent Warrior @Roger Raupp
110 C Skeletal Crocodile @Mike Dringenberg
111 C Skeletal Snake @John Matson
67 C Snapping Drake @Christopher Rush
68 C Sorcerous Sight @Kaja Foglio
112 C Soul Shred @Alan Rabinowitz
185 C Spined Wurm @Colin MacNeil
27 R Spiritual Guardian @Terese Nielsen
150 C Spitting Earth @Hannibal King
28 C Spotted Griffin @William Simpson
186 C Stalking Tiger @Colin MacNeil
29 U Starlight @John Avon
30 U Starlit Angel @Rebecca Guay
31 C Steadfastness @Kev Walker
32 R Stern Marshal @D. Alexander Gregory
151 C Stone Rain @John Matson
69 C Storm Crow @Una Fricker
152 R Thundermare @Bob Eggleton
153 R Volcanic Dragon @Tom Wänerstrand
154 C Volcanic Hammer @Christopher Rush
155 U Wall of Granite @Kev Walker
156 R Winds of Change @Adam Rex
157 R Alluring Scent @Ted Naifeh
158 U Anaconda @Andrew Robinson
158† U Anaconda @Andrew Robinson
159 U Bee Sting @Phil Foglio
160 U Bull Hippo @Roger Raupp
160d U Bull Hippo @Roger Raupp
161 R Charging Rhino @Una Fricker
162 U Deep Wood @Paolo Parente
163 C Elite Cat Warrior @Eric Peterson
163† C Elite Cat Warrior @Eric Peterson
164 C Elven Cache @Rebecca Guay
164s C Elven Cache @张艺娜
165 C Elvish Ranger @DiTerlizzi
166 C Fruition @Steve Luke
166s C Fruition @王玉群
167 C Giant Spider @Randy Gallegos
168 C Gorilla Warrior @John Matson
169 C Grizzly Bears @Zina Saunders
170 R Hurricane @Andrew Robinson
171 C Jungle Lion @Janine Johnston
172 C Mobilize @Rebecca Guay
173 C Monstrous Growth @Dan Frazier
173† C Monstrous Growth @Dan Frazier
174 U Moon Sprite @Terese Nielsen
175 R Natural Order @Alan Rabinowitz
176 U Natural Spring @Janine Johnston
177 R Nature's Cloak @Rebecca Guay
178 C Nature's Lore @Terese Nielsen
179 U Needle Storm @Charles Gillespie
180 C Panther Warriors @Eric Peterson
181 U Plant Elemental @Ted Naifeh
182 R Primeval Force @Randy Gallegos
183 C Redwood Treefolk @Steve Luke
184 C Rowan Treefolk @Gerry Grace
185 C Spined Wurm @Colin MacNeil
186 C Stalking Tiger @Colin MacNeil
187 R Summer Bloom @Kaja Foglio
188 R Sylvan Tutor @Kaja Foglio
189 R Thundering Wurm @Paolo Parente
190 R Treetop Defense @Zina Saunders
191 U Untamed Wilds @Romas Kukalis
192 U Whiptail Wurm @Una Fricker
193 C Willow Dryad @D. Alexander Gregory
194 U Winter's Grasp @Paolo Parente
195 R Wood Elves @Rebecca Guay
196 L Plains @Douglas Shuler
196s L Plains @Jack Wei
197 L Plains @Douglas Shuler
197s L Plains @Jack Wei
198 L Plains @Douglas Shuler
198s L Plains @Jack Wei
199 L Plains @Douglas Shuler
199s L Plains @Jack Wei
200 L Island @Eric Peterson
200s L Island @库雪明
201 L Island @Eric Peterson
201s L Island @库雪明
202 L Island @Eric Peterson
202s L Island @库雪明
203 L Island @Eric Peterson
203s L Island @库雪明
204 L Swamp @Romas Kukalis
205 L Swamp @Romas Kukalis
206 L Swamp @Romas Kukalis
207 L Swamp @Romas Kukalis
188 R Sylvan Tutor @Kaja Foglio
70 C Symbol of Unsummoning @Adam Rex
71 R Taunt @Phil Foglio
33 R Temporary Truce @Mike Raabe
72 U Theft of Dreams @Adam Rex
73 R Thing from the Deep @Paolo Parente
189 R Thundering Wurm @Paolo Parente
152 R Thundermare @Bob Eggleton
74 C Tidal Surge @Douglas Shuler
75 C Time Ebb @Alan Rabinowitz
76 C Touch of Brilliance @John Coulthart
190 R Treetop Defense @Zina Saunders
113 C Undying Beast @Steve Luke
191 U Untamed Wilds @Romas Kukalis
34 U Valorous Charge @Douglas Shuler
114 U Vampiric Feast @D. Alexander Gregory
115 C Vampiric Touch @Zina Saunders
35 U Venerable Monk @D. Alexander Gregory
36 U Vengeance @Andrew Robinson
116 U Virtue's Ruin @Mike Dringenberg
153 R Volcanic Dragon @Tom Wänerstrand
154 C Volcanic Hammer @Christopher Rush
155 U Wall of Granite @Kev Walker
37 U Wall of Swords @Douglas Shuler
38 C Warrior's Charge @Ted Naifeh
38† C Warrior's Charge @Ted Naifeh
192 U Whiptail Wurm @Una Fricker
117 R Wicked Pact @Adam Rex
193 C Willow Dryad @D. Alexander Gregory
77 C Wind Drake @Zina Saunders
156 R Winds of Change @Adam Rex
194 U Winter's Grasp @Paolo Parente
78 U Withering Gaze @Scott M. Fischer
195 R Wood Elves @Rebecca Guay
39 R Wrath of God @Mike Raabe
208 L Mountain @Brian Durfee
208s L Mountain @康羽
209 L Mountain @Brian Durfee
209s L Mountain @康羽
210 L Mountain @Brian Durfee
210s L Mountain @康羽
211 L Mountain @Brian Durfee
211s L Mountain @康羽
212 L Forest @John Avon
212s L Forest @李铁
213 L Forest @John Avon
213s L Forest @李铁
214 L Forest @John Avon
214s L Forest @李铁
215 L Forest @John Avon
215s L Forest @李铁

View File

@@ -609,6 +609,7 @@ ScryfallCode=SLD
638 R Fury Sliver @Paolo Parente
640 R Homing Sliver @Trevor Hairsine
641 R Magma Sliver @Wayne England
642 R Sedge Sliver @Richard Kane Ferguson
643 R Spiteful Sliver @Johann Bodin
644 R Striking Sliver @Maciej Kuciara
645 R Thorncaster Sliver @Trevor Claxton

View File

@@ -0,0 +1,9 @@
[metadata]
Code=PW23
Date=2023-01-01
Name=Wizards Play Network 2023
Type=Promo
ScryfallCode=PW23
[cards]
1 R Norn's Annex @James Paick

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