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) { if (bAssault) {
return prefDefender; return prefDefender;
} }
// 2. attack planeswalkers // 2. attack planeswalkers
List<Card> pwDefending = c.getDefendingPlaneswalkers(); List<Card> pwDefending = c.getDefendingPlaneswalkers();
if (!pwDefending.isEmpty()) { if (!pwDefending.isEmpty()) {
@@ -801,7 +802,7 @@ public class AiAttackController {
return pwNearUlti != null ? pwNearUlti : ComputerUtilCard.getBestPlaneswalkerAI(pwDefending); 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(); final CardCollection defBattles = c.getDefendingBattles();
List<Card> ownBattleDefending = CardLists.filter(defBattles, CardPredicates.isController(ai)); List<Card> ownBattleDefending = CardLists.filter(defBattles, CardPredicates.isController(ai));
List<Card> allyBattleDefending = CardLists.filter(defBattles, CardPredicates.isControlledByAnyOf(ai.getAllies())); List<Card> allyBattleDefending = CardLists.filter(defBattles, CardPredicates.isControlledByAnyOf(ai.getAllies()));
@@ -1168,10 +1169,8 @@ public class AiAttackController {
attritionalAttackers.remove(attritionalAttackers.size() - 1); attritionalAttackers.remove(attritionalAttackers.size() - 1);
} }
} }
attackRounds += 1; attackRounds++;
if (humanLife <= 0) { doAttritionalAttack = humanLife <= 0;
doAttritionalAttack = true;
}
} }
// ********************* // *********************
// end attritional attack calculation // end attritional attack calculation
@@ -1332,74 +1331,48 @@ public class AiAttackController {
return aiAggression; return aiAggression;
} }
/** private class SpellAbilityFactors {
* <p> Card attacker = null;
* 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) {
boolean canBeKilled = false; // indicates if the attacker can be killed 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 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 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 canKillAllDangerous = true; // indicates if the attacker can kill all single blockers with wither or infect
boolean isWorthLessThanAllKillers = true; boolean isWorthLessThanAllKillers = true;
boolean canBeBlocked = false; boolean hasAttackEffect = false;
boolean hasCombatEffect = false;
boolean dangerousBlockersPresent = false;
boolean canTrampleOverDefenders = false;
int numberOfPossibleBlockers = 0; 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? SpellAbilityFactors(Card c) {
if (attacker.hasSVar("NonCombatPriority") && !attacker.hasKeyword(Keyword.VIGILANCE)) { attacker = c;
// 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...
}
}
}
} }
if (!isEffectiveAttacker(ai, attacker, combat, defender)) { private boolean canBeBlocked() {
return false; 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,...) // 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")); hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE") || "Blocked".equals(attacker.getSVar("HasAttackEffect"))
|| attacker.isWitherDamage() || attacker.hasKeyword(Keyword.LIFELINK) || attacker.hasKeyword(Keyword.AFFLICT);
if (!hasCombatEffect) {
if (attacker.isWitherDamage() || attacker.hasKeyword(Keyword.LIFELINK) || attacker.hasKeyword(Keyword.AFFLICT)) {
hasCombatEffect = true;
}
}
// contains only the defender's blockers that can actually block the attacker // contains only the defender's blockers that can actually block the attacker
CardCollection validBlockers = CardLists.filter(defenders, defender1 -> CombatUtil.canBlock(attacker, defender1)); 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 // 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.WITHER), CardPredicates.hasKeyword(Keyword.INFECT),
CardPredicates.hasKeyword(Keyword.LIFELINK))); CardPredicates.hasKeyword(Keyword.LIFELINK)));
// total power of the defending creatures, used in predicting whether a gang block can kill the attacker // 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 // look at the attacker in relation to the blockers to establish a
// number of factors about the attacking context that will be relevant // 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 // not record that it can't kill everything
if (canKillAllDangerous && !ComputerUtilCombat.canDestroyBlocker(ai, blocker, attacker, combat, false)) { if (canKillAllDangerous && !ComputerUtilCombat.canDestroyBlocker(ai, blocker, attacker, combat, false)) {
canKillAll = false; canKillAll = false;
if (blocker.getSVar("HasCombatEffect").equals("TRUE") || blocker.getSVar("HasBlockEffect").equals("TRUE")) {
canKillAllDangerous = false; if (blocker.getSVar("HasCombatEffect").equals("TRUE") || blocker.getSVar("HasBlockEffect").equals("TRUE")
} else { || blocker.hasKeyword(Keyword.WITHER) || blocker.hasKeyword(Keyword.INFECT) || blocker.hasKeyword(Keyword.LIFELINK)) {
if (blocker.hasKeyword(Keyword.WITHER) || blocker.hasKeyword(Keyword.INFECT)
|| blocker.hasKeyword(Keyword.LIFELINK)) {
canKillAllDangerous = false; canKillAllDangerous = false;
// there is a creature that can survive an attack from this creature // there is a creature that can survive an attack from this creature
// and combat will have negative effects // 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); && ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK);
boolean attackerWillDie = defPower >= attacker.getNetToughness(); boolean attackerWillDie = defPower >= attacker.getNetToughness();
boolean uselessAttack = !hasCombatEffect && !hasAttackEffect; 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: // We are attacking too recklessly if we can't kill a single blocker and:
// - our creature will die for sure (chump attack) // - 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)) { if (!attacker.hasKeyword(Keyword.VIGILANCE) && ComputerUtilCard.canBeKilledByRoyalAssassin(ai, attacker)) {
canKillAllDangerous = false; canKillAllDangerous = false;
canBeKilled = true; canBeKilled = true;
@@ -1463,28 +1434,70 @@ public class AiAttackController {
canKillAllDangerous = false; canKillAllDangerous = false;
canBeKilled = true; 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 // if the creature cannot block and can kill all opponents they might as
// well attack, they do nothing staying back // 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) if (LOG_AI_ATTACKS)
System.out.println(attacker.getName() + " = attacking because they can't block, expecting to kill or damage player"); System.out.println(attacker.getName() + " = attacking because they can't block, expecting to kill or damage player");
return true; return true;
} else if (!canBeKilled && !dangerousBlockersPresent && canTrampleOverDefenders) { }
if (!saf.canBeKilled && !saf.dangerousBlockersPresent && saf.canTrampleOverDefenders) {
if (LOG_AI_ATTACKS) if (LOG_AI_ATTACKS)
System.out.println(attacker.getName() + " = expecting to survive and get some Trample damage through"); System.out.println(attacker.getName() + " = expecting to survive and get some Trample damage through");
return true; 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 // decide if the creature should attack based on the prevailing strategy choice in aiAggression
switch (aiAggression) { switch (aiAggression) {
case 6: // Exalted: expecting to at least kill a creature of equal value or not be blocked 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) if (LOG_AI_ATTACKS)
System.out.println(attacker.getName() + " = attacking expecting to kill creature, or is unblockable"); System.out.println(attacker.getName() + " = attacking expecting to kill creature, or is unblockable");
return true; return true;
@@ -1495,32 +1508,32 @@ public class AiAttackController {
System.out.println(attacker.getName() + " = all out attacking"); System.out.println(attacker.getName() + " = all out attacking");
return true; return true;
case 4: // expecting to at least trade with something, or can attack "for free", expecting no counterattack case 4: // expecting to at least trade with something, or can attack "for free", expecting no counterattack
if (canKillAll || (dangerousBlockersPresent && canKillAllDangerous && !canBeKilledByOne) || !canBeBlocked if (saf.canKillAll || (saf.dangerousBlockersPresent && saf.canKillAllDangerous && !saf.canBeKilledByOne) || !saf.canBeBlocked()
|| (defPower == 0 && !ComputerUtilCombat.lifeInDanger(ai, combat))) { || saf.defPower == 0) {
if (LOG_AI_ATTACKS) if (LOG_AI_ATTACKS)
System.out.println(attacker.getName() + " = attacking expecting to at least trade with something"); System.out.println(attacker.getName() + " = attacking expecting to at least trade with something");
return true; return true;
} }
break; break;
case 3: // expecting to at least kill a creature of equal value or not be blocked case 3: // expecting to at least kill a creature of equal value or not be blocked
if ((canKillAll && isWorthLessThanAllKillers) if ((saf.canKillAll && saf.isWorthLessThanAllKillers)
|| (((dangerousBlockersPresent && canKillAllDangerous) || hasAttackEffect || hasCombatEffect) && !canBeKilledByOne) || (((saf.dangerousBlockersPresent && saf.canKillAllDangerous) || saf.hasAttackEffect || saf.hasCombatEffect) && !saf.canBeKilledByOne)
|| !canBeBlocked) { || !saf.canBeBlocked()) {
if (LOG_AI_ATTACKS) if (LOG_AI_ATTACKS)
System.out.println(attacker.getName() + " = attacking expecting to kill creature or cause damage, or is unblockable"); System.out.println(attacker.getName() + " = attacking expecting to kill creature or cause damage, or is unblockable");
return true; return true;
} }
break; break;
case 2: // attack expecting to attract a group block or destroying a single blocker and surviving case 2: // attack expecting to attract a group block or destroying a single blocker and surviving
if (!canBeBlocked || ((canKillAll || hasAttackEffect || hasCombatEffect) && !canBeKilledByOne && if (!saf.canBeBlocked() || ((saf.canKillAll || saf.hasAttackEffect || saf.hasCombatEffect) && !saf.canBeKilledByOne &&
((dangerousBlockersPresent && canKillAllDangerous) || !canBeKilled))) { ((saf.dangerousBlockersPresent && saf.canKillAllDangerous) || !saf.canBeKilled))) {
if (LOG_AI_ATTACKS) if (LOG_AI_ATTACKS)
System.out.println(attacker.getName() + " = attacking expecting to survive or attract group block"); System.out.println(attacker.getName() + " = attacking expecting to survive or attract group block");
return true; return true;
} }
break; break;
case 1: // unblockable creatures only 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) if (LOG_AI_ATTACKS)
System.out.println(attacker.getName() + " = attacking expecting not to be blocked"); System.out.println(attacker.getName() + " = attacking expecting not to be blocked");
return true; return true;

View File

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

View File

@@ -4097,7 +4097,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
return state.getType(); return state.getType();
} }
// TODO add changed type by card text
public Iterable<CardChangedType> getChangedCardTypes() { public Iterable<CardChangedType> getChangedCardTypes() {
// If there are no changed types, just return an empty immutable list, which actually // 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 // 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 // Just phased in, time to run the phased in trigger
getGame().getTriggerHandler().registerActiveTrigger(this, false); getGame().getTriggerHandler().registerActiveTrigger(this, false);
getGame().getTriggerHandler().runTrigger(TriggerType.PhaseIn, runParams, false); getGame().getTriggerHandler().runTrigger(TriggerType.PhaseIn, runParams, true);
} }
game.updateLastStateForCard(this); game.updateLastStateForCard(this);

View File

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

View File

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

View File

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

View File

@@ -232,7 +232,7 @@ public class Untap extends Phase {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap(); final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Map, untapMap); runParams.put(AbilityKey.Map, untapMap);
game.getTriggerHandler().runTrigger(TriggerType.UntapAll, runParams, false); game.getTriggerHandler().runTrigger(TriggerType.UntapAll, runParams, false);
} // end doUntap }
private static boolean optionalUntap(final Card c) { private static boolean optionalUntap(final Card c) {
boolean untap = true; boolean untap = true;
@@ -301,6 +301,10 @@ public class Untap extends Phase {
runParams.put(AbilityKey.Cards, phasedOut); runParams.put(AbilityKey.Cards, phasedOut);
turn.getGame().getTriggerHandler().runTrigger(TriggerType.PhaseOutAll, runParams, false); 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) { private static void doDayTime(final Player previous) {

View File

@@ -8,25 +8,6 @@ public class StaticAbilityCrewValue {
static String MODE = "CrewValue"; 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) { public static boolean crewsWithToughness(final Card card) {
final Game game = card.getGame(); final Game game = card.getGame();
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) { for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {

View File

@@ -3,6 +3,6 @@ ManaCost:1 G
Types:Snow Creature Mammoth Types:Snow Creature Mammoth
PT:4/5 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. 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 DeckHas:Ability$Token
Oracle:Domesticated Mammoth enters with a token copy of Pacifism attached to it. 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 Types:Legendary Creature Human Knight
PT:4/4 PT:4/4
K:Haste 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:TrigUntapAll:DB$ UntapAll | ValidCards$ Creature.YouCtrl | SubAbility$ DBAddCombat
SVar:DBAddCombat:DB$ AddPhase | ExtraPhase$ Combat | AfterPhase$ EndCombat SVar:DBAddCombat:DB$ AddPhase | ExtraPhase$ Combat | AfterPhase$ EndCombat
DeckHints:Type$Legendary 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:TrigBranch:DB$ Branch | BranchConditionSVar$ X | BranchConditionSVarCompare$ GE1 | TrueSubAbility$ DBDraw | FalseSubAbility$ DBDamage
SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 1 SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 1
SVar:DBDamage:DB$ DealDamage | Defined$ Opponent | NumDmg$ 1 SVar:DBDamage:DB$ DealDamage | Defined$ Opponent | NumDmg$ 1
SVar:X:TriggeredCard$Valid Creature.attackingLKI SVar:X:TriggeredCard$Valid Creature.attacking
DeckHas:Ability$Sacrifice 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. 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 ManaCost:2 G G
Types:Enchantment 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. 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. Oracle:At the beginning of your upkeep, create a Forest land token.

View File

@@ -2,9 +2,9 @@ Name:Rootcast Apprenticeship
ManaCost:3 G ManaCost:3 G
Types:Sorcery Types:Sorcery
A:SP$ Charm | Choices$ DBPutCounter,DBCopy,DBToken,DBSacrifice | CharmNum$ 3 | CanRepeatModes$ True 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: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: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 SVar:DBSacrifice:DB$ Sacrifice | ValidTgts$ Opponent | SacValid$ Artifact.nonToken | SpellDescription$ Target opponent sacrifices a nontoken artifact. | SacMessage$ nontoken artifact
DeckHas:Ability$Counters|Token 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 Types:Land
A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. 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}.) 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 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}.) 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 Types:Enchantment Aura Role
K:Enchant creature K:Enchant creature
A:SP$ Attach | Cost$ 0 | ValidTgts$ Creature | AILogic$ Curse 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 SVar:SacMe:2
Oracle:Enchant Creature\nEnchanted creature is 1/1 Oracle:Enchant Creature\nEnchanted creature is 1/1.