Some cleaning brought with smaller fixes (#6029)

* Fix Teferi's Imp
This commit is contained in:
tool4ever
2024-09-02 10:47:50 +02:00
committed by GitHub
parent 40bc5aa021
commit 229b1562be
18 changed files with 152 additions and 172 deletions

View File

@@ -794,6 +794,7 @@ public class AiAttackController {
if (bAssault) {
return prefDefender;
}
// 2. attack planeswalkers
List<Card> pwDefending = c.getDefendingPlaneswalkers();
if (!pwDefending.isEmpty()) {
@@ -801,7 +802,7 @@ public class AiAttackController {
return pwNearUlti != null ? pwNearUlti : ComputerUtilCard.getBestPlaneswalkerAI(pwDefending);
}
// Get the preferred battle (prefer own battles, then ally battles)
// 3. Get the preferred battle (prefer own battles, then ally battles)
final CardCollection defBattles = c.getDefendingBattles();
List<Card> ownBattleDefending = CardLists.filter(defBattles, CardPredicates.isController(ai));
List<Card> allyBattleDefending = CardLists.filter(defBattles, CardPredicates.isControlledByAnyOf(ai.getAllies()));
@@ -1168,10 +1169,8 @@ public class AiAttackController {
attritionalAttackers.remove(attritionalAttackers.size() - 1);
}
}
attackRounds += 1;
if (humanLife <= 0) {
doAttritionalAttack = true;
}
attackRounds++;
doAttritionalAttack = humanLife <= 0;
}
// *********************
// end attritional attack calculation
@@ -1332,74 +1331,48 @@ public class AiAttackController {
return aiAggression;
}
/**
* <p>
* shouldAttack.
* </p>
*
* @param attacker
* a {@link forge.game.card.Card} object.
* @param defenders
* a object.
* @param combat
* a {@link forge.game.combat.Combat} object.
* @return a boolean.
*/
public final boolean shouldAttack(final Card attacker, final List<Card> defenders, final Combat combat, final GameEntity defender) {
private class SpellAbilityFactors {
Card attacker = null;
boolean canBeKilled = false; // indicates if the attacker can be killed
boolean canBeKilledByOne = false; // indicates if the attacker can be killed by a single blocker
boolean canKillAll = true; // indicates if the attacker can kill all single blockers
boolean canKillAllDangerous = true; // indicates if the attacker can kill all single blockers with wither or infect
boolean isWorthLessThanAllKillers = true;
boolean canBeBlocked = false;
boolean hasAttackEffect = false;
boolean hasCombatEffect = false;
boolean dangerousBlockersPresent = false;
boolean canTrampleOverDefenders = false;
int numberOfPossibleBlockers = 0;
int defPower = 0;
// Is it a creature that has a more valuable ability with a tap cost than what it can do by attacking?
if (attacker.hasSVar("NonCombatPriority") && !attacker.hasKeyword(Keyword.VIGILANCE)) {
// For each level of priority, enemy has to have life as much as the creature's power
// so a priority of 4 means the creature will not attack unless it can defeat that player in 4 successful attacks.
// the lower the priroity, the less willing the AI is to use the creature for attacking.
// TODO Somehow subtract expected damage of other attacking creatures from enemy life total (how? other attackers not yet declared? Can the AI guesstimate which of their creatures will not get blocked?)
if (attacker.getCurrentPower() * Integer.parseInt(attacker.getSVar("NonCombatPriority")) < ai.getOpponentsSmallestLifeTotal()) {
// Check if the card actually has an ability the AI can and wants to play, if not, attacking is fine!
for (SpellAbility sa : attacker.getSpellAbilities()) {
// Do not attack if we can afford using the ability.
if (sa.isActivatedAbility()) {
if (ComputerUtilCost.canPayCost(sa, ai, false)) {
return false;
}
// TODO Eventually The Ai will need to learn to predict if they have any use for the ability before next untap or not.
// TODO abilities that tap enemy creatures should probably only be saved if the enemy has nonzero creatures? Haste can be a threat though...
}
}
}
SpellAbilityFactors(Card c) {
attacker = c;
}
if (!isEffectiveAttacker(ai, attacker, combat, defender)) {
return false;
private boolean canBeBlocked() {
return numberOfPossibleBlockers > 2
|| (numberOfPossibleBlockers >= 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1, defendingOpponent))
|| (numberOfPossibleBlockers == 2 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 2, defendingOpponent));
}
boolean hasAttackEffect = attacker.getSVar("HasAttackEffect").equals("TRUE") || attacker.hasKeyword(Keyword.ANNIHILATOR);
private void calculate(final List<Card> defenders, final Combat combat) {
hasAttackEffect = attacker.getSVar("HasAttackEffect").equals("TRUE") || attacker.hasKeyword(Keyword.ANNIHILATOR);
// is there a gain in attacking even when the blocker is not killed (Lifelink, Wither,...)
boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE") || "Blocked".equals(attacker.getSVar("HasAttackEffect"));
if (!hasCombatEffect) {
if (attacker.isWitherDamage() || attacker.hasKeyword(Keyword.LIFELINK) || attacker.hasKeyword(Keyword.AFFLICT)) {
hasCombatEffect = true;
}
}
hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE") || "Blocked".equals(attacker.getSVar("HasAttackEffect"))
|| attacker.isWitherDamage() || attacker.hasKeyword(Keyword.LIFELINK) || attacker.hasKeyword(Keyword.AFFLICT);
// contains only the defender's blockers that can actually block the attacker
CardCollection validBlockers = CardLists.filter(defenders, defender1 -> CombatUtil.canBlock(attacker, defender1));
boolean canTrampleOverDefenders = attacker.hasKeyword(Keyword.TRAMPLE) && attacker.getNetCombatDamage() > Aggregates.sum(validBlockers, Card::getNetToughness);
canTrampleOverDefenders = attacker.hasKeyword(Keyword.TRAMPLE) && attacker.getNetCombatDamage() > Aggregates.sum(validBlockers, Card::getNetToughness);
// used to check that CanKillAllDangerous check makes sense in context where creatures with dangerous abilities are present
boolean dangerousBlockersPresent = Iterables.any(validBlockers, Predicates.or(
dangerousBlockersPresent = Iterables.any(validBlockers, Predicates.or(
CardPredicates.hasKeyword(Keyword.WITHER), CardPredicates.hasKeyword(Keyword.INFECT),
CardPredicates.hasKeyword(Keyword.LIFELINK)));
// total power of the defending creatures, used in predicting whether a gang block can kill the attacker
int defPower = CardLists.getTotalPower(validBlockers, true, false);
defPower = CardLists.getTotalPower(validBlockers, true, false);
// look at the attacker in relation to the blockers to establish a
// number of factors about the attacking context that will be relevant
@@ -1422,11 +1395,9 @@ public class AiAttackController {
// not record that it can't kill everything
if (canKillAllDangerous && !ComputerUtilCombat.canDestroyBlocker(ai, blocker, attacker, combat, false)) {
canKillAll = false;
if (blocker.getSVar("HasCombatEffect").equals("TRUE") || blocker.getSVar("HasBlockEffect").equals("TRUE")) {
canKillAllDangerous = false;
} else {
if (blocker.hasKeyword(Keyword.WITHER) || blocker.hasKeyword(Keyword.INFECT)
|| blocker.hasKeyword(Keyword.LIFELINK)) {
if (blocker.getSVar("HasCombatEffect").equals("TRUE") || blocker.getSVar("HasBlockEffect").equals("TRUE")
|| blocker.hasKeyword(Keyword.WITHER) || blocker.hasKeyword(Keyword.INFECT) || blocker.hasKeyword(Keyword.LIFELINK)) {
canKillAllDangerous = false;
// there is a creature that can survive an attack from this creature
// and combat will have negative effects
@@ -1438,7 +1409,7 @@ public class AiAttackController {
&& ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK);
boolean attackerWillDie = defPower >= attacker.getNetToughness();
boolean uselessAttack = !hasCombatEffect && !hasAttackEffect;
boolean noContributionToAttack = this.attackers.size() <= defenders.size() || attacker.getNetPower() <= 0;
boolean noContributionToAttack = attackers.size() <= defenders.size() || attacker.getNetPower() <= 0;
// We are attacking too recklessly if we can't kill a single blocker and:
// - our creature will die for sure (chump attack)
@@ -1451,8 +1422,8 @@ public class AiAttackController {
}
}
}
}
// performance-wise it doesn't seem worth it to check attackVigilance() instead (only includes a single niche card)
if (!attacker.hasKeyword(Keyword.VIGILANCE) && ComputerUtilCard.canBeKilledByRoyalAssassin(ai, attacker)) {
canKillAllDangerous = false;
canBeKilled = true;
@@ -1463,28 +1434,70 @@ public class AiAttackController {
canKillAllDangerous = false;
canBeKilled = true;
}
}
}
/**
* <p>
* shouldAttack.
* </p>
*
* @param attacker
* a {@link forge.game.card.Card} object.
* @param defenders
* a object.
* @param combat
* a {@link forge.game.combat.Combat} object.
* @return a boolean.
*/
public final boolean shouldAttack(final Card attacker, final List<Card> defenders, final Combat combat, final GameEntity defender) {
// Is it a creature that has a more valuable ability with a tap cost than what it can do by attacking?
if (attacker.hasSVar("NonCombatPriority") && !attacker.hasKeyword(Keyword.VIGILANCE)) {
// For each level of priority, enemy has to have life as much as the creature's power
// so a priority of 4 means the creature will not attack unless it can defeat that player in 4 successful attacks.
// the lower the priroity, the less willing the AI is to use the creature for attacking.
// TODO Somehow subtract expected damage of other attacking creatures from enemy life total (how? other attackers not yet declared? Can the AI guesstimate which of their creatures will not get blocked?)
if (attacker.getCurrentPower() * Integer.parseInt(attacker.getSVar("NonCombatPriority")) < ai.getOpponentsSmallestLifeTotal()) {
// Check if the card actually has an ability the AI can and wants to play, if not, attacking is fine!
for (SpellAbility sa : attacker.getSpellAbilities()) {
// Do not attack if we can afford using the ability.
if (sa.isActivatedAbility() && sa.getPayCosts().hasTapCost()) {
if (ComputerUtilCost.canPayCost(sa, ai, false)) {
return false;
}
// TODO Eventually The Ai will need to learn to predict if they have any use for the ability before next untap or not.
// TODO abilities that tap enemy creatures should probably only be saved if the enemy has nonzero creatures? Haste can be a threat though...
}
}
}
}
if (!isEffectiveAttacker(ai, attacker, combat, defender)) {
return false;
}
SpellAbilityFactors saf = new SpellAbilityFactors(attacker);
if (aiAggression != 5) {
saf.calculate(defenders, combat);
}
// if the creature cannot block and can kill all opponents they might as
// well attack, they do nothing staying back
if (canKillAll && isWorthLessThanAllKillers && !CombatUtil.canBlock(attacker)) {
if (saf.canKillAll && saf.isWorthLessThanAllKillers && !CombatUtil.canBlock(attacker)) {
if (LOG_AI_ATTACKS)
System.out.println(attacker.getName() + " = attacking because they can't block, expecting to kill or damage player");
return true;
} else if (!canBeKilled && !dangerousBlockersPresent && canTrampleOverDefenders) {
}
if (!saf.canBeKilled && !saf.dangerousBlockersPresent && saf.canTrampleOverDefenders) {
if (LOG_AI_ATTACKS)
System.out.println(attacker.getName() + " = expecting to survive and get some Trample damage through");
return true;
}
if (numberOfPossibleBlockers > 2
|| (numberOfPossibleBlockers >= 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1, defendingOpponent))
|| (numberOfPossibleBlockers == 2 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 2, defendingOpponent))) {
canBeBlocked = true;
}
// decide if the creature should attack based on the prevailing strategy choice in aiAggression
switch (aiAggression) {
case 6: // Exalted: expecting to at least kill a creature of equal value or not be blocked
if ((canKillAll && isWorthLessThanAllKillers) || !canBeBlocked) {
if ((saf.canKillAll && saf.isWorthLessThanAllKillers) || !saf.canBeBlocked()) {
if (LOG_AI_ATTACKS)
System.out.println(attacker.getName() + " = attacking expecting to kill creature, or is unblockable");
return true;
@@ -1495,32 +1508,32 @@ public class AiAttackController {
System.out.println(attacker.getName() + " = all out attacking");
return true;
case 4: // expecting to at least trade with something, or can attack "for free", expecting no counterattack
if (canKillAll || (dangerousBlockersPresent && canKillAllDangerous && !canBeKilledByOne) || !canBeBlocked
|| (defPower == 0 && !ComputerUtilCombat.lifeInDanger(ai, combat))) {
if (saf.canKillAll || (saf.dangerousBlockersPresent && saf.canKillAllDangerous && !saf.canBeKilledByOne) || !saf.canBeBlocked()
|| saf.defPower == 0) {
if (LOG_AI_ATTACKS)
System.out.println(attacker.getName() + " = attacking expecting to at least trade with something");
return true;
}
break;
case 3: // expecting to at least kill a creature of equal value or not be blocked
if ((canKillAll && isWorthLessThanAllKillers)
|| (((dangerousBlockersPresent && canKillAllDangerous) || hasAttackEffect || hasCombatEffect) && !canBeKilledByOne)
|| !canBeBlocked) {
if ((saf.canKillAll && saf.isWorthLessThanAllKillers)
|| (((saf.dangerousBlockersPresent && saf.canKillAllDangerous) || saf.hasAttackEffect || saf.hasCombatEffect) && !saf.canBeKilledByOne)
|| !saf.canBeBlocked()) {
if (LOG_AI_ATTACKS)
System.out.println(attacker.getName() + " = attacking expecting to kill creature or cause damage, or is unblockable");
return true;
}
break;
case 2: // attack expecting to attract a group block or destroying a single blocker and surviving
if (!canBeBlocked || ((canKillAll || hasAttackEffect || hasCombatEffect) && !canBeKilledByOne &&
((dangerousBlockersPresent && canKillAllDangerous) || !canBeKilled))) {
if (!saf.canBeBlocked() || ((saf.canKillAll || saf.hasAttackEffect || saf.hasCombatEffect) && !saf.canBeKilledByOne &&
((saf.dangerousBlockersPresent && saf.canKillAllDangerous) || !saf.canBeKilled))) {
if (LOG_AI_ATTACKS)
System.out.println(attacker.getName() + " = attacking expecting to survive or attract group block");
return true;
}
break;
case 1: // unblockable creatures only
if (!canBeBlocked || (numberOfPossibleBlockers == 1 && canKillAll && !canBeKilledByOne)) {
if (!saf.canBeBlocked() || (saf.numberOfPossibleBlockers == 1 && saf.canKillAll && !saf.canBeKilledByOne)) {
if (LOG_AI_ATTACKS)
System.out.println(attacker.getName() + " = attacking expecting not to be blocked");
return true;

View File

@@ -724,7 +724,6 @@ public class ComputerUtilCombat {
return totalDamageOfBlockers(attacker, blockers) >= getDamageToKill(attacker, false);
}
// Will this trigger trigger?
/**
* <p>
* combatTriggerWillTrigger.

View File

@@ -4097,7 +4097,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
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
@@ -5702,7 +5701,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
// Just phased in, time to run the phased in trigger
getGame().getTriggerHandler().registerActiveTrigger(this, false);
getGame().getTriggerHandler().runTrigger(TriggerType.PhaseIn, runParams, false);
getGame().getTriggerHandler().runTrigger(TriggerType.PhaseIn, runParams, true);
}
game.updateLastStateForCard(this);

View File

@@ -414,7 +414,7 @@ public class CardLists {
public static int getTotalPower(Iterable<Card> cardList, boolean ignoreNegativePower, boolean crew) {
int total = 0;
for (final Card crd : cardList) {
if (crew && StaticAbilityCrewValue.hasAnyCrewValue(crd)) {
if (crew) {
if (StaticAbilityCrewValue.crewsWithToughness(crd)) {
total += ignoreNegativePower ? Math.max(0, crd.getNetToughness()) : crd.getNetToughness();
} else {

View File

@@ -1628,8 +1628,7 @@ public class CardProperty {
}
}
if (property.startsWith("attacking ")) { // generic "attacking [DefinedGameEntity]"
FCollection<GameEntity> defined = AbilityUtils.getDefinedEntities(source, property.split(" ")[1],
spellAbility);
FCollection<GameEntity> defined = AbilityUtils.getDefinedEntities(source, property.split(" ")[1], spellAbility);
final GameEntity defender = combat.getDefenderByAttacker(card);
if (!defined.contains(defender)) {
return false;

View File

@@ -100,6 +100,7 @@ public class CardZoneTable extends ForwardingTable<ZoneType, ZoneType, CardColle
// will be handled by original "cause" instead
return;
}
if (!isEmpty()) {
// this should still refresh for empty battlefield
if (lastStateBattlefield != CardCollection.EMPTY) {
game.getTriggerHandler().resetActiveTriggers(false);
@@ -108,7 +109,7 @@ public class CardZoneTable extends ForwardingTable<ZoneType, ZoneType, CardColle
game.getTriggerHandler().registerActiveLTBTrigger(lki);
}
}
if (!isEmpty()) {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Cards, new CardZoneTable(this));
runParams.put(AbilityKey.Cause, cause);

View File

@@ -232,7 +232,7 @@ public class Untap extends Phase {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Map, untapMap);
game.getTriggerHandler().runTrigger(TriggerType.UntapAll, runParams, false);
} // end doUntap
}
private static boolean optionalUntap(final Card c) {
boolean untap = true;
@@ -301,6 +301,10 @@ public class Untap extends Phase {
runParams.put(AbilityKey.Cards, phasedOut);
turn.getGame().getTriggerHandler().runTrigger(TriggerType.PhaseOutAll, runParams, false);
}
if (!toPhase.isEmpty()) {
// collect now before some zone change during Untap resets triggers
turn.getGame().getTriggerHandler().collectTriggerForWaiting();
}
}
private static void doDayTime(final Player previous) {

View File

@@ -8,25 +8,6 @@ public class StaticAbilityCrewValue {
static String MODE = "CrewValue";
public static boolean hasAnyCrewValue(final Card card) {
final Game game = card.getGame();
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final StaticAbility stAb : ca.getStaticAbilities()) {
if (!stAb.checkConditions(MODE)) {
continue;
}
if (hasAnyCrewValue(stAb, card)) {
return true;
}
}
}
return false;
}
public static boolean hasAnyCrewValue(final StaticAbility stAb, final Card card) {
return stAb.matchesValidParam("ValidCard", card);
}
public static boolean crewsWithToughness(final Card card) {
final Game game = card.getGame();
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {

View File

@@ -3,6 +3,6 @@ ManaCost:1 G
Types:Snow Creature Mammoth
PT:4/5
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | Static$ True | TriggerDescription$ CARDNAME enters with a token copy of Pacifism attached to it.
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ pacifism | TokenOwner$ You | AttachedTo$ Self
SVar:TrigToken:DB$ CopyPermanent | DefinedName$ Pacifism | AttachedTo$ Self
DeckHas:Ability$Token
Oracle:Domesticated Mammoth enters with a token copy of Pacifism attached to it.

View File

@@ -3,7 +3,7 @@ ManaCost:2 R R
Types:Legendary Creature Human Knight
PT:4/4
K:Haste
T:Mode$ ChangesZoneAll | Origin$ Battlefield | Destination$ Graveyard | ValidCards$ Creature.attackingLKI+Legendary+Other+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigUntapAll | ActivationLimit$ 1 | TriggerDescription$ Whenever one or more other attacking legendary creatures you control die, untap all creatures you control. After this phase, there is an additional combat phase. This ability triggers only once each turn.
T:Mode$ ChangesZoneAll | Origin$ Battlefield | Destination$ Graveyard | ValidCards$ Creature.attacking+Legendary+Other+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigUntapAll | ActivationLimit$ 1 | TriggerDescription$ Whenever one or more other attacking legendary creatures you control die, untap all creatures you control. After this phase, there is an additional combat phase. This ability triggers only once each turn.
SVar:TrigUntapAll:DB$ UntapAll | ValidCards$ Creature.YouCtrl | SubAbility$ DBAddCombat
SVar:DBAddCombat:DB$ AddPhase | ExtraPhase$ Combat | AfterPhase$ EndCombat
DeckHints:Type$Legendary

View File

@@ -6,6 +6,6 @@ T:Mode$ ChangesZone | ValidCard$ Creature.Other+YouCtrl | Origin$ Battlefield |
SVar:TrigBranch:DB$ Branch | BranchConditionSVar$ X | BranchConditionSVarCompare$ GE1 | TrueSubAbility$ DBDraw | FalseSubAbility$ DBDamage
SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 1
SVar:DBDamage:DB$ DealDamage | Defined$ Opponent | NumDmg$ 1
SVar:X:TriggeredCard$Valid Creature.attackingLKI
SVar:X:TriggeredCard$Valid Creature.attacking
DeckHas:Ability$Sacrifice
Oracle:Whenever another creature you control dies, draw a card if it was attacking. Otherwise, Garna, Bloodfist of Keld deals 1 damage to each opponent.

View File

@@ -2,5 +2,5 @@ Name:Generated Horizons
ManaCost:2 G G
Types:Enchantment
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ At the beginning of your upkeep, you create a Forest land token.
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_l_forest | TokenOwner$ You
SVar:TrigToken:DB$ CopyPermanent | DefinedName$ Forest
Oracle:At the beginning of your upkeep, create a Forest land token.

View File

@@ -2,9 +2,9 @@ Name:Rootcast Apprenticeship
ManaCost:3 G
Types:Sorcery
A:SP$ Charm | Choices$ DBPutCounter,DBCopy,DBToken,DBSacrifice | CharmNum$ 3 | CanRepeatModes$ True
SVar:DBPutCounter:DB$ PutCounter | ValidTgts$ Creature | CounterType$ P1P1 | CounterNum$ 3 | SpellDescription$ Put three +1/+1 counters on target creature.
SVar:DBPutCounter:DB$ PutCounter | ValidTgts$ Creature | CounterType$ P1P1 | CounterNum$ 2 | SpellDescription$ Put two +1/+1 counters on target creature.
SVar:DBCopy:DB$ CopyPermanent | ValidTgts$ Permanent.token+YouCtrl | TgtPrompt$ Select target token you control | SpellDescription$ Create a token that's a copy of target token you control.
SVar:DBToken:DB$ Token | ValidTgts$ Player | TokenAmount$ 1 | TokenScript$ g_1_1_squirrel | StackDescription$ SpellDescription | SpellDescription$ Target player creates a 1/1 green Squirrel creature token.
SVar:DBSacrifice:DB$ Sacrifice | ValidTgts$ Opponent | SacValid$ Artifact.nonToken | SpellDescription$ Target opponent sacrifices a nontoken artifact. | SacMessage$ nontoken artifact
DeckHas:Ability$Counters|Token
Oracle:Choose three. You may choose the same mode more than once.\n• Put three +1/+1 counters on target creature.\n• Create a token that's a copy of target token you control.\n• Target player creates a 1/1 green Squirrel creature token.\n• Target opponent sacrifices a nontoken artifact.
Oracle:Choose three. You may choose the same mode more than once.\n• Put two +1/+1 counters on target creature.\n• Create a token that's a copy of target token you control.\n• Target player creates a 1/1 green Squirrel creature token.\n• Target opponent sacrifices a nontoken artifact.

View File

@@ -3,6 +3,6 @@ ManaCost:no cost
Types:Land
A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}.
A:AB$ Destroy | Cost$ T Sac<1/CARDNAME> | ValidTgts$ Land.nonBasic | TgtPrompt$ Select target land. | SubAbility$ DBToken | AILogic$ GhostQuarter | AITgts$ Land.nonBasic | SpellDescription$ Destroy target nonbasic land. That land's controller creates a Wastes land token. (It has {T}: Add {C}.)
SVar:DBToken:DB$ Token | TokenScript$ c_l_wastes | TokenOwner$ TargetedController
SVar:DBToken:DB$ CopyPermanent | DefinedName$ Wastes | Controller$ TargetedController
DeckHas:Ability$Mana.Colorless
Oracle:{T}: Add {C}.\n{T}, Sacrifice Waste Land: Destroy target nonbasic land. That land's controller creates a Wastes token. (It's a land with {T}: Add {C}.)

View File

@@ -1,4 +0,0 @@
Name:Forest Token
ManaCost:no cost
Types:Land Forest
Oracle:

View File

@@ -1,5 +0,0 @@
Name:Wastes Token
ManaCost:no cost
Types:Land
A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}.
Oracle:{T}: Add {C}.

View File

@@ -1,7 +0,0 @@
Name:Pacifism
ManaCost:1 W
Types:Enchantment Aura
K:Enchant creature
A:SP$ Attach | Cost$ 1 W | ValidTgts$ Creature | AILogic$ Curse
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddHiddenKeyword$ CARDNAME can't attack or block. | Description$ Enchanted creature can't attack or block.
Oracle:Enchant creature\nEnchanted creature can't attack or block.

View File

@@ -3,6 +3,6 @@ ManaCost:no cost
Types:Enchantment Aura Role
K:Enchant creature
A:SP$ Attach | Cost$ 0 | ValidTgts$ Creature | AILogic$ Curse
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | SetPower$ 1 | SetToughness$ 1 | Description$ Enchanted creature is 1/1
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | SetPower$ 1 | SetToughness$ 1 | Description$ Enchanted creature is 1/1.
SVar:SacMe:2
Oracle:Enchant Creature\nEnchanted creature is 1/1
Oracle:Enchant Creature\nEnchanted creature is 1/1.