mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-11 16:26:22 +00:00
Merge branch 'Card-Forge:master' into master
This commit is contained in:
@@ -277,7 +277,7 @@ public class AiBlockController {
|
||||
|
||||
if (mode == TriggerType.DamageDone) {
|
||||
if (trigger.matchesValidParam("ValidSource", attacker)
|
||||
&& attacker.getNetCombatDamage() > 0
|
||||
&& !"False".equals(trigger.getParam("CombatDamage")) && attacker.getNetCombatDamage() > 0
|
||||
&& trigger.matchesValidParam("ValidTarget", combat.getDefenderByAttacker(attacker))) {
|
||||
value += 50;
|
||||
}
|
||||
|
||||
@@ -812,16 +812,11 @@ public class AiController {
|
||||
for (Card tgt : tc.getTargetCards()) {
|
||||
// TODO some older cards don't use the keyword, so check for trigger instead
|
||||
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(host.getController())) {
|
||||
int amount = 0;
|
||||
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
|
||||
if (wardCost.hasManaCost()) {
|
||||
amount = wardCost.getTotalMana().getCMC();
|
||||
if (amount > 0 && !ComputerUtilCost.canPayCost(sa, player, true)) {
|
||||
return AiPlayDecision.CantAfford;
|
||||
}
|
||||
xCost = wardCost.getTotalMana().getCMC() > 0;
|
||||
}
|
||||
SpellAbilityAi topAI = new SpellAbilityAi() {
|
||||
};
|
||||
SpellAbilityAi topAI = new SpellAbilityAi() {};
|
||||
if (!topAI.willPayCosts(player, sa, wardCost, host)) {
|
||||
return AiPlayDecision.CostNotAcceptable;
|
||||
}
|
||||
@@ -831,7 +826,7 @@ public class AiController {
|
||||
}
|
||||
|
||||
// check if some target raised cost
|
||||
if (oldCMC > -1) {
|
||||
if (!xCost && oldCMC > -1) {
|
||||
int finalCMC = CostAdjustment.adjust(sa.getPayCosts(), sa).getTotalMana().getCMC();
|
||||
if (finalCMC > oldCMC) {
|
||||
xCost = true;
|
||||
|
||||
@@ -673,7 +673,6 @@ public class ComputerUtilCombat {
|
||||
&& !blocker.hasKeyword(Keyword.INDESTRUCTIBLE)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // flanking
|
||||
|
||||
final int defBushidoMagnitude = blocker.getKeywordMagnitude(Keyword.BUSHIDO);
|
||||
@@ -859,7 +858,7 @@ public class ComputerUtilCombat {
|
||||
}
|
||||
} else if (mode == TriggerType.DamageDone) {
|
||||
willTrigger = true;
|
||||
if (trigger.hasParam("ValidSource")) {
|
||||
if (trigger.hasParam("ValidSource") && !"False".equals(trigger.getParam("CombatDamage"))) {
|
||||
if (!(trigger.matchesValidParam("ValidSource", defender)
|
||||
&& defender.getNetCombatDamage() > 0
|
||||
&& trigger.matchesValidParam("ValidTarget", attacker))) {
|
||||
@@ -2461,7 +2460,7 @@ public class ComputerUtilCombat {
|
||||
CardCollectionView trigCards = attacker.getController().getCardsIn(ZoneType.Battlefield);
|
||||
for (Card c : trigCards) {
|
||||
for (Trigger t : c.getTriggers()) {
|
||||
if (t.getMode() == TriggerType.DamageDone && "True".equals(t.getParam("CombatDamage")) && t.matchesValidParam("ValidSource", attacker)) {
|
||||
if (t.getMode() == TriggerType.DamageDone && !"False".equals(t.getParam("CombatDamage")) && t.matchesValidParam("ValidSource", attacker)) {
|
||||
SpellAbility ab = t.getOverridingAbility();
|
||||
if (ab.getApi() == ApiType.Poison && "TriggeredTarget".equals(ab.getParam("Defined"))) {
|
||||
pd += AbilityUtils.calculateAmount(attacker, ab.getParam("Num"), ab);
|
||||
|
||||
@@ -22,6 +22,7 @@ import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.Spell;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetChoices;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.TextUtil;
|
||||
@@ -556,12 +557,14 @@ public class ComputerUtilCost {
|
||||
}
|
||||
|
||||
// Ward - will be accounted for when rechecking a targeted ability
|
||||
if (!sa.isTrigger() && sa.usesTargeting() && (!sa.isSpell() || !cannotBeCountered)) {
|
||||
for (Card tgt : sa.getTargets().getTargetCards()) {
|
||||
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) {
|
||||
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
|
||||
if (wardCost.hasManaCost()) {
|
||||
extraManaNeeded += wardCost.getTotalMana().getCMC();
|
||||
if (!sa.isTrigger() && (!sa.isSpell() || !cannotBeCountered)) {
|
||||
for (TargetChoices tc : sa.getAllTargetChoices()) {
|
||||
for (Card tgt : tc.getTargetCards()) {
|
||||
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) {
|
||||
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
|
||||
if (wardCost.hasManaCost()) {
|
||||
extraManaNeeded += wardCost.getTotalMana().getCMC();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
// Don't use instant speed animate abilities before AI's COMBAT_BEGIN
|
||||
if (!ph.is(PhaseType.COMBAT_BEGIN) && ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa, ai)
|
||||
if (!ph.is(PhaseType.COMBAT_BEGIN) && ph.isPlayerTurn(ai) && !isSorcerySpeed(sa, ai)
|
||||
&& !sa.hasParam("ActivationPhases") && !"Permanent".equals(sa.getParam("Duration"))) {
|
||||
return false;
|
||||
}
|
||||
@@ -174,7 +174,7 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (!SpellAbilityAi.isSorcerySpeed(sa, aiPlayer) && !"Permanent".equals(sa.getParam("Duration"))) {
|
||||
if (!isSorcerySpeed(sa, aiPlayer) && !"Permanent".equals(sa.getParam("Duration"))) {
|
||||
if (sa.hasParam("Crew") && c.isCreature()) {
|
||||
// Do not try to crew a vehicle which is already a creature
|
||||
return false;
|
||||
|
||||
@@ -1069,7 +1069,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
if (!immediately && (!game.getPhaseHandler().getNextTurn().equals(ai)
|
||||
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa, ai)
|
||||
&& !sa.hasParam("PlayerTurn") && !isSorcerySpeed(sa, ai)
|
||||
&& !ComputerUtil.activateForCost(sa, ai)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -242,7 +242,7 @@ public class CloneAi extends SpellAbilityAi {
|
||||
// don't use instant speed clone abilities outside computers
|
||||
// Combat_Begin step
|
||||
if (!ph.is(PhaseType.COMBAT_BEGIN)
|
||||
&& ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa, ai)
|
||||
&& ph.isPlayerTurn(ai) && !isSorcerySpeed(sa, ai)
|
||||
&& !sa.hasParam("ActivationPhases") && sa.hasParam("Duration")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ public class CounterAi extends SpellAbilityAi {
|
||||
|
||||
if (toPay <= usableManaSources) {
|
||||
// If this is a reusable Resource, feel free to play it most of the time
|
||||
if (!SpellAbilityAi.playReusable(ai, sa)) {
|
||||
if (!playReusable(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -287,7 +287,7 @@ public class CounterAi extends SpellAbilityAi {
|
||||
if (toPay <= usableManaSources) {
|
||||
// If this is a reusable Resource, feel free to play it most
|
||||
// of the time
|
||||
if (!SpellAbilityAi.playReusable(ai,sa) || (MyRandom.getRandom().nextFloat() < .4)) {
|
||||
if (!playReusable(ai,sa) || (MyRandom.getRandom().nextFloat() < .4)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (!SpellAbilityAi.playReusable(ai, sa)) {
|
||||
if (!playReusable(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -437,7 +437,7 @@ public class CountersPutAi extends CountersAi {
|
||||
}
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
if (!ai.getGame().getStack().isEmpty() && !SpellAbilityAi.isSorcerySpeed(sa, ai)) {
|
||||
if (!ai.getGame().getStack().isEmpty() && !isSorcerySpeed(sa, ai)) {
|
||||
// only evaluates case where all tokens are placed on a single target
|
||||
if (sa.getMinTargets() < 2) {
|
||||
if (ComputerUtilCard.canPumpAgainstRemoval(ai, sa)) {
|
||||
@@ -550,7 +550,7 @@ public class CountersPutAi extends CountersAi {
|
||||
if (sa.isCurse()) {
|
||||
choice = chooseCursedTarget(list, type, amount, ai);
|
||||
} else {
|
||||
if (type.equals("P1P1") && !SpellAbilityAi.isSorcerySpeed(sa, ai)) {
|
||||
if (type.equals("P1P1") && !isSorcerySpeed(sa, ai)) {
|
||||
for (Card c : list) {
|
||||
if (ComputerUtilCard.shouldPumpCard(ai, sa, c, amount, amount, Lists.newArrayList())) {
|
||||
choice = c;
|
||||
@@ -623,7 +623,7 @@ public class CountersPutAi extends CountersAi {
|
||||
return false;
|
||||
}
|
||||
// Instant +1/+1
|
||||
if (type.equals("P1P1") && !SpellAbilityAi.isSorcerySpeed(sa, ai)) {
|
||||
if (type.equals("P1P1") && !isSorcerySpeed(sa, ai)) {
|
||||
if (!(ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN) && abCost.isReusuableResource())) {
|
||||
return false; // only if next turn and cost is reusable
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||
if (playReusable(ai, sa)) {
|
||||
return chance;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -46,11 +43,6 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention) {
|
||||
// TODO: once the "planeswalker redirection" rule is removed completely, just remove this code and
|
||||
// remove the "burn Planeswalkers" code in the called method.
|
||||
return shouldTgtP(comp, sa, d, noPrevention, false);
|
||||
}
|
||||
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention, final boolean noPlaneswalkerRedirection) {
|
||||
int restDamage = d;
|
||||
final Game game = comp.getGame();
|
||||
Player enemy = comp.getWeakestOpponent();
|
||||
@@ -89,13 +81,6 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
// burn Planeswalkers
|
||||
// TODO: Must be removed completely when the "planeswalker redirection" rule is removed.
|
||||
if (!noPlaneswalkerRedirection
|
||||
&& Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (avoidTargetP(comp, sa)) {
|
||||
return false;
|
||||
}
|
||||
@@ -131,7 +116,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
||||
// chance to burn player based on current hand size
|
||||
if (hand.size() > 2) {
|
||||
float value = 0;
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa, comp)) {
|
||||
if (isSorcerySpeed(sa, comp)) {
|
||||
//lower chance for sorcery as other spells may be cast in main2
|
||||
if (phase.isPlayerTurn(comp) && phase.is(PhaseType.MAIN2)) {
|
||||
value = 1.0f * restDamage / enemy.getLife();
|
||||
|
||||
@@ -1,28 +1,10 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.ai.AiController;
|
||||
import forge.ai.AiProps;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.*;
|
||||
import forge.card.MagicColor;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.Game;
|
||||
@@ -30,11 +12,7 @@ import forge.game.GameEntity;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostPartMana;
|
||||
import forge.game.keyword.Keyword;
|
||||
@@ -51,6 +29,12 @@ import forge.game.staticability.StaticAbilityMustTarget;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.MyRandom;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class DamageDealAi extends DamageAiBase {
|
||||
@Override
|
||||
@@ -630,7 +614,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
if (tgt.canTgtPlaneswalker()) {
|
||||
// We can damage planeswalkers with this, consider targeting.
|
||||
Card c = dealDamageChooseTgtPW(ai, sa, dmg, noPrevention, enemy, false);
|
||||
if (c != null && !shouldTgtP(ai, sa, dmg, noPrevention, true)) {
|
||||
if (c != null && !shouldTgtP(ai, sa, dmg, noPrevention)) {
|
||||
tcs.add(c);
|
||||
if (divided) {
|
||||
int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
||||
@@ -746,17 +730,17 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// TODO: Improve Damage, we shouldn't just target the player just because we can
|
||||
if (sa.canTarget(enemy) && sa.canAddMoreTarget()) {
|
||||
if (((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|
||||
|| (SpellAbilityAi.isSorcerySpeed(sa, ai) && phase.is(PhaseType.MAIN2))
|
||||
|| ("PingAfterAttack".equals(logic) && phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai))
|
||||
|| immediately || shouldTgtP(ai, sa, dmg, noPrevention)) &&
|
||||
(!avoidTargetP(ai, sa))) {
|
||||
tcs.add(enemy);
|
||||
if (divided) {
|
||||
sa.addDividedAllocation(enemy, dmg);
|
||||
break;
|
||||
if ((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|
||||
|| (isSorcerySpeed(sa, ai) && phase.is(PhaseType.MAIN2))
|
||||
|| immediately) {
|
||||
boolean pingAfterAttack = "PingAfterAttack".equals(logic) && phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai);
|
||||
if ((pingAfterAttack && !avoidTargetP(ai, sa)) || shouldTgtP(ai, sa, dmg, noPrevention)) {
|
||||
tcs.add(enemy);
|
||||
if (divided) {
|
||||
sa.addDividedAllocation(enemy, dmg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -836,7 +820,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
if (!positive && !(saMe instanceof AbilitySub)) {
|
||||
return false;
|
||||
}
|
||||
if (!urgent && !SpellAbilityAi.playReusable(ai, saMe)) {
|
||||
if (!urgent && !playReusable(ai, saMe)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -16,7 +16,7 @@ public class DayTimeAi extends SpellAbilityAi {
|
||||
|
||||
if ((sa.getHostCard().isCreature() && sa.getPayCosts().hasTapCost()) || sa.getPayCosts().hasManaCost()) {
|
||||
// If it involves a cost that may put us at a disadvantage, better activate before own turn if possible
|
||||
if (!SpellAbilityAi.isSorcerySpeed(sa, aiPlayer)) {
|
||||
if (!isSorcerySpeed(sa, aiPlayer)) {
|
||||
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == aiPlayer;
|
||||
} else {
|
||||
return ph.is(PhaseType.MAIN2, aiPlayer); // Give other things a chance to be cast (e.g. Celestus)
|
||||
|
||||
@@ -60,7 +60,7 @@ public class DebuffAi extends SpellAbilityAi {
|
||||
|| !game.getStack().isEmpty()) {
|
||||
// Instant-speed pumps should not be cast outside of combat when the
|
||||
// stack is empty, unless there are specific activation phase requirements
|
||||
if (!SpellAbilityAi.isSorcerySpeed(sa, ai) && !sa.hasParam("ActivationPhases")) {
|
||||
if (!isSorcerySpeed(sa, ai) && !sa.hasParam("ActivationPhases")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
||||
list = ComputerUtilCard.prioritizeCreaturesWorthRemovingNow(ai, list, false);
|
||||
}
|
||||
if (!SpellAbilityAi.playReusable(ai, sa)) {
|
||||
if (!playReusable(ai, sa)) {
|
||||
list = CardLists.filter(list, Predicates.not(CardPredicates.hasCounter(CounterEnumType.SHIELD, 1)));
|
||||
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
|
||||
@@ -101,13 +101,13 @@ public class DigAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||
if (playReusable(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((!game.getPhaseHandler().getNextTurn().equals(ai)
|
||||
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa, ai)
|
||||
&& !sa.hasParam("PlayerTurn") && !isSorcerySpeed(sa, ai)
|
||||
&& (ai.getCardsIn(ZoneType.Hand).size() > 1 || game.getPhaseHandler().getPhase().isBefore(PhaseType.DRAW))
|
||||
&& !ComputerUtil.activateForCost(sa, ai)) {
|
||||
return false;
|
||||
|
||||
@@ -62,13 +62,13 @@ public class DigMultipleAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||
if (playReusable(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((!game.getPhaseHandler().getNextTurn().equals(ai)
|
||||
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa, ai)
|
||||
&& !sa.hasParam("PlayerTurn") && !isSorcerySpeed(sa, ai)
|
||||
&& (ai.getCardsIn(ZoneType.Hand).size() > 1 || game.getPhaseHandler().getPhase().isBefore(PhaseType.DRAW))
|
||||
&& !ComputerUtil.activateForCost(sa, ai)) {
|
||||
return false;
|
||||
|
||||
@@ -23,7 +23,7 @@ public class DigUntilAi extends SpellAbilityAi {
|
||||
Card source = sa.getHostCard();
|
||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||
double chance = .4; // 40 percent chance with instant speed stuff
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa, ai)) {
|
||||
if (isSorcerySpeed(sa, ai)) {
|
||||
chance = .667; // 66.7% chance for sorcery speed (since it will
|
||||
// never activate EOT)
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph, String logic) {
|
||||
if ((!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa, ai)
|
||||
&& !sa.hasParam("PlayerTurn") && !isSorcerySpeed(sa, ai)
|
||||
&& ai.getCardsIn(ZoneType.Hand).size() > 1 && !ComputerUtil.activateForCost(sa, ai)
|
||||
&& !"YawgmothsBargain".equals(logic)) {
|
||||
return false;
|
||||
|
||||
@@ -20,7 +20,7 @@ public class FlipOntoBattlefieldAi extends SpellAbilityAi {
|
||||
PhaseHandler ph = sa.getHostCard().getGame().getPhaseHandler();
|
||||
String logic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
if (!SpellAbilityAi.isSorcerySpeed(sa, aiPlayer) && sa.getPayCosts().hasManaCost()) {
|
||||
if (!isSorcerySpeed(sa, aiPlayer) && sa.getPayCosts().hasManaCost()) {
|
||||
return ph.is(PhaseType.END_OF_TURN);
|
||||
}
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ public class LifeGainAi extends SpellAbilityAi {
|
||||
|
||||
return lifeCritical || activateForCost
|
||||
|| (ph.getNextTurn().equals(ai) && !ph.getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||
|| sa.hasParam("PlayerTurn") || SpellAbilityAi.isSorcerySpeed(sa, ai);
|
||||
|| sa.hasParam("PlayerTurn") || isSorcerySpeed(sa, ai);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -180,8 +180,8 @@ public class LifeGainAi extends SpellAbilityAi {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa, ai)
|
||||
|| sa.getSubAbility() != null || SpellAbilityAi.playReusable(ai, sa)) {
|
||||
if (isSorcerySpeed(sa, ai)
|
||||
|| sa.getSubAbility() != null || playReusable(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ public class LifeLoseAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa, ai) || sa.hasParam("ActivationPhases") || SpellAbilityAi.playReusable(ai, sa)
|
||||
if (isSorcerySpeed(sa, ai) || sa.hasParam("ActivationPhases") || playReusable(ai, sa)
|
||||
|| ComputerUtil.activateForCost(sa, ai)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ public class ManifestAi extends SpellAbilityAi {
|
||||
if (!buff) {
|
||||
return false;
|
||||
}
|
||||
} else if (!SpellAbilityAi.isSorcerySpeed(sa, ai)) {
|
||||
} else if (!isSorcerySpeed(sa, ai)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@@ -152,7 +152,7 @@ public class ManifestAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
// Probably should be a little more discerning on playing during OPPs turn
|
||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||
if (playReusable(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
|
||||
@@ -51,7 +51,7 @@ public class MillAi extends SpellAbilityAi {
|
||||
return (ph.is(PhaseType.MAIN1) || ph.is(PhaseType.MAIN2)) && ph.isPlayerTurn(ai); // Chandra, Torch of Defiance and similar
|
||||
}
|
||||
if (!sa.isPwAbility()) { // Planeswalker abilities are only activated at sorcery speed
|
||||
if ("You".equals(sa.getParam("Defined")) && !(!SpellAbilityAi.isSorcerySpeed(sa, ai) && ph.is(PhaseType.END_OF_TURN)
|
||||
if ("You".equals(sa.getParam("Defined")) && !(!isSorcerySpeed(sa, ai) && ph.is(PhaseType.END_OF_TURN)
|
||||
&& ph.getNextTurn().equals(ai))) {
|
||||
return false; // only self-mill at opponent EOT
|
||||
}
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.CardType.Supertype;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.*;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
@@ -24,6 +18,7 @@ import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class PermanentAi extends SpellAbilityAi {
|
||||
|
||||
@@ -49,19 +44,19 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
final Card card = sa.getHostCard();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
// check on legendary
|
||||
if (!card.ignoreLegendRule() && ai.isCardInPlay(card.getName())) {
|
||||
if (!source.ignoreLegendRule() && ai.isCardInPlay(source.getName())) {
|
||||
// TODO check the risk we'd lose the effect with bad timing
|
||||
if (!card.hasSVar("AILegendaryException")) {
|
||||
if (!source.hasSVar("AILegendaryException")) {
|
||||
// AiPlayDecision.WouldDestroyLegend
|
||||
return false;
|
||||
} else {
|
||||
String specialRule = card.getSVar("AILegendaryException");
|
||||
String specialRule = source.getSVar("AILegendaryException");
|
||||
if ("TwoCopiesAllowed".equals(specialRule)) {
|
||||
// One extra copy allowed on the battlefield, e.g. Brothers Yamazaki
|
||||
if (CardLists.count(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals(card.getName())) > 1) {
|
||||
if (CardLists.count(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals(source.getName())) > 1) {
|
||||
return false;
|
||||
}
|
||||
} else if ("AlwaysAllowed".equals(specialRule)) {
|
||||
@@ -73,23 +68,7 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
/* -- not used anymore after Ixalan (Planeswalkers are now legendary, not unique by subtype) --
|
||||
if (card.isPlaneswalker()) {
|
||||
CardCollection list = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield),
|
||||
CardPredicates.Presets.PLANESWALKERS);
|
||||
for (String type : card.getType().getSubtypes()) { // determine
|
||||
// planewalker
|
||||
// subtype
|
||||
final CardCollection cl = CardLists.getType(list, type);
|
||||
if (!cl.isEmpty()) {
|
||||
// AiPlayDecision.WouldDestroyOtherPlaneswalker
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
|
||||
if (card.getType().hasSupertype(Supertype.World)) {
|
||||
if (source.getType().hasSupertype(Supertype.World)) {
|
||||
CardCollection list = CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "World");
|
||||
if (!list.isEmpty()) {
|
||||
// AiPlayDecision.WouldDestroyWorldEnchantment
|
||||
@@ -101,7 +80,6 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
if (mana.countX() > 0) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai, false);
|
||||
final Card source = sa.getHostCard();
|
||||
if (source.hasConverge()) {
|
||||
int nColors = ComputerUtilMana.getConvergeCount(sa, ai);
|
||||
for (int i = 1; i <= xPay; i++) {
|
||||
@@ -123,7 +101,7 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
}
|
||||
} else if (mana.isZero()) {
|
||||
// if mana is zero, but card mana cost does have X, then something is wrong
|
||||
ManaCost cardCost = card.getManaCost();
|
||||
ManaCost cardCost = source.getManaCost();
|
||||
if (cardCost != null && cardCost.countX() > 0) {
|
||||
// AiPlayDecision.CantPlayAi
|
||||
return false;
|
||||
@@ -143,6 +121,38 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
sa.setXManaCostPaid(xPay);
|
||||
}
|
||||
|
||||
if ("ChaliceOfTheVoid".equals(source.getSVar("AICurseEffect"))) {
|
||||
int maxX = sa.getXManaCostPaid(); // as set above
|
||||
CardCollection otherChalices = CardLists.filter(ai.getGame().getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Chalice of the Void"));
|
||||
outer: for (int i = 0; i <= maxX; i++) {
|
||||
for (Card chalice : otherChalices) {
|
||||
if (chalice.getCounters(CounterEnumType.CHARGE) == i) {
|
||||
continue outer; // already disabled, no point in adding another one
|
||||
}
|
||||
}
|
||||
// assume the AI knows the deck lists of its opponents and if we see a card in a certain zone except for the library or hand,
|
||||
// it likely won't be cast unless it's bounced back (ideally, this should also somehow account for hidden information such as face down cards in exile)
|
||||
final int manaValue = i;
|
||||
CardCollection aiCards = CardLists.filter(ai.getAllCards(), card -> (card.isInZone(ZoneType.Library) || !card.isInZone(ZoneType.Hand))
|
||||
&& card.getState(CardStateName.Original).getManaCost() != null
|
||||
&& card.getState(CardStateName.Original).getManaCost().getCMC() == manaValue);
|
||||
CardCollection oppCards = CardLists.filter(ai.getStrongestOpponent().getAllCards(), card -> (card.isInZone(ZoneType.Library) || !card.isInZone(ZoneType.Hand))
|
||||
&& card.getState(CardStateName.Original).getManaCost() != null
|
||||
&& card.getState(CardStateName.Original).getManaCost().getCMC() == manaValue);
|
||||
if (manaValue == 0) {
|
||||
aiCards = CardLists.filter(aiCards, Predicates.not(CardPredicates.isType("Land")));
|
||||
oppCards = CardLists.filter(oppCards, Predicates.not(CardPredicates.isType("Land")));
|
||||
// also filter out other Chalices in our own deck
|
||||
aiCards = CardLists.filter(aiCards, Predicates.not(CardPredicates.nameEquals("Chalice of the Void")));
|
||||
}
|
||||
if (oppCards.size() > 3 && oppCards.size() >= aiCards.size() * 2) {
|
||||
sa.setXManaCostPaid(manaValue);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.hasParam("Announce") && sa.getParam("Announce").startsWith("Multikicker")) {
|
||||
// String announce = sa.getParam("Announce");
|
||||
ManaCost mkCost = sa.getMultiKickerManaCost();
|
||||
@@ -152,13 +162,13 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
mCost = ManaCost.combine(mCost, mkCost);
|
||||
ManaCostBeingPaid mcbp = new ManaCostBeingPaid(mCost);
|
||||
if (!ComputerUtilMana.canPayManaCost(mcbp, sa, ai, false)) {
|
||||
card.setKickerMagnitude(i);
|
||||
source.setKickerMagnitude(i);
|
||||
sa.setSVar("Multikicker", String.valueOf(i));
|
||||
break;
|
||||
}
|
||||
card.setKickerMagnitude(i + 1);
|
||||
source.setKickerMagnitude(i + 1);
|
||||
}
|
||||
if (isZeroCost && card.getKickerMagnitude() == 0) {
|
||||
if (isZeroCost && source.getKickerMagnitude() == 0) {
|
||||
// Bail if the card cost was {0} and no multikicker was paid (e.g. Everflowing Chalice).
|
||||
// TODO: update this if there's ever a card where it makes sense to play it for {0} with no multikicker
|
||||
return false;
|
||||
@@ -166,13 +176,13 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// don't play cards without being able to pay the upkeep for
|
||||
for (KeywordInterface inst : card.getKeywords()) {
|
||||
for (KeywordInterface inst : source.getKeywords()) {
|
||||
String ability = inst.getOriginal();
|
||||
if (ability.startsWith("UpkeepCost")) {
|
||||
final String[] k = ability.split(":");
|
||||
final String costs = k[1];
|
||||
|
||||
final SpellAbility emptyAbility = new SpellAbility.EmptySa(card, ai);
|
||||
final SpellAbility emptyAbility = new SpellAbility.EmptySa(source, ai);
|
||||
emptyAbility.setPayCosts(new Cost(costs, true));
|
||||
emptyAbility.setTargetRestrictions(sa.getTargetRestrictions());
|
||||
emptyAbility.setCardState(sa.getCardState());
|
||||
@@ -186,8 +196,8 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// check for specific AI preferences
|
||||
if (card.hasSVar("AICastPreference")) {
|
||||
String pref = card.getSVar("AICastPreference");
|
||||
if (source.hasSVar("AICastPreference")) {
|
||||
String pref = source.getSVar("AICastPreference");
|
||||
String[] groups = StringUtils.split(pref, "|");
|
||||
boolean dontCast = false;
|
||||
for (String group : groups) {
|
||||
@@ -205,7 +215,7 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
// Only cast unless there are X or more cards like this on the battlefield under AI control already,
|
||||
CardCollectionView valid = param.contains("Globally") ? ai.getGame().getCardsIn(ZoneType.Battlefield)
|
||||
: ai.getCardsIn(ZoneType.Battlefield);
|
||||
CardCollection ctrld = CardLists.filter(valid, CardPredicates.nameEquals(card.getName()));
|
||||
CardCollection ctrld = CardLists.filter(valid, CardPredicates.nameEquals(source.getName()));
|
||||
|
||||
int numControlled = 0;
|
||||
if (param.endsWith("WithoutOppAuras")) {
|
||||
@@ -242,7 +252,7 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
// if there are X-1 mana sources in play but the AI has an extra land in hand
|
||||
CardCollection m = ComputerUtilMana.getAvailableManaSources(ai, true);
|
||||
int extraMana = CardLists.count(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS) > 0 ? 1 : 0;
|
||||
if (card.getName().equals("Illusions of Grandeur")) {
|
||||
if (source.getName().equals("Illusions of Grandeur")) {
|
||||
// TODO: this is currently hardcoded for specific Illusions-Donate cost reduction spells, need to make this generic.
|
||||
extraMana += Math.min(3, CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), Predicates.or(CardPredicates.nameEquals("Sapphire Medallion"), CardPredicates.nameEquals("Helm of Awakening"))).size()) * 2; // each cost-reduction spell accounts for {1} in both Illusions and Donate
|
||||
}
|
||||
@@ -260,7 +270,7 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
break; // disregard other preferences, always cast as a last resort
|
||||
}
|
||||
} else if (param.equals("OnlyFromZone")) {
|
||||
if (!card.getZone().getZoneType().toString().equals(value)) {
|
||||
if (!source.getZone().getZoneType().toString().equals(value)) {
|
||||
dontCast = true;
|
||||
break; // limit casting to a specific zone only
|
||||
}
|
||||
|
||||
@@ -235,11 +235,19 @@ public class PermanentCreatureAi extends PermanentAi {
|
||||
* worth it. Not sure what 4. is for. 5. needs to be updated to ensure
|
||||
* that the net toughness is still positive after static effects.
|
||||
*/
|
||||
// AiPlayDecision.WouldBecomeZeroToughnessCreature
|
||||
if (card.hasStartOfKeyword("etbCounter") || mana.countX() != 0
|
||||
|| card.hasETBTrigger(false) || card.hasETBReplacement() || card.hasSVar("NoZeroToughnessAI")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final Card copy = CardUtil.getLKICopy(card);
|
||||
ComputerUtilCard.applyStaticContPT(game, copy, null);
|
||||
// AiPlayDecision.WouldBecomeZeroToughnessCreature
|
||||
return copy.getNetToughness() > 0 || copy.hasStartOfKeyword("etbCounter") || mana.countX() != 0
|
||||
|| copy.hasETBTrigger(false) || copy.hasETBReplacement() || copy.hasSVar("NoZeroToughnessAI");
|
||||
if (copy.getNetToughness() > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ public class ProtectAi extends SpellAbilityAi {
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
final boolean notAiMain1 = !(ph.getPlayerTurn() == ai && ph.getPhase() == PhaseType.MAIN1);
|
||||
// sorceries can only give protection in order to create an unblockable attacker
|
||||
return !SpellAbilityAi.isSorcerySpeed(sa, ai) || !notAiMain1;
|
||||
return !isSorcerySpeed(sa, ai) || !notAiMain1;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -87,7 +87,7 @@ public class PumpAi extends PumpAiBase {
|
||||
return true;
|
||||
}
|
||||
|
||||
return SpellAbilityAi.isSorcerySpeed(sa, ai) || (ph.getNextTurn().equals(ai) && !ph.getPhase().isBefore(PhaseType.END_OF_TURN));
|
||||
return isSorcerySpeed(sa, ai) || (ph.getNextTurn().equals(ai) && !ph.getPhase().isBefore(PhaseType.END_OF_TURN));
|
||||
} else if (logic.equals("Aristocrat")) {
|
||||
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(ai, null, true).contains(sa.getHostCard());
|
||||
if (!ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS) && !isThreatened) {
|
||||
@@ -118,7 +118,7 @@ public class PumpAi extends PumpAiBase {
|
||||
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS))) {
|
||||
// Instant-speed pumps should not be cast outside of combat when the
|
||||
// stack is empty
|
||||
return sa.isCurse() || SpellAbilityAi.isSorcerySpeed(sa, ai) || main1Preferred;
|
||||
return sa.isCurse() || isSorcerySpeed(sa, ai) || main1Preferred;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -367,7 +367,7 @@ public class PumpAi extends PumpAiBase {
|
||||
if (ComputerUtilCard.shouldPumpCard(ai, sa, card, defense, attack, keywords, false)) {
|
||||
return true;
|
||||
} else if (containsUsefulKeyword(ai, keywords, card, sa, attack)) {
|
||||
if (game.getPhaseHandler().isPreCombatMain() && SpellAbilityAi.isSorcerySpeed(sa, ai) ||
|
||||
if (game.getPhaseHandler().isPreCombatMain() && isSorcerySpeed(sa, ai) ||
|
||||
game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS, ai) ||
|
||||
game.getPhaseHandler().is(PhaseType.COMBAT_BEGIN, ai)) {
|
||||
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords);
|
||||
|
||||
@@ -4,7 +4,6 @@ import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.AiPlayDecision;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.cost.Cost;
|
||||
@@ -26,7 +25,7 @@ public class RevealAi extends RevealAiBase {
|
||||
|
||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.667, sa.getActivationsThisTurn() + 1);
|
||||
|
||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||
if (playReusable(ai, sa)) {
|
||||
randomReturn = true;
|
||||
}
|
||||
return randomReturn;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.MyRandom;
|
||||
@@ -20,7 +19,7 @@ public class RevealHandAi extends RevealAiBase {
|
||||
|
||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.667, sa.getActivationsThisTurn() + 1);
|
||||
|
||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||
if (playReusable(ai, sa)) {
|
||||
randomReturn = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ public class ScryAi extends SpellAbilityAi {
|
||||
// even if there's no mana cost.
|
||||
if (sa.getPayCosts().hasTapCost()
|
||||
&& (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature()))
|
||||
&& !SpellAbilityAi.isSorcerySpeed(sa, ai)) {
|
||||
&& !isSorcerySpeed(sa, ai)) {
|
||||
return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ public class ScryAi extends SpellAbilityAi {
|
||||
|
||||
// in the playerturn Scry should only be done in Main1 or in upkeep if able
|
||||
if (ph.isPlayerTurn(ai)) {
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa, ai)) {
|
||||
if (isSorcerySpeed(sa, ai)) {
|
||||
return ph.is(PhaseType.MAIN1) || sa.isPwAbility();
|
||||
} else {
|
||||
return ph.is(PhaseType.UPKEEP);
|
||||
@@ -121,12 +121,12 @@ public class ScryAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
double chance = .4; // 40 percent chance of milling with instant speed stuff
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa, ai)) {
|
||||
if (isSorcerySpeed(sa, ai)) {
|
||||
chance = .667; // 66.7% chance for sorcery speed (since it will never activate EOT)
|
||||
}
|
||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||
|
||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||
if (playReusable(ai, sa)) {
|
||||
randomReturn = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -54,13 +54,13 @@ public class SurveilAi extends SpellAbilityAi {
|
||||
// even if there's no mana cost.
|
||||
if (sa.getPayCosts().hasTapCost()
|
||||
&& (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature()))
|
||||
&& !SpellAbilityAi.isSorcerySpeed(sa, ai)) {
|
||||
&& !isSorcerySpeed(sa, ai)) {
|
||||
return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
|
||||
}
|
||||
|
||||
// in the player's turn Surveil should only be done in Main1 or in Upkeep if able
|
||||
if (ph.isPlayerTurn(ai)) {
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa, ai)) {
|
||||
if (isSorcerySpeed(sa, ai)) {
|
||||
return ph.is(PhaseType.MAIN1) || sa.isPwAbility();
|
||||
} else {
|
||||
return ph.is(PhaseType.UPKEEP);
|
||||
@@ -104,12 +104,12 @@ public class SurveilAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
double chance = .4; // 40 percent chance for instant speed
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa, ai)) {
|
||||
if (isSorcerySpeed(sa, ai)) {
|
||||
chance = .667; // 66.7% chance for sorcery speed (since it will never activate EOT)
|
||||
}
|
||||
|
||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||
if (playReusable(ai, sa)) {
|
||||
randomReturn = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ public class TapAi extends TapAiBase {
|
||||
if (turn.isOpponentOf(ai) && phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
// Tap things down if it's Human's turn
|
||||
} else if (turn.equals(ai)) {
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa, ai) && phase.getPhase().isBefore(PhaseType.COMBAT_BEGIN)) {
|
||||
if (isSorcerySpeed(sa, ai) && phase.getPhase().isBefore(PhaseType.COMBAT_BEGIN)) {
|
||||
// Cast it if it's a sorcery.
|
||||
} else if (phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
// Aggro Brains are willing to use TapEffects aggressively instead of defensively
|
||||
@@ -33,7 +33,7 @@ public class TapAi extends TapAiBase {
|
||||
// Don't tap down after blockers
|
||||
return false;
|
||||
}
|
||||
} else if (!SpellAbilityAi.playReusable(ai, sa)) {
|
||||
} else if (!playReusable(ai, sa)) {
|
||||
// Generally don't want to tap things with an Instant during Players turn outside of combat
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ public class TokenAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
if ((ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS))
|
||||
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa, ai)
|
||||
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("PlayerTurn") && !isSorcerySpeed(sa, ai)
|
||||
&& !haste && !pwMinus) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ public class VentureAi extends SpellAbilityAi {
|
||||
// If this has a mana cost, do it at opponent's EOT if able to prevent spending mana early; if sorcery, do it in Main2
|
||||
PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
|
||||
if (sa.getPayCosts().hasManaCost() || sa.getPayCosts().hasTapCost()) {
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa, aiPlayer)) {
|
||||
if (isSorcerySpeed(sa, aiPlayer)) {
|
||||
return ph.is(PhaseType.MAIN2, aiPlayer);
|
||||
} else {
|
||||
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == aiPlayer;
|
||||
|
||||
@@ -1582,10 +1582,6 @@ public class AbilityUtils {
|
||||
host.addRemembered(sa.getTargets());
|
||||
}
|
||||
|
||||
if (sa.hasParam("ImprintTargets") && sa.usesTargeting()) {
|
||||
host.addImprintedCards(sa.getTargets().getTargetCards());
|
||||
}
|
||||
|
||||
if (sa.hasParam("RememberCostMana")) {
|
||||
host.clearRemembered();
|
||||
ManaCostBeingPaid activationMana = new ManaCostBeingPaid(sa.getPayCosts().getTotalMana());
|
||||
|
||||
@@ -62,10 +62,6 @@ public class FightEffect extends DamageBaseEffect {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sa.hasParam("RememberObjects")) {
|
||||
host.addRemembered(AbilityUtils.getDefinedObjects(host, sa.getParam("RememberObjects"), sa));
|
||||
}
|
||||
|
||||
Player controller = host.getController();
|
||||
boolean isOptional = sa.hasParam("Optional");
|
||||
|
||||
@@ -147,7 +143,6 @@ public class FightEffect extends DamageBaseEffect {
|
||||
}
|
||||
|
||||
private void dealDamage(final SpellAbility sa, Card fighterA, Card fighterB) {
|
||||
|
||||
boolean usedDamageMap = true;
|
||||
CardDamageMap damageMap = sa.getDamageMap();
|
||||
CardDamageMap preventMap = sa.getPreventMap();
|
||||
|
||||
@@ -70,7 +70,11 @@ public class StaticAbilityDisableTriggers {
|
||||
|
||||
if (trigMode.equals(TriggerType.ChangesZone)) {
|
||||
// Cause of the trigger – the card changing zones
|
||||
if (!stAb.matchesValidParam("ValidCause", runParams.get(AbilityKey.Card))) {
|
||||
Card moved = (Card) runParams.get(AbilityKey.Card);
|
||||
if ("Battlefield".equals(regtrig.getParam("Origin"))) {
|
||||
moved = (Card) runParams.get(AbilityKey.CardLKI);
|
||||
}
|
||||
if (!stAb.matchesValidParam("ValidCause", moved)) {
|
||||
return false;
|
||||
}
|
||||
if (!stAb.matchesValidParam("Destination", runParams.get(AbilityKey.Destination))) {
|
||||
|
||||
@@ -89,7 +89,11 @@ public class StaticAbilityPanharmonicon {
|
||||
|
||||
if (trigMode.equals(TriggerType.ChangesZone)) {
|
||||
// Cause of the trigger – the card changing zones
|
||||
if (!stAb.matchesValidParam("ValidCause", runParams.get(AbilityKey.Card))) {
|
||||
Card moved = (Card) runParams.get(AbilityKey.Card);
|
||||
if ("Battlefield".equals(trigger.getParam("Origin"))) {
|
||||
moved = (Card) runParams.get(AbilityKey.CardLKI);
|
||||
}
|
||||
if (!stAb.matchesValidParam("ValidCause", moved)) {
|
||||
return false;
|
||||
}
|
||||
if (!stAb.matchesValidParam("Origin", runParams.get(AbilityKey.Origin))) {
|
||||
|
||||
@@ -120,19 +120,8 @@ public class TriggerChangesZone extends Trigger {
|
||||
}
|
||||
}
|
||||
|
||||
if (hasParam("ValidCause")) {
|
||||
if (!runParams.containsKey(AbilityKey.Cause)) {
|
||||
return false;
|
||||
}
|
||||
SpellAbility cause = (SpellAbility) runParams.get(AbilityKey.Cause);
|
||||
if (cause == null) {
|
||||
return false;
|
||||
}
|
||||
if (!matchesValid(cause, getParam("ValidCause").split(","))) {
|
||||
if (!matchesValid(cause.getHostCard(), getParam("ValidCause").split(","))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!matchesValidParam("ValidCause", runParams.get(AbilityKey.Cause))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasParam("Fizzle")) {
|
||||
|
||||
@@ -25,7 +25,9 @@ public class TriggerChangesZoneAll extends Trigger {
|
||||
|
||||
if (!matchesValidParam("ValidCause", runParams.get(AbilityKey.Cause))) {
|
||||
return false;
|
||||
} else if (hasParam("ValidAmount")) {
|
||||
}
|
||||
|
||||
if (hasParam("ValidAmount")) {
|
||||
int right = AbilityUtils.calculateAmount(hostCard, getParam("ValidAmount").substring(2), this);
|
||||
if (!Expressions.compare(this.filterCards(table).size(), getParam("ValidAmount").substring(0, 2), right)) { return false; }
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@ SVar:TrigCounter:DB$ Counter | Defined$ TriggeredSpellAbility
|
||||
SVar:X:Count$xPaid
|
||||
SVar:Y:Count$CardCounters.CHARGE
|
||||
SVar:AICurseEffect:ChaliceOfTheVoid
|
||||
AI:RemoveDeck:All
|
||||
AI:RemoveDeck:Random
|
||||
Oracle:Chalice of the Void enters the battlefield with X charge counters on it.\nWhenever a player casts a spell with mana value equal to the number of charge counters on Chalice of the Void, counter that spell.
|
||||
|
||||
@@ -4,7 +4,7 @@ Types:Legendary Creature Human Shaman
|
||||
PT:2/2
|
||||
T:Mode$ SpellCast | ValidCard$ Card.Red | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUntap | TriggerDescription$ Whenever you cast a red spell, untap CARDNAME.
|
||||
SVar:TrigUntap:DB$ Untap | Defined$ Self
|
||||
A:AB$ DealDamage | Cost$ T | ValidTgts$ Player,Planeswalker | TgtPrompt$ Select target player or planeswalker | NumDmg$ 1 | SubAbility$ DBTransform | AILogic$ PingAfterAttack | SpellDescription$ CARDNAME deals 1 damage to target player or planeswalker. If CARDNAME has dealt 3 or more damage this turn, exile her, then return her to the battlefield transformed under her owner's control.
|
||||
A:AB$ DealDamage | Cost$ T | ValidTgts$ Player,Planeswalker | TgtPrompt$ Select target player or planeswalker | NumDmg$ 1 | SubAbility$ DBTransform | AILogic$ PingAfterAttack | SpellDescription$ CARDNAME deals 1 damage to target player or planeswalker. If NICKNAME has dealt 3 or more damage this turn, exile her, then return her to the battlefield transformed under her owner's control.
|
||||
SVar:DBTransform:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | SubAbility$ DBReturn | ConditionCheckSVar$ X | ConditionSVarCompare$ GE3 | StackDescription$ If CARDNAME has dealt 3 or more damage this turn, exile her, then return her to the battlefield transformed under her owner's control.
|
||||
SVar:DBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | Transformed$ True | SubAbility$ DBCleanup | StackDescription$
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Cleaver Skaab
|
||||
ManaCost:3 U
|
||||
Types:Creature Zombie Horror
|
||||
PT:2/4
|
||||
A:AB$ CopyPermanent | Cost$ 3 T Sac<1/Zombie.Other/another zombie> | Defined$ Sacrificed | NumCopies$ 2 | AILogic$ AtOppEOT
|
||||
A:AB$ CopyPermanent | Cost$ 3 T Sac<1/Zombie.Other/another zombie> | Defined$ Sacrificed | NumCopies$ 2 | AILogic$ AtOppEOT | SpellDescription$ Create two tokens that are copies of the sacrificed creature.
|
||||
DeckNeeds:Type$Zombie
|
||||
DeckHas:Ability$Sacrifice|Token
|
||||
SVar:AIPreference:SacCost$Zombie.Other
|
||||
|
||||
@@ -2,10 +2,9 @@ Name:Parallax Tide
|
||||
ManaCost:2 U U
|
||||
Types:Enchantment
|
||||
K:Fading:5
|
||||
A:AB$ ChangeZone | Cost$ SubCounter<1/FADE> | ValidTgts$ Land | TgtPrompt$ Select target land | ImprintTargets$ True | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Exile target land.
|
||||
A:AB$ ChangeZone | Cost$ SubCounter<1/FADE> | ValidTgts$ Land | TgtPrompt$ Select target land | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Exile target land.
|
||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When CARDNAME leaves the battlefield, each player returns to the battlefield all cards they own exiled with CARDNAME.
|
||||
SVar:TrigReturn:DB$ ChangeZone | Defined$ Imprinted | Origin$ Exile | Destination$ Battlefield | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearImprinted$ True
|
||||
SVar:TrigReturn:DB$ ChangeZone | Defined$ ExiledWith | Origin$ Exile | Destination$ Battlefield
|
||||
SVar:PlayMain1:TRUE
|
||||
AI:RemoveDeck:All
|
||||
Oracle:Fading 5 (This enchantment enters the battlefield with five fade counters on it. At the beginning of your upkeep, remove a fade counter from it. If you can't, sacrifice it.)\nRemove a fade counter from Parallax Tide: Exile target land.\nWhen Parallax Tide leaves the battlefield, each player returns to the battlefield all cards they own exiled with Parallax Tide.
|
||||
|
||||
@@ -2,10 +2,9 @@ Name:Parallax Wave
|
||||
ManaCost:2 W W
|
||||
Types:Enchantment
|
||||
K:Fading:5
|
||||
A:AB$ ChangeZone | Cost$ SubCounter<1/FADE> | ValidTgts$ Creature | TgtPrompt$ Select target creature | ImprintTargets$ True | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Exile target creature.
|
||||
A:AB$ ChangeZone | Cost$ SubCounter<1/FADE> | ValidTgts$ Creature | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Exile target creature.
|
||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When CARDNAME leaves the battlefield, each player returns to the battlefield all cards they own exiled with CARDNAME.
|
||||
SVar:TrigReturn:DB$ ChangeZone | Defined$ Imprinted | Origin$ Exile | Destination$ Battlefield | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearImprinted$ True
|
||||
SVar:TrigReturn:DB$ ChangeZone | Defined$ ExiledWith | Origin$ Exile | Destination$ Battlefield
|
||||
SVar:PlayMain1:TRUE
|
||||
AI:RemoveDeck:All
|
||||
Oracle:Fading 5 (This enchantment enters the battlefield with five fade counters on it. At the beginning of your upkeep, remove a fade counter from it. If you can't, sacrifice it.)\nRemove a fade counter from Parallax Wave: Exile target creature.\nWhen Parallax Wave leaves the battlefield, each player returns to the battlefield all cards they own exiled with Parallax Wave.
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Preston, the Vanisher
|
||||
ManaCost:3 W
|
||||
Types:Legendary Creature Rabbit Wizard
|
||||
PT:2/5
|
||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.nonToken+wasNotCast+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigCopyPermanent | TriggerDescription$ Whenever another nontoken creature enters the battlefield under your control, if it wasn't cast, create a token that's a copy of that creature except it's a 0/1 white Illusion.
|
||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.Other+nonToken+wasNotCast+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigCopyPermanent | TriggerDescription$ Whenever another nontoken creature enters the battlefield under your control, if it wasn't cast, create a token that's a copy of that creature except it's a 0/1 white Illusion.
|
||||
SVar:TrigCopyPermanent:DB$ CopyPermanent | Defined$ TriggeredCardLKICopy | NumCopies$ 1 | SetColor$ White | SetCreatureTypes$ Illusion | SetPower$ 0 | SetToughness$ 1
|
||||
A:AB$ ChangeZone | Cost$ 1 W Sac<5/Illusion> | ValidTgts$ Permanent.nonLand | Origin$ Battlefield | Destination$ Exile | TgtPrompt$ Select target nonland permanent | SpellDescription$ Exile target nonland permanent.
|
||||
DeckHas:Ability$Token|Sacrifice & Type$Illusion
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
Name:Sacred Ground
|
||||
ManaCost:1 W
|
||||
Types:Enchantment
|
||||
T:Mode$ Sacrificed | ValidCard$ Land.YouOwn | ValidCause$ SpellAbility.OppCtrl | Execute$ TrigReturn | TriggerZones$ Battlefield | TriggerDescription$ Whenever a spell or ability an opponent controls causes a land to be put into your graveyard from the battlefield, return that card to the battlefield.
|
||||
T:Mode$ Destroyed | ValidCauser$ Player.Opponent | ValidCard$ Land.YouOwn | Execute$ TrigReturn | Secondary$ True | TriggerZones$ Battlefield | TriggerDescription$ Whenever a spell or ability an opponent controls causes a land to be put into your graveyard from the battlefield, return that card to the battlefield.
|
||||
SVar:TrigReturn:DB$ ChangeZone | Defined$ TriggeredCard | Origin$ Graveyard | Destination$ Battlefield
|
||||
T:Mode$ ChangesZone | ValidCard$ Land.YouOwn | ValidCause$ SpellAbility.OppCtrl | Execute$ TrigReturn | TriggerZones$ Battlefield | TriggerDescription$ Whenever a spell or ability an opponent controls causes a land to be put into your graveyard from the battlefield, return that card to the battlefield.
|
||||
SVar:TrigReturn:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Origin$ Graveyard | Destination$ Battlefield
|
||||
AI:RemoveDeck:Random
|
||||
Oracle:Whenever a spell or ability an opponent controls causes a land to be put into your graveyard from the battlefield, return that card to the battlefield.
|
||||
|
||||
@@ -2,9 +2,9 @@ Name:Skyshroud Ambush
|
||||
ManaCost:1 G
|
||||
Types:Instant
|
||||
A:SP$ Pump | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Choose target creature you control | ImprintCards$ ThisTargetedCard | SubAbility$ DBFight | StackDescription$ Target creature you control [{c:ThisTargetedCard}] | SpellDescription$ Target creature you control fights target creature you don't control. When the creature you control wins the fight, draw a card.
|
||||
SVar:DBFight:DB$ Fight | Defined$ ParentTarget | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Choose target creature you don't control | RememberObjects$ ThisTargetedCard | SubAbility$ DBEffect | StackDescription$ fights target creature you don't control [{c:ThisTargetedCard}]. When the creature you control wins the fight, draw a card.
|
||||
SVar:DBEffect:DB$ Effect | Triggers$ TrigWin | RememberObjects$ Remembered | ImprintCards$ Imprinted | Duration$ UntilStateBasedActionChecked | SubAbility$ DBCleanup
|
||||
SVar:DBFight:DB$ Fight | Defined$ ParentTarget | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Choose target creature you don't control | SubAbility$ DBEffect | StackDescription$ fights target creature you don't control [{c:ThisTargetedCard}]. When the creature you control wins the fight, draw a card.
|
||||
SVar:DBEffect:DB$ Effect | Triggers$ TrigWin | RememberObjects$ ParentTarget | ImprintCards$ Imprinted | Duration$ UntilStateBasedActionChecked | SubAbility$ DBCleanup
|
||||
SVar:TrigWin:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Creature.IsRemembered | IsPresent$ Creature.IsImprinted | NoResolvingCheck$ True | Execute$ TrigDraw | TriggerDescription$ When the creature you control wins the fight, draw a card.
|
||||
SVar:TrigDraw:DB$ Draw | NumCards$ 1
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearImprinted$ True
|
||||
Oracle:Target creature you control fights target creature you don't control. When the creature you control wins the fight, draw a card.
|
||||
|
||||
@@ -5,6 +5,7 @@ PT:4/5
|
||||
K:Exalted
|
||||
T:Mode$ Attacks | ValidCard$ Creature.YouCtrl | Alone$ True | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigSearch | TriggerDescription$ Whenever a creature you control attacks alone, you may search your library for an Aura card that could enchant that creature, put it onto the battlefield attached to that creature, then shuffle.
|
||||
SVar:TrigSearch:DB$ Pump | RememberObjects$ TriggeredAttacker | SubAbility$ DBMoveAura
|
||||
SVar:DBMoveAura:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Aura.CanEnchantRemembered+YouCtrl | AttachedTo$ Remembered | ChangeNum$ 1 | Hidden$ True | ShuffleNonMandatory$ True
|
||||
SVar:DBMoveAura:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Aura.CanEnchantRemembered+YouCtrl | AttachedTo$ Remembered | ChangeNum$ 1 | Hidden$ True | ShuffleNonMandatory$ True | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
AI:RemoveDeck:Random
|
||||
Oracle:Exalted (Whenever a creature you control attacks alone, that creature gets +1/+1 until end of turn.)\nWhenever a creature you control attacks alone, you may search your library for an Aura card that could enchant that creature, put it onto the battlefield attached to that creature, then shuffle.
|
||||
|
||||
@@ -4,6 +4,6 @@ Types:Artifact Equipment
|
||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigInitiative | TriggerDescription$ When CARDNAME enters the battlefield, you take the initiative.
|
||||
SVar:TrigInitiative:DB$ TakeInitiative
|
||||
T:Mode$ AttackerBlocked | ValidCard$ Creature.EquippedBy | Execute$ TrigDamage | TriggerDescription$ Whenever equipped creature becomes blocked, it deals 2 damage to each creature blocking it.
|
||||
SVar:TrigDamage:DB$ DamageAll | NumDmg$ 2 | ValidCards$ Creature.blockingEquipped
|
||||
SVar:TrigDamage:DB$ DamageAll | NumDmg$ 2 | ValidCards$ Creature.blockingEquipped | DamageSource$ TriggeredAttackerLKICopy
|
||||
K:Equip:1
|
||||
Oracle:When Trailblazer's Torch enters the battlefield, you take the initiative.\nWhenever equipped creature becomes blocked, it deals 2 damage to each creature blocking it.\nEquip {1} ({1}: Attach to target creature you control. Equip only as a sorcery.)
|
||||
|
||||
@@ -5,6 +5,6 @@ PT:1/3
|
||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigScry | TriggerDescription$ When CARDNAME enters the battlefield, scry 2.
|
||||
SVar:TrigScry:DB$ Scry | ScryNum$ 2
|
||||
A:AB$ BecomeMonarch | Cost$ 3 R T Sac<1/NICKNAME> | ValidTgts$ Player | SubAbility$ DBDamage | SpellDescription$ Target player becomes the monarch. NICKNAME deals 3 damage to any target.
|
||||
SVar:DBPower:DB$ DealDamage | ValidTgts$ Any | NumDmg$ 3
|
||||
SVar:DBDamage:DB$ DealDamage | ValidTgts$ Any | NumDmg$ 3
|
||||
DeckHas:Ability$Sacrifice
|
||||
Oracle:When Denethor, Stone Seer enters the battlefield, scry 2.\n{3}{R}, {T}, Sacrifice Denethor: Target player becomes the monarch. Denethor deals 3 damage to any target.
|
||||
Oracle:When Denethor, Stone Seer enters the battlefield, scry 2.\n{3}{R}, {T}, Sacrifice Denethor: Target player becomes the monarch. Denethor deals 3 damage to any target.
|
||||
|
||||
@@ -2,7 +2,6 @@ Name:Esquire of the King
|
||||
ManaCost:W
|
||||
Types:Creature Human Soldier
|
||||
PT:1/1
|
||||
K:Flying
|
||||
A:AB$ PumpAll | NumAtt$ +1 | NumDef$ +1 | Cost$ 4 W T | ValidCards$ Creature.YouCtrl | ReduceCost$ X | SpellDescription$ Creatures you control get +1/+1 until end of turn. This ability costs {2} less to activate if you control a legendary creature.
|
||||
SVar:X:Count$Compare Y GE1.2.0
|
||||
SVar:Y:Count$Valid Creature.Legendary+YouCtrl
|
||||
|
||||
@@ -4,8 +4,8 @@ Types:Legendary Creature Human Advisor
|
||||
PT:1/4
|
||||
S:Mode$ CantBlockBy | ValidAttacker$ Creature.Self | Description$ CARDNAME can't be blocked.
|
||||
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDigUntil | TriggerZones$ Battlefield | TriggerDescription$ Whenever NICKNAME deals combat damage to a player, that player exiles cards from the top of their library until they exile an instant or sorcery card. You may cast that card without paying its mana cost. Then that player puts the exiled cards that weren't cast this way on the bottom of their library in a random order.
|
||||
SVar:TrigDigUntil:DB$ DigUntil | Defined$ TriggeredTarget | Valid$ Instant,Sorcery | ValidDescription$ instant or sorcery | FoundDestination$ Exile | RevealedDestination$ Exile | RememberFound$ True | IsCurse$ True | SubAbility$ DBPlay
|
||||
SVar:DBPlay:DB$ Play | ValidZone$ Exile | Valid$ Instant.IsRemembered,Sorcery.IsRemembered | ValidSA$ Spell | WithoutManaCost$ True | Optional$ True | ForgetPlayed$ True | SubAbility$ DBRestRandomOrder
|
||||
SVar:DBRestRandomOrder:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Exile | Destination$ Library | LibraryPosition$ -1 | RandomOrder$ True | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
SVar:TrigDigUntil:DB$ DigUntil | Defined$ TriggeredTarget | Valid$ Instant,Sorcery | ValidDescription$ instant or sorcery | FoundDestination$ Exile | RevealedDestination$ Exile | RememberFound$ True | ImprintRevealed$ True | IsCurse$ True | SubAbility$ DBPlay
|
||||
SVar:DBPlay:DB$ Play | ValidZone$ Exile | Valid$ Instant.IsRemembered,Sorcery.IsRemembered | ValidSA$ Spell | WithoutManaCost$ True | Optional$ True | SubAbility$ DBRestRandomOrder
|
||||
SVar:DBRestRandomOrder:DB$ ChangeZoneAll | ChangeType$ Card.IsImprinted | Origin$ Exile | Destination$ Library | LibraryPosition$ -1 | RandomOrder$ True | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True
|
||||
Oracle:Grima, Saruman's Footman can't be blocked.\nWhenever Grima deals combat damage to a player, that player exiles cards from the top of their library until they exile an instant or sorcery card. You may cast that card without paying its mana cost. Then that player puts the exiled cards that weren't cast this way on the bottom of their library in a random order.
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
Name:Vivien's Invocation
|
||||
ManaCost:5 G G
|
||||
Types:Sorcery
|
||||
A:SP$ Dig | Cost$ 5 G G | DigNum$ 7 | ChangeNum$ 1 | ChangeValid$ Creature | Optional$ True | RestRandomOrder$ True | DestinationZone$ Battlefield | ForceRevealToController$ True | SpellDescription$ Look at the top seven cards of your library. You may put a creature card from among them onto the battlefield. Put the rest on the bottom of your library in a random order. When a creature is put onto the battlefield this way, it deals damage equal to its power to target creature an opponent controls.
|
||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature | ValidCause$ Card.Self | Execute$ DBDealDamage | Secondary$ True | TriggerDescription$ When a creature is put onto the battlefield this way, it deals damage equals to its power to target creature an opponent controls.
|
||||
SVar:DBDealDamage:DB$ DealDamage | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls | NumDmg$ X | DamageSource$ TriggeredCard
|
||||
SVar:X:TriggeredCard$CardPower
|
||||
A:SP$ Dig | Cost$ 5 G G | DigNum$ 7 | ChangeNum$ 1 | ChangeValid$ Creature | Optional$ True | RestRandomOrder$ True | DestinationZone$ Battlefield | ForceRevealToController$ True | RememberChanged$ True | SubAbility$ DBTrig | SpellDescription$ Look at the top seven cards of your library. You may put a creature card from among them onto the battlefield. Put the rest on the bottom of your library in a random order. When a creature is put onto the battlefield this way, it deals damage equal to its power to target creature an opponent controls.
|
||||
SVar:DBTrig:DB$ ImmediateTrigger | RememberObjects$ Remembered | Execute$ DBDealDamage | ConditionDefined$ Remembered | ConditionPresent$ Card | SubAbility$ DBCleanup | TriggerDescription$ When a creature is put onto the battlefield this way, it deals damage equals to its power to target creature an opponent controls.
|
||||
SVar:DBDealDamage:DB$ DealDamage | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls | NumDmg$ X | DamageSource$ DelayTriggerRememberedLKI
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
SVar:X:TriggerRemembered$CardPower
|
||||
Oracle:Look at the top seven cards of your library. You may put a creature card from among them onto the battlefield. Put the rest on the bottom of your library in a random order. When a creature is put onto the battlefield this way, it deals damage equal to its power to target creature an opponent controls.
|
||||
|
||||
27
forge-gui/res/editions/Explorer Anthology 3.txt
Normal file
27
forge-gui/res/editions/Explorer Anthology 3.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
[metadata]
|
||||
Code=EA3
|
||||
Date=2023-07-18
|
||||
Name=Explorer Anthology 3
|
||||
Type=Online
|
||||
ScryfallCode=EA3
|
||||
|
||||
[cards]
|
||||
1 R Cyclonic Rift @Chris Rahn
|
||||
2 R Abbot of Keral Keep @Deruchenko Alexander
|
||||
3 U Shrapnel Blast @Hideaki Takamura
|
||||
4 R Eidolon of Blossoms @Min Yum
|
||||
5 R Chord of Calling @Karl Kopinski
|
||||
6 M Worldspine Wurm @Richard Wright
|
||||
7 U Golgari Charm @Zoltan Boros
|
||||
8 U Simic Charm @Zoltan Boros
|
||||
9 C Izzet Charm @Zoltan Boros
|
||||
10 U Gruul Charm @Zoltan Boros
|
||||
11 U Orzhov Charm @Zoltan Boros
|
||||
12 R Voice of Resurgence @Winona Nelson
|
||||
13 R Deathrite Shaman @Steve Argyle
|
||||
14 R Canopy Vista @Adam Paquette
|
||||
15 R Cinder Glade @Adam Paquette
|
||||
16 R Smoldering Marsh @Adam Paquette
|
||||
17 R Sunken Hollow @Adam Paquette
|
||||
18 R Prairie Stream @Adam Paquette
|
||||
19 R Thespian's Stage @John Avon
|
||||
27
forge-gui/res/editions/Historic Anthology 7.txt
Normal file
27
forge-gui/res/editions/Historic Anthology 7.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
[metadata]
|
||||
Code=HA7
|
||||
Date=2023-07-18
|
||||
Name=Historic Anthology 7
|
||||
Type=Online
|
||||
ScryfallCode=HA7
|
||||
|
||||
[cards]
|
||||
1 M Sun Titan @Todd Lockwood
|
||||
2 M Frost Titan @Mike Bierek
|
||||
3 M Vendilion Clique @Willian Murai
|
||||
4 M Grave Titan @Nils Hamm
|
||||
5 M Inferno Titan @Kev Walker
|
||||
6 C Tribal Flames @Zack Stella
|
||||
7 M Primeval Titan @Aleksi Briclot
|
||||
8 C Wild Nacatl @Wayne Reynolds
|
||||
9 U Acidic Slime @Karl Kopinski
|
||||
10 R Tooth and Nail @Jesper Ejsing
|
||||
11 U Bloodbraid Elf @Raymond Swanland
|
||||
12 U Mortarpod @Eric Deschamps
|
||||
13 U Worn Powerstone @Henry G. Higginbotham
|
||||
14 R Sword of Fire and Ice @Mark Zug
|
||||
15 R Fiery Islet @Richard Wright
|
||||
16 R Sunbaked Canyon @Yeong-Hao Han
|
||||
17 R Nurturing Peatland @Sam Burley
|
||||
18 R Silent Clearing @Seb McKinnon
|
||||
19 R Waterlogged Grove @John Avon
|
||||
@@ -93,7 +93,7 @@ ScryfallCode=JMP
|
||||
85 R Angel of the Dire Hour @Jack Wang
|
||||
86 R Angelic Arbiter @Steve Argyle
|
||||
87 C Angelic Edict @Trevor Claxton
|
||||
88 U Angelic Page @Chris Rahn
|
||||
88 C Angelic Page @Chris Rahn
|
||||
89 R Archon of Justice @Jason Chan
|
||||
90 R Archon of Redemption @Steven Belledin
|
||||
91 C Battlefield Promotion @Scott Murphy
|
||||
|
||||
8
forge-gui/res/formats/Archived/Explorer/2023-07-18.txt
Normal file
8
forge-gui/res/formats/Archived/Explorer/2023-07-18.txt
Normal file
File diff suppressed because one or more lines are too long
7
forge-gui/res/formats/Archived/Historic/2023-07-18.txt
Normal file
7
forge-gui/res/formats/Archived/Historic/2023-07-18.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
[format]
|
||||
Name:Historic (HA7)
|
||||
Type:Archived
|
||||
Subtype:Arena
|
||||
Effective:2023-07-18
|
||||
Sets:XLN, RIX, DOM, M19, ANA, PANA, GRN, G18, RNA, WAR, M20, ELD, HA1, THB, HA2, IKO, HA3, M21, JMP, AJMP, AKR, ANB, ZNR, KLR, KHM, HA4, STX, STA, HA5, AFR, J21, MID, VOW, YMID, NEO, YNEO, SNC, YSNC, HBG, HA6, EA1, DMU, YDMU, BRO, BRR, YBRO, EA2, ONE, YONE, SIR, SIS, MOM, MUL, MAT, LTR, HA7, EA3
|
||||
Banned:Agent of Treachery; Brainstorm; Channel; Counterspell; Dark Ritual; Demonic Tutor; Field of the Dead; Lightning Bolt; Memory Lapse; Mishra's Bauble; Natural Order; Nexus of Fate; Oko, Thief of Crowns; Once Upon a Time; Ragavan, Nimble Pilferer; Swords to Plowshares; Thassa's Oracle; Tibalt's Trickery; Time Warp; Uro, Titan of Nature's Wrath; Veil of Summer; Wilderness Reclamation
|
||||
@@ -4,5 +4,5 @@ Type:Digital
|
||||
Subtype:Arena
|
||||
Effective:2019-11-21
|
||||
Order:142
|
||||
Sets:XLN, RIX, DOM, M19, ANA, PANA, GRN, G18, RNA, WAR, M20, ELD, HA1, THB, HA2, IKO, HA3, M21, JMP, AJMP, AKR, ANB, ZNR, KLR, KHM, HA4, STX, STA, HA5, AFR, J21, MID, VOW, YMID, NEO, YNEO, SNC, YSNC, HBG, HA6, EA1, DMU, YDMU, BRO, BRR, YBRO, EA2, ONE, YONE, SIR, SIS, MOM, MUL, MAT, LTR
|
||||
Sets:XLN, RIX, DOM, M19, ANA, PANA, GRN, G18, RNA, WAR, M20, ELD, HA1, THB, HA2, IKO, HA3, M21, JMP, AJMP, AKR, ANB, ZNR, KLR, KHM, HA4, STX, STA, HA5, AFR, J21, MID, VOW, YMID, NEO, YNEO, SNC, YSNC, HBG, HA6, EA1, DMU, YDMU, BRO, BRR, YBRO, EA2, ONE, YONE, SIR, SIS, MOM, MUL, MAT, LTR, HA7, EA3
|
||||
Banned:Agent of Treachery; Brainstorm; Channel; Counterspell; Dark Ritual; Demonic Tutor; Field of the Dead; Lightning Bolt; Memory Lapse; Mishra's Bauble; Natural Order; Nexus of Fate; Oko, Thief of Crowns; Once Upon a Time; Ragavan, Nimble Pilferer; Swords to Plowshares; Thassa's Oracle; Tibalt's Trickery; Time Warp; Uro, Titan of Nature's Wrath; Veil of Summer; Wilderness Reclamation
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.5 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 697 KiB After Width: | Height: | Size: 823 KiB |
Reference in New Issue
Block a user