mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 12:48:00 +00:00
@@ -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;
|
||||
|
||||
@@ -724,7 +724,6 @@ public class ComputerUtilCombat {
|
||||
return totalDamageOfBlockers(attacker, blockers) >= getDamageToKill(attacker, false);
|
||||
}
|
||||
|
||||
// Will this trigger trigger?
|
||||
/**
|
||||
* <p>
|
||||
* combatTriggerWillTrigger.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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}.)
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
Name:Forest Token
|
||||
ManaCost:no cost
|
||||
Types:Land Forest
|
||||
Oracle:
|
||||
@@ -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}.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user