mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 11:18:01 +00:00
Card: refactor hidden keywords into Table Structure
This commit is contained in:
committed by
Michael Kamensky
parent
83a7cc8a3e
commit
bfc938be57
@@ -42,7 +42,6 @@ import forge.game.combat.Combat;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.combat.GlobalAttackRestrictions;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -742,14 +741,9 @@ public class AiAttackController {
|
||||
// TODO: if there are other ways to tap this creature (like mana creature), then don't need to attack
|
||||
mustAttack = true;
|
||||
} else {
|
||||
for (KeywordInterface inst : attacker.getKeywords()) {
|
||||
String s = inst.getOriginal();
|
||||
if (s.equals("CARDNAME attacks each turn if able.")
|
||||
|| s.startsWith("CARDNAME attacks specific player each combat if able")
|
||||
|| s.equals("CARDNAME attacks each combat if able.")) {
|
||||
mustAttack = true;
|
||||
break;
|
||||
}
|
||||
// TODO move to static Ability
|
||||
if (attacker.hasKeyword("CARDNAME attacks each combat if able.") || attacker.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
|
||||
mustAttack = true;
|
||||
}
|
||||
}
|
||||
if (mustAttack || attacker.getController().getMustAttackEntity() != null || attacker.getController().getMustAttackEntityThisTurn() != null) {
|
||||
@@ -1201,13 +1195,9 @@ public class AiAttackController {
|
||||
int defPower = CardLists.getTotalPower(validBlockers, true, false);
|
||||
|
||||
if (!hasCombatEffect) {
|
||||
for (KeywordInterface inst : attacker.getKeywords()) {
|
||||
String keyword = inst.getOriginal();
|
||||
if (keyword.equals("Wither") || keyword.equals("Infect")
|
||||
|| keyword.equals("Lifelink") || keyword.startsWith("Afflict")) {
|
||||
hasCombatEffect = true;
|
||||
break;
|
||||
}
|
||||
if (attacker.hasKeyword(Keyword.WITHER) || attacker.hasKeyword(Keyword.INFECT)
|
||||
|| attacker.hasKeyword(Keyword.LIFELINK) || attacker.hasKeyword(Keyword.AFFLICT)) {
|
||||
hasCombatEffect = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ import forge.game.combat.Combat;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.staticability.StaticAbilityCantAttackBlock;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
@@ -183,9 +184,7 @@ public class AiBlockController {
|
||||
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
||||
|
||||
for (final Card attacker : attackersLeft) {
|
||||
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
||||
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")
|
||||
|| attacker.hasKeyword(Keyword.MENACE)) {
|
||||
if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() > 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -296,8 +295,7 @@ public class AiBlockController {
|
||||
|
||||
// 6. Blockers that don't survive until the next turn anyway
|
||||
for (final Card attacker : attackersLeft) {
|
||||
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)
|
||||
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
||||
if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() > 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -323,7 +321,17 @@ public class AiBlockController {
|
||||
attackersLeft = (new ArrayList<>(currentAttackers));
|
||||
}
|
||||
|
||||
static final Predicate<Card> rampagesOrNeedsManyToBlock = Predicates.or(CardPredicates.containsKeyword("Rampage"), CardPredicates.containsKeyword("CantBeBlockedByAmount GT"));
|
||||
private Predicate<Card> rampagesOrNeedsManyToBlock(final Combat combat) {
|
||||
return Predicates.or(CardPredicates.hasKeyword(Keyword.RAMPAGE), new Predicate<Card>() {
|
||||
|
||||
@Override
|
||||
public boolean apply(Card input) {
|
||||
// select creature that has a max blocker
|
||||
return StaticAbilityCantAttackBlock.getMinMaxBlocker(input, combat.getDefenderPlayerByAttacker(input)).getRight() < Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
// Good Gang Blocks means a good trade or no trade
|
||||
/**
|
||||
@@ -334,7 +342,7 @@ public class AiBlockController {
|
||||
* @param combat a {@link forge.game.combat.Combat} object.
|
||||
*/
|
||||
private void makeGangBlocks(final Combat combat) {
|
||||
List<Card> currentAttackers = CardLists.filter(attackersLeft, Predicates.not(rampagesOrNeedsManyToBlock));
|
||||
List<Card> currentAttackers = CardLists.filter(attackersLeft, Predicates.not(rampagesOrNeedsManyToBlock(combat)));
|
||||
List<Card> blockers;
|
||||
|
||||
// Try to block an attacker without first strike with a gang of first strikers
|
||||
@@ -528,7 +536,7 @@ public class AiBlockController {
|
||||
|
||||
// Try to block a Menace attacker with two blockers, neither of which will die
|
||||
for (final Card attacker : attackersLeft) {
|
||||
if (!attacker.hasKeyword(Keyword.MENACE) && !attacker.hasStartOfKeyword("CantBeBlockedByAmount LT2")) {
|
||||
if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() <= 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -584,9 +592,7 @@ public class AiBlockController {
|
||||
List<Card> killingBlockers;
|
||||
|
||||
for (final Card attacker : attackersLeft) {
|
||||
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
||||
|| attacker.hasKeyword(Keyword.MENACE)
|
||||
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
||||
if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() > 1) {
|
||||
continue;
|
||||
}
|
||||
if (ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) {
|
||||
@@ -635,10 +641,8 @@ public class AiBlockController {
|
||||
|
||||
Card attacker = attackers.get(0);
|
||||
|
||||
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
||||
if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() > 1
|
||||
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|
||||
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")
|
||||
|| attacker.hasKeyword(Keyword.MENACE)
|
||||
|| ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) {
|
||||
attackers.remove(0);
|
||||
makeChumpBlocks(combat, attackers);
|
||||
@@ -686,9 +690,7 @@ public class AiBlockController {
|
||||
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
||||
|
||||
for (final Card attacker : currentAttackers) {
|
||||
if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
||||
&& !attacker.hasKeyword(Keyword.MENACE)
|
||||
&& !attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
||||
if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() <= 1) {
|
||||
continue;
|
||||
}
|
||||
List<Card> possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
||||
@@ -720,14 +722,14 @@ public class AiBlockController {
|
||||
List<Card> chumpBlockers;
|
||||
|
||||
List<Card> tramplingAttackers = CardLists.getKeyword(attackers, Keyword.TRAMPLE);
|
||||
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock));
|
||||
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock(combat)));
|
||||
|
||||
// TODO - should check here for a "rampage-like" trigger that replaced the keyword:
|
||||
// "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it."
|
||||
|
||||
for (final Card attacker : tramplingAttackers) {
|
||||
|
||||
if (((attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)) && !combat.isBlocked(attacker))
|
||||
if (((StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() > 1) && !combat.isBlocked(attacker))
|
||||
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|
||||
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
||||
continue;
|
||||
@@ -751,7 +753,7 @@ public class AiBlockController {
|
||||
private void reinforceBlockersToKill(final Combat combat) {
|
||||
List<Card> safeBlockers;
|
||||
List<Card> blockers;
|
||||
List<Card> targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock));
|
||||
List<Card> targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock(combat)));
|
||||
|
||||
// TODO - should check here for a "rampage-like" trigger that replaced
|
||||
// the keyword: "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it."
|
||||
@@ -1076,8 +1078,7 @@ public class AiBlockController {
|
||||
}
|
||||
|
||||
// assign blockers that have to block
|
||||
chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each turn if able.");
|
||||
chumpBlockers.addAll(CardLists.getKeyword(blockersLeft, "CARDNAME blocks each combat if able."));
|
||||
chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each combat if able.");
|
||||
// if an attacker with lure attacks - all that can block
|
||||
for (final Card blocker : blockersLeft) {
|
||||
if (CombatUtil.mustBlockAnAttacker(blocker, combat, null)) {
|
||||
@@ -1091,7 +1092,6 @@ public class AiBlockController {
|
||||
for (final Card blocker : blockers) {
|
||||
if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker)
|
||||
&& (CombatUtil.mustBlockAnAttacker(blocker, combat, null)
|
||||
|| blocker.hasKeyword("CARDNAME blocks each turn if able.")
|
||||
|| blocker.hasKeyword("CARDNAME blocks each combat if able."))) {
|
||||
combat.addBlocker(attacker, blocker);
|
||||
if (blocker.getMustBlockCards() != null) {
|
||||
|
||||
@@ -1654,10 +1654,11 @@ public class ComputerUtilCard {
|
||||
Card pumped = CardFactory.copyCard(c, false);
|
||||
pumped.setSickness(c.hasSickness());
|
||||
final long timestamp = c.getGame().getNextTimestamp();
|
||||
final List<String> kws = new ArrayList<>();
|
||||
final List<String> kws = Lists.newArrayList();
|
||||
final List<String> hiddenKws = Lists.newArrayList();
|
||||
for (String kw : keywords) {
|
||||
if (kw.startsWith("HIDDEN")) {
|
||||
pumped.addHiddenExtrinsicKeyword(kw);
|
||||
hiddenKws.add(kw.substring(7));
|
||||
} else {
|
||||
kws.add(kw);
|
||||
}
|
||||
@@ -1686,7 +1687,13 @@ public class ComputerUtilCard {
|
||||
pumped.addNewPT(c.getCurrentPower(), c.getCurrentToughness(), timestamp);
|
||||
pumped.setPTBoost(c.getPTBoostTable());
|
||||
pumped.addPTBoost(power + berserkPower, toughness, timestamp, 0);
|
||||
pumped.addChangedCardKeywords(kws, null, false, false, timestamp, 0);
|
||||
|
||||
if (!kws.isEmpty()) {
|
||||
pumped.addChangedCardKeywords(kws, null, false, false, timestamp, 0);
|
||||
}
|
||||
if (!hiddenKws.isEmpty()) {
|
||||
pumped.addHiddenExtrinsicKeywords(timestamp, 0, hiddenKws);
|
||||
}
|
||||
Set<CounterType> types = c.getCounters().keySet();
|
||||
for(CounterType ct : types) {
|
||||
pumped.addCounterFireNoEvents(ct, c.getCounters(ct), ai, sa, true, null);
|
||||
@@ -1699,14 +1706,10 @@ public class ComputerUtilCard {
|
||||
KeywordCollection copiedKeywords = new KeywordCollection();
|
||||
copiedKeywords.insertAll(pumped.getKeywords());
|
||||
List<KeywordInterface> toCopy = Lists.newArrayList();
|
||||
for (KeywordInterface k : c.getKeywords()) {
|
||||
for (KeywordInterface k : c.getUnhiddenKeywords()) {
|
||||
KeywordInterface copiedKI = k.copy(c, true);
|
||||
if (!copiedKeywords.contains(copiedKI.getOriginal())) {
|
||||
if (copiedKI.getHidden()) {
|
||||
pumped.addHiddenExtrinsicKeyword(copiedKI);
|
||||
} else {
|
||||
toCopy.add(copiedKI);
|
||||
}
|
||||
toCopy.add(copiedKI);
|
||||
}
|
||||
}
|
||||
final long timestamp2 = c.getGame().getNextTimestamp(); //is this necessary or can the timestamp be re-used?
|
||||
@@ -1744,7 +1747,7 @@ public class ComputerUtilCard {
|
||||
if (!stAb.hasParam("AddPower") && !stAb.hasParam("AddToughness")) {
|
||||
continue;
|
||||
}
|
||||
if (!vCard.isValid(stAb.getParam("Affected").split(","), c.getController(), c, stAb)) {
|
||||
if (!stAb.matchesValidParam("Affected", vCard)) {
|
||||
continue;
|
||||
}
|
||||
int att = 0;
|
||||
|
||||
@@ -110,7 +110,17 @@ public class ComputerUtilCombat {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (final KeywordInterface inst : attacker.getKeywords()) {
|
||||
// TODO replace with Static Ability
|
||||
for (final String keyword : attacker.getHiddenExtrinsicKeywords()) {
|
||||
if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
|
||||
final String defined = keyword.split(":")[1];
|
||||
final Player player = AbilityUtils.getDefinedPlayers(attacker, defined, null).get(0);
|
||||
if (!defender.equals(player)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (final KeywordInterface inst : attacker.getKeywords(Keyword.UNDEFINED)) {
|
||||
final String keyword = inst.getOriginal();
|
||||
if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
|
||||
final String defined = keyword.split(":")[1];
|
||||
|
||||
@@ -8,7 +8,6 @@ import forge.game.card.Card;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.cost.CostPayEnergy;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class CreatureEvaluator implements Function<Card, Integer> {
|
||||
@@ -35,16 +34,15 @@ public class CreatureEvaluator implements Function<Card, Integer> {
|
||||
}
|
||||
int power = getEffectivePower(c);
|
||||
final int toughness = getEffectiveToughness(c);
|
||||
for (KeywordInterface kw : c.getKeywords()) {
|
||||
String keyword = kw.getOriginal();
|
||||
if (keyword.equals("Prevent all combat damage that would be dealt by CARDNAME.")
|
||||
|| keyword.equals("Prevent all damage that would be dealt by CARDNAME.")
|
||||
|| keyword.equals("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
|
||||
|| keyword.equals("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
|
||||
power = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO replace with ReplacementEffect checks
|
||||
if (c.hasKeyword("Prevent all combat damage that would be dealt by CARDNAME.")
|
||||
|| c.hasKeyword("Prevent all damage that would be dealt by CARDNAME.")
|
||||
|| c.hasKeyword("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
|
||||
|| c.hasKeyword("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
|
||||
power = 0;
|
||||
}
|
||||
|
||||
if (considerPT) {
|
||||
value += addValue(power * 15, "power");
|
||||
value += addValue(toughness * 10, "toughness: " + toughness);
|
||||
@@ -157,8 +155,7 @@ public class CreatureEvaluator implements Function<Card, Integer> {
|
||||
}
|
||||
if (c.hasKeyword("CARDNAME can't block.")) {
|
||||
value -= subValue(10, "cant-block");
|
||||
} else if (c.hasKeyword("CARDNAME attacks each turn if able.")
|
||||
|| c.hasKeyword("CARDNAME attacks each combat if able.")) {
|
||||
} else if (c.hasKeyword("CARDNAME attacks each combat if able.")) {
|
||||
value -= subValue(10, "must-attack");
|
||||
} else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
|
||||
value -= subValue(10, "must-attack-player");
|
||||
|
||||
@@ -16,7 +16,6 @@ import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.AiBlockController;
|
||||
import forge.ai.AiCardMemory;
|
||||
import forge.ai.AiController;
|
||||
import forge.ai.AiProps;
|
||||
|
||||
@@ -24,7 +24,6 @@ import forge.game.card.CardFactory;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
@@ -295,8 +294,8 @@ public class GameCopier {
|
||||
newCard.setChangedCardNames(c.getChangedCardNames());
|
||||
|
||||
// TODO: Is this correct? Does it not duplicate keywords from enchantments and such?
|
||||
for (KeywordInterface kw : c.getHiddenExtrinsicKeywords())
|
||||
newCard.addHiddenExtrinsicKeyword(kw);
|
||||
//for (KeywordInterface kw : c.getHiddenExtrinsicKeywords())
|
||||
// newCard.addHiddenExtrinsicKeyword(kw);
|
||||
if (c.isTapped()) {
|
||||
newCard.setTapped(true);
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityPredicates;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityCantAttackBlock;
|
||||
import forge.game.staticability.StaticAbilityLayer;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.PlayerZone;
|
||||
@@ -682,17 +683,25 @@ public class GameAction {
|
||||
|
||||
// need to refresh ability text for affected cards
|
||||
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
||||
if (stAb.isSecondary() ||
|
||||
!stAb.getParam("Mode").equals("CantBlockBy") ||
|
||||
stAb.isSuppressed() || !stAb.checkConditions() ||
|
||||
!stAb.hasParam("ValidAttacker") ||
|
||||
(stAb.hasParam("ValidBlocker") && stAb.getParam("ValidBlocker").equals("Creature.Self"))) {
|
||||
if (stAb.isSuppressed() || !stAb.checkConditions()) {
|
||||
continue;
|
||||
}
|
||||
final Card host = stAb.getHostCard();
|
||||
for (Card creature : Iterables.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES)) {
|
||||
if (creature.isValid(stAb.getParam("ValidAttacker").split(","), host.getController(), host, stAb)) {
|
||||
creature.updateAbilityTextForView();
|
||||
|
||||
if (stAb.getParam("Mode").equals("CantBlockBy")) {
|
||||
if (!stAb.hasParam("ValidAttacker") || (stAb.hasParam("ValidBlocker") && stAb.getParam("ValidBlocker").equals("Creature.Self"))) {
|
||||
continue;
|
||||
}
|
||||
for (Card creature : Iterables.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES)) {
|
||||
if (stAb.matchesValidParam("ValidAttacker", creature)) {
|
||||
creature.updateAbilityTextForView();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (stAb.getParam("Mode").equals(StaticAbilityCantAttackBlock.MinMaxBlockerMode)) {
|
||||
for (Card creature : Iterables.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES)) {
|
||||
if (stAb.matchesValidParam("ValidCard", creature)) {
|
||||
creature.updateAbilityTextForView();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,18 +174,8 @@ public class StaticEffect {
|
||||
final CardCollectionView affectedCards = getAffectedCards();
|
||||
final List<Player> affectedPlayers = getAffectedPlayers();
|
||||
|
||||
boolean setPT = false;
|
||||
String[] addHiddenKeywords = null;
|
||||
boolean removeMayPlay = false;
|
||||
|
||||
if (hasParam("SetPower") || hasParam("SetToughness")) {
|
||||
setPT = true;
|
||||
}
|
||||
|
||||
if (hasParam("AddHiddenKeyword")) {
|
||||
addHiddenKeywords = getParam("AddHiddenKeyword").split(" & ");
|
||||
}
|
||||
|
||||
if (hasParam("MayPlay")) {
|
||||
removeMayPlay = true;
|
||||
}
|
||||
@@ -220,7 +210,7 @@ public class StaticEffect {
|
||||
affectedCard.removeChangedTextColorWord(getTimestamp(), ability.getId());
|
||||
|
||||
// remove set P/T
|
||||
if (setPT) {
|
||||
if (hasParam("SetPower") || hasParam("SetToughness")) {
|
||||
affectedCard.removeNewPT(getTimestamp());
|
||||
}
|
||||
|
||||
@@ -240,10 +230,8 @@ public class StaticEffect {
|
||||
affectedCard.removeCantHaveKeyword(getTimestamp());
|
||||
}
|
||||
|
||||
if (addHiddenKeywords != null) {
|
||||
for (final String k : addHiddenKeywords) {
|
||||
affectedCard.removeHiddenExtrinsicKeyword(k);
|
||||
}
|
||||
if (hasParam("AddHiddenKeyword")) {
|
||||
affectedCard.removeHiddenExtrinsicKeywords(timestamp, ability.getId());
|
||||
}
|
||||
|
||||
// remove abilities
|
||||
|
||||
@@ -154,7 +154,7 @@ public class AnimateAllEffect extends AnimateEffectBase {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
doUnanimate(c, sa, hiddenKeywords, timestamp);
|
||||
doUnanimate(c, timestamp);
|
||||
|
||||
game.fireEvent(new GameEventCardStatsChanged(c));
|
||||
}
|
||||
|
||||
@@ -103,8 +103,8 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
||||
c.addCantHaveKeyword(timestamp, Keyword.setValueOf(sa.getParam("CantHaveKeyword")));
|
||||
}
|
||||
|
||||
for (final String k : hiddenKeywords) {
|
||||
c.addHiddenExtrinsicKeyword(k);
|
||||
if (!hiddenKeywords.isEmpty()) {
|
||||
c.addHiddenExtrinsicKeywords(timestamp, 0, hiddenKeywords);
|
||||
}
|
||||
|
||||
c.addColor(colors, !sa.hasParam("OverwriteColors"), timestamp, false);
|
||||
@@ -157,7 +157,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
doUnanimate(c, sa, hiddenKeywords, timestamp);
|
||||
doUnanimate(c, timestamp);
|
||||
|
||||
c.removeChangedName(timestamp);
|
||||
c.updateStateForView();
|
||||
@@ -217,8 +217,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
||||
* @param timestamp
|
||||
* a long.
|
||||
*/
|
||||
static void doUnanimate(final Card c, SpellAbility sa,
|
||||
final List<String> hiddenKeywords, final long timestamp) {
|
||||
static void doUnanimate(final Card c, final long timestamp) {
|
||||
|
||||
c.removeNewPT(timestamp);
|
||||
|
||||
@@ -231,9 +230,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
||||
|
||||
c.removeCantHaveKeyword(timestamp);
|
||||
|
||||
for (final String k : hiddenKeywords) {
|
||||
c.removeHiddenExtrinsicKeyword(k);
|
||||
}
|
||||
c.removeHiddenExtrinsicKeywords(timestamp, 0);
|
||||
|
||||
// any other unanimate cleanup
|
||||
if (!c.isCreature()) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import forge.game.combat.Combat;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbilityCantAttackBlock;
|
||||
import forge.util.Localizer;
|
||||
|
||||
public class CamouflageEffect extends SpellAbilityEffect {
|
||||
@@ -36,7 +37,7 @@ public class CamouflageEffect extends SpellAbilityEffect {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (attacker.hasKeyword("CantBeBlockedByAmount GT1") && blockers.size() > 1) {
|
||||
if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, defender).getRight() < blockers.size()) {
|
||||
// If no more than one creature can block, order the player to choose one to block
|
||||
Card chosen = declarer.getController().chooseCardsForEffect(blockers, sa,
|
||||
Localizer.getInstance().getMessage("lblChooseBlockerForAttacker", attacker.toString()), 1, 1, false, null).get(0);
|
||||
|
||||
@@ -128,10 +128,11 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
final List<String> kws = Lists.newArrayList();
|
||||
final List<String> hiddenKws = Lists.newArrayList();
|
||||
if (null != keywords) {
|
||||
for (final String kw : keywords) {
|
||||
if (kw.startsWith("HIDDEN")) {
|
||||
tgtC.addHiddenExtrinsicKeyword(kw);
|
||||
hiddenKws.add(kw.substring(7));
|
||||
} else {
|
||||
kws.add(kw);
|
||||
}
|
||||
@@ -142,6 +143,9 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
||||
tgtC.addChangedCardKeywords(kws, Lists.newArrayList(), false, false, tStamp, 0);
|
||||
game.fireEvent(new GameEventCardStatsChanged(tgtC));
|
||||
}
|
||||
if (hiddenKws.isEmpty()) {
|
||||
tgtC.addHiddenExtrinsicKeywords(tStamp, 0, hiddenKws);
|
||||
}
|
||||
|
||||
if (remember && !source.isRemembered(tgtC)) {
|
||||
source.addRemembered(tgtC);
|
||||
@@ -191,14 +195,8 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (keywords.size() > 0) {
|
||||
for (String kw : keywords) {
|
||||
if (kw.startsWith("HIDDEN")) {
|
||||
tgtC.removeHiddenExtrinsicKeyword(kw);
|
||||
}
|
||||
}
|
||||
tgtC.removeChangedCardKeywords(tStamp, 0);
|
||||
}
|
||||
tgtC.removeHiddenExtrinsicKeywords(tStamp, 0);
|
||||
tgtC.removeChangedCardKeywords(tStamp, 0);
|
||||
}
|
||||
};
|
||||
game.getEndOfTurn().addUntil(untilKeywordEOT);
|
||||
|
||||
@@ -32,6 +32,7 @@ import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.player.PlayerController;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbilityAdapt;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerHandler;
|
||||
import forge.game.trigger.TriggerType;
|
||||
@@ -288,8 +289,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
|
||||
// Adapt need extra logic
|
||||
if (sa.hasParam("Adapt")) {
|
||||
if (!(gameCard.getCounters(CounterEnumType.P1P1) == 0
|
||||
|| gameCard.hasKeyword("CARDNAME adapts as though it had no +1/+1 counters"))) {
|
||||
if (!(gameCard.getCounters(CounterEnumType.P1P1) == 0 || StaticAbilityAdapt.anyWithAdapt(sa, gameCard))) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -362,8 +362,6 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
game.getTriggerHandler().runTrigger(TriggerType.BecomeRenowned, AbilityKey.mapFromCard(gameCard), false);
|
||||
}
|
||||
if (sa.hasParam("Adapt")) {
|
||||
// need to remove special keyword
|
||||
gameCard.removeHiddenExtrinsicKeyword("CARDNAME adapts as though it had no +1/+1 counters");
|
||||
game.getTriggerHandler().runTrigger(TriggerType.Adapt, AbilityKey.mapFromCard(gameCard), false);
|
||||
}
|
||||
} else {
|
||||
@@ -422,7 +420,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
List<String> keywords = Arrays.asList(sa.getParam("SharedKeywords").split(" & "));
|
||||
List<ZoneType> zones = ZoneType.listValueOf(sa.getParam("SharedKeywordsZone"));
|
||||
String[] restrictions = sa.hasParam("SharedRestrictions") ? sa.getParam("SharedRestrictions").split(",") : new String[]{"Card"};
|
||||
keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, card);
|
||||
keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, card, sa);
|
||||
for (String k : keywords) {
|
||||
resolvePerType(sa, placer, CounterType.getType(k), counterAmount, table);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import forge.card.MagicColor;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
@@ -72,17 +73,16 @@ public class DebuffEffect extends SpellAbilityEffect {
|
||||
final List<String> removedKW = Lists.newArrayList();
|
||||
if (tgtC.isInPlay() && (!sa.usesTargeting() || tgtC.canBeTargetedBy(sa))) {
|
||||
if (sa.hasParam("AllSuffixKeywords")) {
|
||||
String suffix = sa.getParam("AllSuffixKeywords");
|
||||
for (final KeywordInterface kw : tgtC.getKeywords()) {
|
||||
String keyword = kw.getOriginal();
|
||||
if (keyword.endsWith(suffix)) {
|
||||
kws.add(keyword);
|
||||
// this only for walk abilities, may to try better
|
||||
if (sa.getParam("AllSuffixKeywords").equals("walk")) {
|
||||
for (final KeywordInterface kw : tgtC.getKeywords(Keyword.LANDWALK)) {
|
||||
removedKW.add(kw.getOriginal());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// special for Protection:Card.<color>:Protection from <color>:*
|
||||
for (final KeywordInterface inst : tgtC.getKeywords()) {
|
||||
for (final KeywordInterface inst : tgtC.getUnhiddenKeywords()) {
|
||||
String keyword = inst.getOriginal();
|
||||
if (keyword.startsWith("Protection:")) {
|
||||
for (final String kw : kws) {
|
||||
|
||||
@@ -31,7 +31,7 @@ public class PumpAllEffect extends SpellAbilityEffect {
|
||||
|
||||
for (String kw : keywords) {
|
||||
if (kw.startsWith("HIDDEN")) {
|
||||
hiddenkws.add(kw);
|
||||
hiddenkws.add(kw.substring(7));
|
||||
} else {
|
||||
kws.add(kw);
|
||||
}
|
||||
@@ -57,13 +57,15 @@ public class PumpAllEffect extends SpellAbilityEffect {
|
||||
redrawPT = true;
|
||||
}
|
||||
|
||||
tgtC.addChangedCardKeywords(kws, null, false, false, timestamp, 0);
|
||||
if (!kws.isEmpty()) {
|
||||
tgtC.addChangedCardKeywords(kws, null, false, false, timestamp, 0);
|
||||
}
|
||||
if (redrawPT) {
|
||||
tgtC.updatePowerToughnessForView();
|
||||
}
|
||||
|
||||
for (String kw : hiddenkws) {
|
||||
tgtC.addHiddenExtrinsicKeyword(kw);
|
||||
if (!hiddenkws.isEmpty()) {
|
||||
tgtC.addHiddenExtrinsicKeywords(timestamp, 0, hiddenkws);
|
||||
}
|
||||
|
||||
if (sa.hasParam("RememberAllPumped")) {
|
||||
@@ -79,10 +81,8 @@ public class PumpAllEffect extends SpellAbilityEffect {
|
||||
public void run() {
|
||||
tgtC.removePTBoost(timestamp, 0);
|
||||
tgtC.removeChangedCardKeywords(timestamp, 0);
|
||||
tgtC.removeHiddenExtrinsicKeywords(timestamp, 0);
|
||||
|
||||
for (String kw : hiddenkws) {
|
||||
tgtC.removeHiddenExtrinsicKeyword(kw);
|
||||
}
|
||||
tgtC.updatePowerToughnessForView();
|
||||
|
||||
game.fireEvent(new GameEventCardStatsChanged(tgtC));
|
||||
@@ -156,7 +156,7 @@ public class PumpAllEffect extends SpellAbilityEffect {
|
||||
String[] restrictions = new String[] {"Card"};
|
||||
if (sa.hasParam("SharedRestrictions"))
|
||||
restrictions = sa.getParam("SharedRestrictions").split(",");
|
||||
keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, sa.getHostCard());
|
||||
keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, sa.getHostCard(), sa);
|
||||
}
|
||||
applyPumpAll(sa, list, a, d, keywords, affectedZones);
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.event.GameEventCardStatsChanged;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -50,11 +49,12 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
return;
|
||||
}
|
||||
final List<String> kws = Lists.newArrayList();
|
||||
final List<String> hiddenKws = Lists.newArrayList();
|
||||
|
||||
boolean redrawPT = false;
|
||||
for (String kw : keywords) {
|
||||
if (kw.startsWith("HIDDEN")) {
|
||||
gameCard.addHiddenExtrinsicKeyword(kw);
|
||||
hiddenKws.add(kw.substring(7));
|
||||
redrawPT |= kw.contains("CARDNAME's power and toughness are switched");
|
||||
} else {
|
||||
kws.add(kw);
|
||||
@@ -66,7 +66,12 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
redrawPT = true;
|
||||
}
|
||||
|
||||
gameCard.addChangedCardKeywords(kws, Lists.newArrayList(), false, false, timestamp, 0);
|
||||
if (!kws.isEmpty()) {
|
||||
gameCard.addChangedCardKeywords(kws, Lists.newArrayList(), false, false, timestamp, 0);
|
||||
}
|
||||
if (!hiddenKws.isEmpty()) {
|
||||
gameCard.addHiddenExtrinsicKeywords(timestamp, 0, hiddenKws);
|
||||
}
|
||||
if (redrawPT) {
|
||||
gameCard.updatePowerToughnessForView();
|
||||
}
|
||||
@@ -95,12 +100,7 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
updateText |= gameCard.removeCanBlockAdditional(timestamp);
|
||||
|
||||
if (keywords.size() > 0) {
|
||||
|
||||
for (String kw : keywords) {
|
||||
if (kw.startsWith("HIDDEN")) {
|
||||
gameCard.removeHiddenExtrinsicKeyword(kw);
|
||||
}
|
||||
}
|
||||
gameCard.removeHiddenExtrinsicKeywords(timestamp, 0);
|
||||
gameCard.removeChangedCardKeywords(timestamp, 0);
|
||||
}
|
||||
gameCard.updatePowerToughnessForView();
|
||||
@@ -244,7 +244,7 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
if (sa.hasParam("SharedKeywordsZone")) {
|
||||
List<ZoneType> zones = ZoneType.listValueOf(sa.getParam("SharedKeywordsZone"));
|
||||
String[] restrictions = sa.hasParam("SharedRestrictions") ? sa.getParam("SharedRestrictions").split(",") : new String[]{"Card"};
|
||||
keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, sa.getHostCard());
|
||||
keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, sa.getHostCard(), sa);
|
||||
}
|
||||
|
||||
List<GameEntity> tgts = Lists.newArrayList();
|
||||
@@ -290,9 +290,10 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
List<String> choice = Lists.newArrayList();
|
||||
List<String> total = Lists.newArrayList(keywords);
|
||||
if (sa.hasParam("NoRepetition")) {
|
||||
for (KeywordInterface inst : tgtCards.get(0).getKeywords()) {
|
||||
final String kws = inst.getOriginal();
|
||||
total.remove(kws);
|
||||
for (String kw : keywords) {
|
||||
if (tgtCards.get(0).hasKeyword(kw)) {
|
||||
total.remove(kw);
|
||||
}
|
||||
}
|
||||
}
|
||||
final int min = Math.min(total.size(), numkw);
|
||||
|
||||
@@ -55,6 +55,7 @@ import forge.game.replacement.ReplacementResult;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.*;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityCantAttackBlock;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.Zone;
|
||||
@@ -99,7 +100,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
|
||||
private CardDamageHistory damageHistory = new CardDamageHistory();
|
||||
// Hidden keywords won't be displayed on the card
|
||||
private final KeywordCollection hiddenExtrinsicKeyword = new KeywordCollection();
|
||||
// x=timestamp y=StaticAbility id
|
||||
private final Table<Long, Long, List<String>> hiddenExtrinsicKeywords = TreeBasedTable.create();
|
||||
|
||||
// cards attached or otherwise linked to this card
|
||||
private CardCollection hauntedBy, devouredCards, exploitedCards, delvedCards, convokedCards, imprintedCards, encodedCards;
|
||||
@@ -1867,7 +1869,37 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
// get the text that does not belong to a cards abilities (and is not really
|
||||
// there rules-wise)
|
||||
public final String getNonAbilityText() {
|
||||
return keywordsToText(getHiddenExtrinsicKeywords());
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final StringBuilder sbLong = new StringBuilder();
|
||||
|
||||
for (String keyword : getHiddenExtrinsicKeywords()) {
|
||||
if (keyword.startsWith("CantBeCounteredBy")) {
|
||||
final String[] p = keyword.split(":");
|
||||
sbLong.append(p[2]).append("\r\n");
|
||||
} else if (keyword.equals("Unblockable")) {
|
||||
sbLong.append(getName()).append(" can't be blocked.\r\n");
|
||||
} else if (keyword.equals("AllNonLegendaryCreatureNames")) {
|
||||
sbLong.append(getName()).append(" has all names of nonlegendary creature cards.\r\n");
|
||||
} else if (keyword.startsWith("IfReach")) {
|
||||
String[] k = keyword.split(":");
|
||||
sbLong.append(getName()).append(" can block ")
|
||||
.append(CardType.getPluralType(k[1]))
|
||||
.append(" as though it had reach.\r\n");
|
||||
} else {
|
||||
sbLong.append(keyword).append("\r\n");
|
||||
}
|
||||
}
|
||||
if (sb.length() > 0) {
|
||||
sb.append("\r\n");
|
||||
if (sbLong.length() > 0) {
|
||||
sb.append("\r\n");
|
||||
}
|
||||
}
|
||||
if (sbLong.length() > 0) {
|
||||
sbLong.append("\r\n");
|
||||
}
|
||||
sb.append(sbLong);
|
||||
return CardTranslation.translateMultipleDescriptionText(sb.toString(), getName());
|
||||
}
|
||||
|
||||
// convert a keyword list to the String that should be displayed in game
|
||||
@@ -2120,9 +2152,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
|| keyword.startsWith("Encore") || keyword.startsWith("Mutate") || keyword.startsWith("Dungeon")
|
||||
|| keyword.startsWith("Class") || keyword.startsWith("Saga")) {
|
||||
// keyword parsing takes care of adding a proper description
|
||||
} else if (keyword.startsWith("CantBeBlockedByAmount")) {
|
||||
sbLong.append(getName()).append(" can't be blocked ");
|
||||
sbLong.append(getTextForKwCantBeBlockedByAmount(keyword));
|
||||
} else if (keyword.equals("Unblockable")) {
|
||||
sbLong.append(getName()).append(" can't be blocked.\r\n");
|
||||
} else if (keyword.equals("AllNonLegendaryCreatureNames")) {
|
||||
@@ -2173,14 +2202,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
return CardTranslation.translateMultipleDescriptionText(sb.toString(), getName());
|
||||
}
|
||||
|
||||
private static String getTextForKwCantBeBlockedByAmount(final String keyword) {
|
||||
final String restriction = keyword.split(" ", 2)[1];
|
||||
final boolean isLT = "LT".equals(restriction.substring(0,2));
|
||||
final String byClause = isLT ? "except by " : "by more than ";
|
||||
final int cnt = Integer.parseInt(restriction.substring(2));
|
||||
return byClause + Lang.nounWithNumeral(cnt, isLT ? "or more creature" : "creature");
|
||||
}
|
||||
|
||||
// get the text of the abilities of a card
|
||||
public String getAbilityText() {
|
||||
return getAbilityText(currentState);
|
||||
@@ -2367,15 +2388,27 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
continue;
|
||||
}
|
||||
for (final StaticAbility stAb : ca.getStaticAbilities()) {
|
||||
if (stAb.isSecondary() ||
|
||||
!stAb.getParam("Mode").equals("CantBlockBy") ||
|
||||
stAb.isSuppressed() || !stAb.checkConditions() ||
|
||||
!stAb.hasParam("ValidAttacker") ||
|
||||
(stAb.hasParam("ValidBlocker") && stAb.getParam("ValidBlocker").equals("Creature.Self"))) {
|
||||
if (stAb.isSuppressed() || !stAb.checkConditions()) {
|
||||
continue;
|
||||
}
|
||||
final Card host = stAb.getHostCard();
|
||||
if (isValid(stAb.getParam("ValidAttacker").split(","), host.getController(), host, stAb)) {
|
||||
|
||||
boolean found = false;
|
||||
if (stAb.getParam("Mode").equals("CantBlockBy")) {
|
||||
if (!stAb.hasParam("ValidAttacker") || (stAb.hasParam("ValidBlocker") && stAb.getParam("ValidBlocker").equals("Creature.Self"))) {
|
||||
continue;
|
||||
}
|
||||
if (stAb.matchesValidParam("ValidAttacker", this)) {
|
||||
found = true;
|
||||
}
|
||||
} else if (stAb.getParam("Mode").equals(StaticAbilityCantAttackBlock.MinMaxBlockerMode)) {
|
||||
if (stAb.matchesValidParam("ValidCard", this)) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
final Card host = stAb.getHostCard();
|
||||
|
||||
String currentName = host.getName();
|
||||
String desc1 = TextUtil.fastReplace(stAb.toString(), "CARDNAME", currentName);
|
||||
String desc = TextUtil.fastReplace(desc1,"NICKNAME", currentName.split(",")[0]);
|
||||
@@ -4119,7 +4152,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
// of lists. Optimizes common operations such as hasKeyword().
|
||||
public final void visitKeywords(CardState state, Visitor<KeywordInterface> visitor) {
|
||||
visitUnhiddenKeywords(state, visitor);
|
||||
visitHiddenExtrinsicKeywords(visitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -4140,8 +4172,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
|
||||
// shortcut for hidden keywords
|
||||
if (this.hiddenExtrinsicKeyword.contains(keyword)) {
|
||||
return true;
|
||||
for (List<String> kw : this.hiddenExtrinsicKeywords.values()) {
|
||||
if (kw.contains(keyword)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
HasKeywordVisitor visitor = new HasKeywordVisitor(keyword, false);
|
||||
@@ -4418,41 +4452,33 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
|
||||
// Hidden Keywords will be returned without the indicator HIDDEN
|
||||
public final List<KeywordInterface> getHiddenExtrinsicKeywords() {
|
||||
ListKeywordVisitor visitor = new ListKeywordVisitor();
|
||||
visitHiddenExtrinsicKeywords(visitor);
|
||||
return visitor.getKeywords();
|
||||
}
|
||||
private void visitHiddenExtrinsicKeywords(Visitor<KeywordInterface> visitor) {
|
||||
for (KeywordInterface inst : hiddenExtrinsicKeyword.getValues()) {
|
||||
if (!visitor.visit(inst)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
public final Iterable<String> getHiddenExtrinsicKeywords() {
|
||||
return Iterables.concat(this.hiddenExtrinsicKeywords.values());
|
||||
}
|
||||
|
||||
public final void addHiddenExtrinsicKeyword(String s) {
|
||||
if (s.startsWith("HIDDEN")) {
|
||||
s = s.substring(7);
|
||||
}
|
||||
if (hiddenExtrinsicKeyword.add(s) != null) {
|
||||
view.updateNonAbilityText(this);
|
||||
updateKeywords();
|
||||
}
|
||||
public final void addHiddenExtrinsicKeywords(long timestamp, long staticId, Iterable<String> keywords) {
|
||||
// TODO if some keywords aren't removed anymore, then no need for extra Array List
|
||||
hiddenExtrinsicKeywords.put(timestamp, staticId, Lists.newArrayList(keywords));
|
||||
|
||||
view.updateNonAbilityText(this);
|
||||
updateKeywords();
|
||||
}
|
||||
|
||||
public final void addHiddenExtrinsicKeyword(KeywordInterface k) {
|
||||
if (hiddenExtrinsicKeyword.insert(k)) {
|
||||
public final void removeHiddenExtrinsicKeywords(long timestamp, long staticId) {
|
||||
if (hiddenExtrinsicKeywords.remove(timestamp, staticId) != null) {
|
||||
view.updateNonAbilityText(this);
|
||||
updateKeywords();
|
||||
}
|
||||
}
|
||||
|
||||
public final void removeHiddenExtrinsicKeyword(String s) {
|
||||
if (s.startsWith("HIDDEN")) {
|
||||
s = s.substring(7);
|
||||
boolean updated = false;
|
||||
for (List<String> list : hiddenExtrinsicKeywords.values()) {
|
||||
if (list.remove(s)) {
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
if (hiddenExtrinsicKeyword.remove(s)) {
|
||||
if (updated) {
|
||||
view.updateNonAbilityText(this);
|
||||
updateKeywords();
|
||||
}
|
||||
@@ -4710,6 +4736,12 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
return hasStartOfKeyword(keyword, currentState);
|
||||
}
|
||||
public final boolean hasStartOfKeyword(String keyword, CardState state) {
|
||||
for (String s : this.getHiddenExtrinsicKeywords()) {
|
||||
if (s.startsWith(keyword)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
HasKeywordVisitor visitor = new HasKeywordVisitor(keyword, true);
|
||||
visitKeywords(state, visitor);
|
||||
return visitor.getResult();
|
||||
@@ -4741,9 +4773,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
return getAmountOfKeyword(k, currentState);
|
||||
}
|
||||
public final int getAmountOfKeyword(final String k, CardState state) {
|
||||
int count = Iterables.frequency(this.getHiddenExtrinsicKeywords(), k);
|
||||
CountKeywordVisitor visitor = new CountKeywordVisitor(k);
|
||||
visitKeywords(state, visitor);
|
||||
return visitor.getCount();
|
||||
return count + visitor.getCount();
|
||||
}
|
||||
|
||||
public final int getAmountOfKeyword(final Keyword k) {
|
||||
@@ -5619,11 +5652,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
|
||||
final boolean colorlessDamage = damageSource && source.hasKeyword("Colorless Damage Source");
|
||||
|
||||
for (final KeywordInterface inst : getKeywords()) {
|
||||
for (final KeywordInterface inst : getKeywords(Keyword.PROTECTION)) {
|
||||
String kw = inst.getOriginal();
|
||||
if (!kw.startsWith("Protection")) {
|
||||
continue;
|
||||
}
|
||||
if (kw.equals("Protection from white")) {
|
||||
if (source.isWhite() && !colorlessDamage) {
|
||||
return true;
|
||||
@@ -5698,11 +5728,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
public String getProtectionKey() {
|
||||
String protectKey = "";
|
||||
boolean pR = false; boolean pG = false; boolean pB = false; boolean pU = false; boolean pW = false;
|
||||
for (final KeywordInterface inst : getKeywords()) {
|
||||
for (final KeywordInterface inst : getKeywords(Keyword.PROTECTION)) {
|
||||
String kw = inst.getOriginal();
|
||||
if (!kw.startsWith("Protection")) {
|
||||
continue;
|
||||
}
|
||||
if (kw.contains("Protection from red")) {
|
||||
if (!pR) {
|
||||
pR = true;
|
||||
@@ -5755,11 +5782,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
public String getHexproofKey() {
|
||||
String hexproofKey = "";
|
||||
boolean hR = false; boolean hG = false; boolean hB = false; boolean hU = false; boolean hW = false;
|
||||
for (final KeywordInterface inst : getKeywords()) {
|
||||
for (final KeywordInterface inst : getKeywords(Keyword.HEXPROOF)) {
|
||||
String kw = inst.getOriginal();
|
||||
if (!kw.startsWith("Hexproof")) {
|
||||
continue;
|
||||
}
|
||||
if (kw.equals("Hexproof")) {
|
||||
hexproofKey += "generic:";
|
||||
}
|
||||
@@ -5859,6 +5883,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if (sa.isSpell()) {
|
||||
// TODO replace with Static Ability
|
||||
for(KeywordInterface inst : source.getKeywords()) {
|
||||
String kw = inst.getOriginal();
|
||||
if(!kw.startsWith("SpellCantTarget")) {
|
||||
@@ -6506,23 +6531,16 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
private static final class CountKeywordVisitor extends Visitor<KeywordInterface> {
|
||||
private String keyword;
|
||||
private int count;
|
||||
private boolean startOf;
|
||||
|
||||
private CountKeywordVisitor(String keyword) {
|
||||
this.keyword = keyword;
|
||||
this.count = 0;
|
||||
this.startOf = false;
|
||||
}
|
||||
|
||||
private CountKeywordVisitor(String keyword, boolean startOf) {
|
||||
this(keyword);
|
||||
this.startOf = startOf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(KeywordInterface inst) {
|
||||
final String kw = inst.getOriginal();
|
||||
if ((startOf && kw.startsWith(keyword)) || kw.equals(keyword)) {
|
||||
if (kw.equals(keyword)) {
|
||||
count++;
|
||||
}
|
||||
return true;
|
||||
@@ -6551,7 +6569,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
return result.isFalse();
|
||||
}
|
||||
|
||||
public boolean getResult() {
|
||||
return result.isTrue();
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ import forge.card.ICardFace;
|
||||
import forge.card.MagicColor;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.card.mana.ManaCostParser;
|
||||
import forge.game.CardTraitBase;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntityCounterTable;
|
||||
import forge.game.GameLogEntryType;
|
||||
@@ -264,8 +265,7 @@ public class CardFactoryUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (KeywordInterface k : c.getKeywords()) {
|
||||
final String o = k.getOriginal();
|
||||
for (String o : c.getHiddenExtrinsicKeywords()) {
|
||||
if (o.startsWith("CantBeCounteredBy")) {
|
||||
final String[] m = o.split(":");
|
||||
if (sa.isValid(m[1].split(","), c.getController(), c, null)) {
|
||||
@@ -510,7 +510,7 @@ public class CardFactoryUtil {
|
||||
* @return a List<String>.
|
||||
*/
|
||||
public static List<String> sharedKeywords(final Iterable<String> kw, final String[] restrictions,
|
||||
final Iterable<ZoneType> zones, final Card host) {
|
||||
final Iterable<ZoneType> zones, final Card host, CardTraitBase ctb) {
|
||||
final List<String> filteredkw = Lists.newArrayList();
|
||||
final Player p = host.getController();
|
||||
CardCollectionView cardlist = p.getGame().getCardsIn(zones);
|
||||
@@ -521,7 +521,7 @@ public class CardFactoryUtil {
|
||||
final Set<String> tramplekw = Sets.newHashSet();
|
||||
final Set<String> allkw = Sets.newHashSet();
|
||||
|
||||
for (Card c : CardLists.getValidCards(cardlist, restrictions, p, host, null)) {
|
||||
for (Card c : CardLists.getValidCards(cardlist, restrictions, p, host, ctb)) {
|
||||
for (KeywordInterface inst : c.getKeywords()) {
|
||||
final String k = inst.getOriginal();
|
||||
if (k.endsWith("walk")) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.Comparator;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.card.CardStateName;
|
||||
import forge.game.CardTraitBase;
|
||||
@@ -31,6 +32,7 @@ import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.Zone;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.PredicateString;
|
||||
import forge.util.collect.FCollectionView;
|
||||
|
||||
|
||||
@@ -109,6 +111,10 @@ public final class CardPredicates {
|
||||
return new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (Iterables.any(c.getHiddenExtrinsicKeywords(), PredicateString.contains(keyword))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (KeywordInterface k : c.getKeywords()) {
|
||||
if (k.getOriginal().contains(keyword)) {
|
||||
return true;
|
||||
|
||||
@@ -52,18 +52,27 @@ public class AttackRequirement {
|
||||
nAttackAnything += attacker.getGoaded().size();
|
||||
}
|
||||
|
||||
// remove it when all of them are HIDDEN or static
|
||||
for (final KeywordInterface inst : attacker.getKeywords()) {
|
||||
final String keyword = inst.getOriginal();
|
||||
if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
|
||||
final String defined = keyword.split(":")[1];
|
||||
final GameEntity mustAttack2 = AbilityUtils.getDefinedPlayers(attacker, defined, null).get(0);
|
||||
defenderSpecific.add(mustAttack2);
|
||||
} else if (keyword.equals("CARDNAME attacks each combat if able.") ||
|
||||
(keyword.equals("CARDNAME attacks each turn if able.")
|
||||
&& !attacker.getDamageHistory().getCreatureAttackedThisTurn())) {
|
||||
} else if (keyword.equals("CARDNAME attacks each combat if able.")) {
|
||||
nAttackAnything++;
|
||||
}
|
||||
}
|
||||
for (final String keyword : attacker.getHiddenExtrinsicKeywords()) {
|
||||
if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
|
||||
final String defined = keyword.split(":")[1];
|
||||
final GameEntity mustAttack2 = AbilityUtils.getDefinedPlayers(attacker, defined, null).get(0);
|
||||
defenderSpecific.add(mustAttack2);
|
||||
} else if (keyword.equals("CARDNAME attacks each combat if able.")) {
|
||||
nAttackAnything++;
|
||||
}
|
||||
}
|
||||
|
||||
final GameEntity mustAttack3 = attacker.getMustAttackEntity();
|
||||
if (mustAttack3 != null) {
|
||||
defenderSpecific.add(mustAttack3);
|
||||
@@ -149,7 +158,7 @@ public class AttackRequirement {
|
||||
int violations = 0;
|
||||
|
||||
// first. check to see if "must attack X or Y with at least one creature" requirements are satisfied
|
||||
List<GameEntity> toRemoveFromDefSpecific = Lists.newArrayList();
|
||||
//List<GameEntity> toRemoveFromDefSpecific = Lists.newArrayList();
|
||||
if (!defenderOrPWSpecific.isEmpty()) {
|
||||
for (GameEntity def : defenderOrPWSpecific.keySet()) {
|
||||
if (defenderSpecificAlternatives.containsKey(def)) {
|
||||
|
||||
@@ -47,9 +47,9 @@ import forge.game.player.Player;
|
||||
import forge.game.player.PlayerController.ManaPaymentPurpose;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityCantAttackBlock;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Expressions;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.collect.FCollection;
|
||||
import forge.util.collect.FCollectionView;
|
||||
@@ -251,12 +251,9 @@ public class CombatUtil {
|
||||
}
|
||||
|
||||
// Keywords
|
||||
for (final KeywordInterface keyword : attacker.getKeywords()) {
|
||||
switch (keyword.getOriginal()) {
|
||||
case "CARDNAME can't attack.":
|
||||
case "CARDNAME can't attack or block.":
|
||||
return false;
|
||||
}
|
||||
// replace with Static Ability if able
|
||||
if (attacker.hasKeyword("CARDNAME can't attack.") || attacker.hasKeyword("CARDNAME can't attack or block.")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// CantAttack static abilities
|
||||
@@ -505,7 +502,7 @@ public class CombatUtil {
|
||||
}
|
||||
|
||||
if ( combat != null ) {
|
||||
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount GT") && !combat.getBlockers(attacker).isEmpty()) {
|
||||
if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, defendingPlayer).getRight() == combat.getBlockers(attacker).size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -587,7 +584,7 @@ public class CombatUtil {
|
||||
}
|
||||
}
|
||||
|
||||
for (final KeywordInterface inst : attacker.getKeywords()) {
|
||||
for (final KeywordInterface inst : attacker.getKeywords(Keyword.LANDWALK)) {
|
||||
String keyword = inst.getOriginal();
|
||||
if (keyword.equals("Legendary landwalk")) {
|
||||
walkTypes.add("Land.Legendary");
|
||||
@@ -762,11 +759,11 @@ public class CombatUtil {
|
||||
}
|
||||
|
||||
// "CARDNAME blocks each turn/combat if able."
|
||||
if (!blockers.contains(blocker) && (blocker.hasKeyword("CARDNAME blocks each turn if able.") || blocker.hasKeyword("CARDNAME blocks each combat if able."))) {
|
||||
if (!blockers.contains(blocker) && (blocker.hasKeyword("CARDNAME blocks each combat if able."))) {
|
||||
for (final Card attacker : attackers) {
|
||||
if (canBlock(attacker, blocker, combat)) {
|
||||
boolean must = true;
|
||||
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)) {
|
||||
if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, defending).getLeft() > 1) {
|
||||
final List<Card> possibleBlockers = Lists.newArrayList(defendersArmy);
|
||||
possibleBlockers.remove(blocker);
|
||||
if (!canBeBlocked(attacker, possibleBlockers, combat)) {
|
||||
@@ -774,8 +771,7 @@ public class CombatUtil {
|
||||
}
|
||||
}
|
||||
if (must) {
|
||||
String unit = blocker.hasKeyword("CARDNAME blocks each combat if able.") ? "combat," : "turn,";
|
||||
return TextUtil.concatWithSpace(blocker.toString(),"must block each", unit, "but was not assigned to block any attacker now.");
|
||||
return TextUtil.concatWithSpace(blocker.toString(),"must block each combat but was not assigned to block any attacker now.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -848,6 +844,7 @@ public class CombatUtil {
|
||||
&& combat.getBlockers(attacker).size() < 2)) {
|
||||
attackersWithLure.add(attacker);
|
||||
} else {
|
||||
// TODO replace with Hidden Keyword or Static Ability
|
||||
for (KeywordInterface inst : attacker.getKeywords()) {
|
||||
String keyword = inst.getOriginal();
|
||||
// MustBeBlockedBy <valid>
|
||||
@@ -875,8 +872,11 @@ public class CombatUtil {
|
||||
for (final Card attacker : attackersWithLure) {
|
||||
if (canBeBlocked(attacker, combat, defender) && canBlock(attacker, blocker)) {
|
||||
boolean canBe = true;
|
||||
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)) {
|
||||
final List<Card> blockers = combat.getDefenderPlayerByAttacker(attacker).getCreaturesInPlay();
|
||||
|
||||
Player defendingPlayer = combat.getDefenderPlayerByAttacker(attacker);
|
||||
|
||||
if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, defendingPlayer).getLeft() > 1) {
|
||||
final List<Card> blockers = defendingPlayer.getCreaturesInPlay();
|
||||
blockers.remove(blocker);
|
||||
if (!canBeBlocked(attacker, blockers, combat)) {
|
||||
canBe = false;
|
||||
@@ -893,8 +893,9 @@ public class CombatUtil {
|
||||
if (canBeBlocked(attacker, combat, defender) && canBlock(attacker, blocker)
|
||||
&& combat.isAttacking(attacker)) {
|
||||
boolean canBe = true;
|
||||
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)) {
|
||||
final List<Card> blockers = freeBlockers != null ? new CardCollection(freeBlockers) : combat.getDefenderPlayerByAttacker(attacker).getCreaturesInPlay();
|
||||
Player defendingPlayer = combat.getDefenderPlayerByAttacker(attacker);
|
||||
if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, defendingPlayer).getLeft() > 1) {
|
||||
final List<Card> blockers = freeBlockers != null ? new CardCollection(freeBlockers) : defendingPlayer.getCreaturesInPlay();
|
||||
blockers.remove(blocker);
|
||||
if (!canBeBlocked(attacker, blockers, combat)) {
|
||||
canBe = false;
|
||||
@@ -968,6 +969,7 @@ public class CombatUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO remove with HiddenKeyword or Static Ability
|
||||
boolean mustBeBlockedBy = false;
|
||||
for (KeywordInterface inst : attacker.getKeywords()) {
|
||||
String keyword = inst.getOriginal();
|
||||
@@ -1085,60 +1087,16 @@ public class CombatUtil {
|
||||
if (amount == 0)
|
||||
return false; // no block
|
||||
|
||||
List<String> restrictions = Lists.newArrayList();
|
||||
for (KeywordInterface inst : attacker.getKeywords()) {
|
||||
String kw = inst.getOriginal();
|
||||
if (kw.startsWith("CantBeBlockedByAmount")) {
|
||||
restrictions.add(TextUtil.split(kw, ' ', 2)[1]);
|
||||
}
|
||||
if (kw.equals("Menace")) {
|
||||
restrictions.add("LT2");
|
||||
}
|
||||
}
|
||||
for (String res : restrictions) {
|
||||
int operand = Integer.parseInt(res.substring(2));
|
||||
String operator = res.substring(0,2);
|
||||
if (Expressions.compare(amount, operator, operand) )
|
||||
return false;
|
||||
}
|
||||
if (defender != null && attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
||||
return amount >= defender.getCreaturesInPlay().size();
|
||||
Pair<Integer, Integer> minMaxBlock = StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, defender);
|
||||
|
||||
if (minMaxBlock.getLeft() > amount || minMaxBlock.getRight() < amount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static int getMinNumBlockersForAttacker(Card attacker, Player defender) {
|
||||
List<String> restrictions = Lists.newArrayList();
|
||||
for (KeywordInterface inst : attacker.getKeywords()) {
|
||||
String kw = inst.getOriginal();
|
||||
if (kw.startsWith("CantBeBlockedByAmount")) {
|
||||
restrictions.add(TextUtil.split(kw, ' ', 2)[1]);
|
||||
}
|
||||
if (kw.equals("Menace")) {
|
||||
restrictions.add("LT2");
|
||||
}
|
||||
}
|
||||
int minBlockers = 1;
|
||||
for (String res : restrictions) {
|
||||
int operand = Integer.parseInt(res.substring(2));
|
||||
String operator = res.substring(0, 2);
|
||||
if (operator.equals("LT") || operator.equals("GE")) {
|
||||
if (minBlockers < operand) {
|
||||
minBlockers = operand;
|
||||
}
|
||||
} else if (operator.equals("LE") || operator.equals("GT") || operator.equals("EQ")) {
|
||||
if (minBlockers < operand + 1) {
|
||||
minBlockers = operand + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
||||
if (defender != null) {
|
||||
minBlockers = defender.getCreaturesInPlay().size();
|
||||
}
|
||||
}
|
||||
|
||||
return minBlockers;
|
||||
return StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, defender).getLeft();
|
||||
}
|
||||
} // end class CombatUtil
|
||||
|
||||
@@ -284,12 +284,10 @@ public class CostAdjustment {
|
||||
|
||||
private static void adjustCostByOffering(final ManaCostBeingPaid cost, final SpellAbility sa) {
|
||||
String offeringType = "";
|
||||
for (KeywordInterface inst : sa.getHostCard().getKeywords()) {
|
||||
for (KeywordInterface inst : sa.getHostCard().getKeywords(Keyword.OFFERING)) {
|
||||
final String kw = inst.getOriginal();
|
||||
if (kw.endsWith(" offering")) {
|
||||
offeringType = kw.split(" ")[0];
|
||||
break;
|
||||
}
|
||||
offeringType = kw.split(" ")[0];
|
||||
break;
|
||||
}
|
||||
|
||||
Card toSac = null;
|
||||
|
||||
@@ -12,7 +12,6 @@ import forge.game.card.Card;
|
||||
|
||||
public class KeywordCollection implements Iterable<KeywordInterface> {
|
||||
|
||||
private boolean hidden = false;
|
||||
|
||||
private transient KeywordCollectionView view;
|
||||
// don't use enumKeys it causes a slow down
|
||||
@@ -21,11 +20,6 @@ public class KeywordCollection implements Iterable<KeywordInterface> {
|
||||
|
||||
public KeywordCollection() {
|
||||
super();
|
||||
this.hidden = false;
|
||||
}
|
||||
public KeywordCollection(boolean hidden) {
|
||||
super();
|
||||
this.hidden = hidden;
|
||||
}
|
||||
|
||||
public boolean contains(Keyword keyword) {
|
||||
@@ -50,7 +44,6 @@ public class KeywordCollection implements Iterable<KeywordInterface> {
|
||||
|
||||
public KeywordInterface add(String k) {
|
||||
KeywordInterface inst = Keyword.getInstance(k);
|
||||
inst.setHidden(hidden);
|
||||
if (insert(inst)) {
|
||||
return inst;
|
||||
}
|
||||
|
||||
@@ -23,8 +23,6 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
|
||||
private Keyword keyword;
|
||||
private String original;
|
||||
|
||||
private boolean hidden;
|
||||
|
||||
private List<Trigger> triggers = Lists.newArrayList();
|
||||
private List<ReplacementEffect> replacements = Lists.newArrayList();
|
||||
private List<SpellAbility> abilities = Lists.newArrayList();
|
||||
@@ -205,21 +203,6 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
|
||||
staticAbilities.add(st);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.game.keyword.KeywordInterface#getHidden()
|
||||
*/
|
||||
@Override
|
||||
public boolean getHidden() {
|
||||
return hidden;
|
||||
}
|
||||
/* (non-Javadoc)
|
||||
* @see forge.game.keyword.KeywordInterface#setHidden(boolean)
|
||||
*/
|
||||
@Override
|
||||
public void setHidden(boolean val) {
|
||||
hidden = val;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see forge.game.keyword.KeywordInterface#getTriggers()
|
||||
|
||||
@@ -19,9 +19,6 @@ public interface KeywordInterface extends Cloneable {
|
||||
|
||||
int getAmount();
|
||||
|
||||
boolean getHidden();
|
||||
void setHidden(boolean val);
|
||||
|
||||
void createTraits(final Card host, final boolean intrinsic);
|
||||
void createTraits(final Card host, final boolean intrinsic, final boolean clear);
|
||||
|
||||
|
||||
@@ -205,11 +205,12 @@ public class Untap extends Phase {
|
||||
}
|
||||
|
||||
// Remove temporary keywords
|
||||
// TODO Replace with Static Abilities
|
||||
for (final Card c : player.getCardsIn(ZoneType.Battlefield)) {
|
||||
c.removeHiddenExtrinsicKeyword("This card doesn't untap during your next untap step.");
|
||||
if (c.hasKeyword("This card doesn't untap during your next two untap steps.")) {
|
||||
c.removeHiddenExtrinsicKeyword("This card doesn't untap during your next two untap steps.");
|
||||
c.addHiddenExtrinsicKeyword("This card doesn't untap during your next untap step.");
|
||||
c.addHiddenExtrinsicKeywords(game.getNextTimestamp(), 0, Lists.newArrayList("This card doesn't untap during your next untap step."));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3014,7 +3014,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
game.getAction().checkStaticAbilities(false);
|
||||
|
||||
for (final Card c : getCardsIn(ZoneType.Sideboard)) {
|
||||
for (KeywordInterface inst : c.getKeywords()) {
|
||||
for (KeywordInterface inst : c.getKeywords(Keyword.COMPANION)) {
|
||||
if (!(inst instanceof Companion)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package forge.game.staticability;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class StaticAbilityAdapt {
|
||||
|
||||
static String MODE = "CanAdapt";
|
||||
|
||||
public static boolean anyWithAdapt(final SpellAbility sa, 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.getParam("Mode").equals(MODE) || stAb.isSuppressed() || !stAb.checkConditions()) {
|
||||
continue;
|
||||
}
|
||||
if (applyWithAdapt(stAb, sa, card)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static boolean applyWithAdapt(final StaticAbility stAb, final SpellAbility sa, final Card card) {
|
||||
if (!stAb.matchesValidParam("ValidCard", card)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stAb.matchesValidParam("ValidSA", sa)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -17,14 +17,19 @@
|
||||
*/
|
||||
package forge.game.staticability;
|
||||
|
||||
import org.apache.commons.lang3.tuple.MutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.zone.ZoneType;
|
||||
@@ -34,6 +39,8 @@ import forge.game.zone.ZoneType;
|
||||
*/
|
||||
public class StaticAbilityCantAttackBlock {
|
||||
|
||||
public static String MinMaxBlockerMode = "MinMaxBlocker";
|
||||
|
||||
/**
|
||||
* TODO Write javadoc for this method.
|
||||
*
|
||||
@@ -221,4 +228,43 @@ public class StaticAbilityCantAttackBlock {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Pair<Integer, Integer> getMinMaxBlocker(final Card attacker, final Player defender) {
|
||||
MutablePair<Integer, Integer> result = MutablePair.of(1, Integer.MAX_VALUE);
|
||||
|
||||
// Menace keyword
|
||||
if (attacker.hasKeyword(Keyword.MENACE)) {
|
||||
result.setLeft(2);
|
||||
}
|
||||
|
||||
final Game game = attacker.getGame();
|
||||
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
|
||||
for (final StaticAbility stAb : ca.getStaticAbilities()) {
|
||||
if (!stAb.getParam("Mode").equals(MinMaxBlockerMode) || stAb.isSuppressed() || !stAb.checkConditions()) {
|
||||
continue;
|
||||
}
|
||||
applyMinMaxBlockerAbility(stAb, attacker, defender, result);
|
||||
}
|
||||
}
|
||||
if (attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
||||
if (defender != null) {
|
||||
result.setLeft(defender.getCreaturesInPlay().size());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void applyMinMaxBlockerAbility(final StaticAbility stAb, final Card attacker, final Player defender, MutablePair<Integer, Integer> result) {
|
||||
if (!stAb.matchesValidParam("ValidCard", attacker)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stAb.hasParam("Min")) {
|
||||
result.setLeft(AbilityUtils.calculateAmount(stAb.getHostCard(), stAb.getParam("Min"), stAb));
|
||||
}
|
||||
|
||||
if (stAb.hasParam("Max")) {
|
||||
result.setRight(AbilityUtils.calculateAmount(stAb.getHostCard(), stAb.getParam("Max"), stAb));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,7 +326,7 @@ public final class StaticAbilityContinuous {
|
||||
if (params.containsKey("SharedKeywordsZone")) {
|
||||
List<ZoneType> zones = ZoneType.listValueOf(params.get("SharedKeywordsZone"));
|
||||
String[] restrictions = params.containsKey("SharedRestrictions") ? params.get("SharedRestrictions").split(",") : new String[] {"Card"};
|
||||
addKeywords = CardFactoryUtil.sharedKeywords(addKeywords, restrictions, zones, hostCard);
|
||||
addKeywords = CardFactoryUtil.sharedKeywords(addKeywords, restrictions, zones, hostCard, stAb);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -708,9 +708,7 @@ public final class StaticAbilityContinuous {
|
||||
|
||||
// add HIDDEN keywords
|
||||
if (!addHiddenKeywords.isEmpty()) {
|
||||
for (final String k : addHiddenKeywords) {
|
||||
affectedCard.addHiddenExtrinsicKeyword(k);
|
||||
}
|
||||
affectedCard.addHiddenExtrinsicKeywords(hostCard.getTimestamp(), stAb.getId(), addHiddenKeywords);
|
||||
}
|
||||
|
||||
// add SVars
|
||||
|
||||
@@ -3,6 +3,6 @@ ManaCost:1 G
|
||||
Types:Enchantment Aura
|
||||
K:Enchant creature
|
||||
A:SP$ Attach | Cost$ 1 G | ValidTgts$ Creature | AILogic$ Pump
|
||||
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Hexproof | AddHiddenKeyword$ CantBeBlockedByAmount GT1 | Description$ Enchanted creature has hexproof and can't be blocked by more than one creature.
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/alpha_authority.jpg
|
||||
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Hexproof | Description$ Enchanted creature has hexproof and can't be blocked by more than one creature.
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Creature.EnchantedBy | Max$ 1 | Secondary$ True | Description$ Enchanted creature can't be blocked by more than one creature.
|
||||
Oracle:Enchant creature\nEnchanted creature has hexproof and can't be blocked by more than one creature.
|
||||
|
||||
@@ -2,10 +2,9 @@ Name:Battlefront Krushok
|
||||
ManaCost:4 G
|
||||
Types:Creature Beast
|
||||
PT:3/4
|
||||
K:CantBeBlockedByAmount GT1
|
||||
S:Mode$ Continuous | Affected$ Creature.YouCtrl+counters_GE1_P1P1 | AddHiddenKeyword$ CantBeBlockedByAmount GT1 | Description$ Each creature you control with a +1/+1 counter on it can't be blocked by more than one creature.
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Creature.YouCtrl+counters_GE1_P1P1 | Max$ 1 | Description$ Each creature you control with a +1/+1 counter on it can't be blocked by more than one creature.
|
||||
SVar:NonStackingEffect:True
|
||||
SVar:PlayMain1:TRUE
|
||||
DeckHints:Ability$Counters
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/battlefront_krushok.jpg
|
||||
Oracle:Battlefront Krushok can't be blocked by more than one creature.\nEach creature you control with a +1/+1 counter on it can't be blocked by more than one creature.
|
||||
|
||||
@@ -3,6 +3,9 @@ ManaCost:G U
|
||||
Types:Creature Mutant
|
||||
PT:2/2
|
||||
S:Mode$ ReduceCost | ValidCard$ Creature.YouCtrl | Type$ Ability | Amount$ 2 | MinMana$ 1 | AffectedZone$ Battlefield | Description$ Activated abilities of creatures you control cost {2} less to activate. This effect can't reduce the mana in that cost to less than one mana.
|
||||
A:AB$ Pump | Cost$ T | ValidTgts$ Creature | KW$ HIDDEN CARDNAME adapts as though it had no +1/+1 counters | TgtPrompt$ Select target creature. | StackDescription$ SpellDescription | SpellDescription$ The next time target creature adapts this turn, it adapts as though it had no +1/+1 counters on it.
|
||||
A:AB$ Effect | Cost$ T | ValidTgts$ Creature | RememberObjects$ ThisTargetedCard | StaticAbilities$ StaticAllowAdapt | Triggers$ TriggerClearAdapt | TgtPrompt$ Select target creature. | StackDescription$ SpellDescription | SpellDescription$ The next time target creature adapts this turn, it adapts as though it had no +1/+1 counters on it.
|
||||
SVar:StaticAllowAdapt:Mode$ CanAdapt | ValidCard$ Card.IsRemembered | Description$ Remembered adapts as though it had no +1/+1 counters on it.
|
||||
SVar:TriggerClearAdapt:Mode$ Adapt | ValidCard$ Card.IsRemembered | Execute$ ExileSelf | Static$ True
|
||||
SVar:ExileSelf:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile
|
||||
DeckHints:Keyword$Adapt
|
||||
Oracle:Activated abilities of creatures you control cost {2} less to activate. This effect can't reduce the mana in that cost to less than one mana.\n{T}: The next time target creature adapts this turn, it adapts as though it had no +1/+1 counters on it.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Name:Bristling Boar
|
||||
ManaCost:3 G
|
||||
Types:Creature Boar
|
||||
K:CantBeBlockedByAmount GT1
|
||||
Oracle:Bristling Boar can't be blocked by more than one creature.
|
||||
PT:4/3
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
|
||||
Oracle:Bristling Boar can't be blocked by more than one creature.
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Challenger Troll
|
||||
ManaCost:4 G
|
||||
Types:Creature Troll
|
||||
PT:6/5
|
||||
S:Mode$ Continuous | Affected$ Creature.YouCtrl+powerGE4 | AddHiddenKeyword$ CantBeBlockedByAmount GT1 | Description$ Each creature you control with power 4 or greater can't be blocked by more than one creature.
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Creature.YouCtrl+powerGE4 | Max$ 1 | Description$ Each creature you control with power 4 or greater can't be blocked by more than one creature.
|
||||
SVar:PlayMain1:TRUE
|
||||
AI:RemoveDeck:Random
|
||||
Oracle:Each creature you control with power 4 or greater can't be blocked by more than one creature.
|
||||
@@ -2,6 +2,5 @@ Name:Charging Rhino
|
||||
ManaCost:3 G G
|
||||
Types:Creature Rhino
|
||||
PT:4/4
|
||||
K:CantBeBlockedByAmount GT1
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/charging_rhino.jpg
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
|
||||
Oracle:Charging Rhino can't be blocked by more than one creature.
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
Name:Familiar Ground
|
||||
ManaCost:2 G
|
||||
Types:Enchantment
|
||||
S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddHiddenKeyword$ CantBeBlockedByAmount GT1 | Description$ Each creature you control can't be blocked by more than one creature.
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Creature.YouCtrl | Max$ 1 | Description$ Each creature you control can't be blocked by more than one creature.
|
||||
SVar:NonStackingEffect:True
|
||||
SVar:PlayMain1:TRUE
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/familiar_ground.jpg
|
||||
Oracle:Each creature you control can't be blocked by more than one creature.
|
||||
|
||||
@@ -4,6 +4,5 @@ Types:Creature Ape Berserker
|
||||
PT:2/3
|
||||
K:Trample
|
||||
K:Rampage:2
|
||||
K:CantBeBlockedByAmount LT3
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/gorilla_berserkers.jpg
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Creature.Self | Min$ 3 | Description$ CARDNAME can't be blocked except by three or more creatures.
|
||||
Oracle:Trample; rampage 2 (Whenever this creature becomes blocked, it gets +2/+2 until end of turn for each creature blocking it beyond the first.)\nGorilla Berserkers can't be blocked except by three or more creatures.
|
||||
|
||||
@@ -2,11 +2,10 @@ Name:Guile
|
||||
ManaCost:3 U U U
|
||||
Types:Creature Elemental Incarnation
|
||||
PT:6/6
|
||||
K:CantBeBlockedByAmount LT3
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Creature.Self | Min$ 3 | Description$ CARDNAME can't be blocked except by three or more creatures.
|
||||
R:Event$ Counter | ActiveZones$ Battlefield | ValidType$ Spell | ValidCause$ Card.YouCtrl | ReplaceWith$ DBRemove | Description$ If a spell or ability you control would counter a spell, instead exile that spell and you may play that card without paying its mana cost.
|
||||
SVar:DBRemove:DB$ ChangeZone | Defined$ ReplacedCard | Origin$ Stack | Destination$ Exile | Fizzle$ True | SubAbility$ DBPlay
|
||||
SVar:DBPlay:DB$ Play | Defined$ ReplacedCard | WithoutManaCost$ True | Optional$ True
|
||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigShuffle | TriggerDescription$ When CARDNAME is put into a graveyard from anywhere, shuffle it into its owner's library.
|
||||
SVar:TrigShuffle:DB$ ChangeZone | Origin$ Graveyard | Destination$ Library | Shuffle$ True | Defined$ TriggeredCardLKICopy
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/guile.jpg
|
||||
Oracle:Guile can't be blocked except by three or more creatures.\nIf a spell or ability you control would counter a spell, instead exile that spell and you may play that card without paying its mana cost.\nWhen Guile is put into a graveyard from anywhere, shuffle it into its owner's library.
|
||||
|
||||
@@ -2,6 +2,5 @@ Name:Huang Zhong, Shu General
|
||||
ManaCost:2 W W
|
||||
Types:Legendary Creature Human Soldier
|
||||
PT:2/3
|
||||
K:CantBeBlockedByAmount GT1
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/huang_zhong_shu_general.jpg
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
|
||||
Oracle:Huang Zhong, Shu General can't be blocked by more than one creature.
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Hungering Hydra
|
||||
ManaCost:X G
|
||||
Types:Creature Hydra
|
||||
PT:0/0
|
||||
K:CantBeBlockedByAmount GT1
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
|
||||
K:etbCounter:P1P1:X
|
||||
SVar:X:Count$xPaid
|
||||
T:Mode$ DamageDoneOnce | ValidTarget$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever CARDNAME is dealt damage, put that many +1/+1 counters on CARDNAME.
|
||||
|
||||
@@ -2,6 +2,5 @@ Name:Ironhoof Ox
|
||||
ManaCost:3 G G
|
||||
Types:Creature Ox
|
||||
PT:4/4
|
||||
K:CantBeBlockedByAmount GT1
|
||||
SVar:Picture:http://resources.wizards.com/magic/cards/p2/en-us/card6628.jpg
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
|
||||
Oracle:Ironhoof Ox can't be blocked by more than one creature.
|
||||
|
||||
@@ -1,36 +1,18 @@
|
||||
Name:Kessig Prowler
|
||||
|
||||
ManaCost:G
|
||||
|
||||
Types:Creature Werewolf Horror
|
||||
|
||||
PT:2/1
|
||||
|
||||
A:AB$SetState | Cost$ 4 G | Defined$ Self | Mode$ Transform | SpellDescription$ Transform CARDNAME.
|
||||
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/kessig_prowler.jpg
|
||||
|
||||
AlternateMode:DoubleFaced
|
||||
|
||||
Oracle:{4}{G}: Transform Kessig Prowler.
|
||||
|
||||
|
||||
|
||||
ALTERNATE
|
||||
|
||||
|
||||
|
||||
Name:Sinuous Predator
|
||||
|
||||
ManaCost:no cost
|
||||
|
||||
Types:Creature Eldrazi Werewolf
|
||||
|
||||
PT:4/4
|
||||
|
||||
K:CantBeBlockedByAmount GT1
|
||||
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/sinuous_predator.jpg
|
||||
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
|
||||
Oracle:Sinuous Predator can't be blocked by more than one creature.
|
||||
|
||||
|
||||
@@ -3,6 +3,5 @@ ManaCost:3 G
|
||||
Types:Creature Cat Beast
|
||||
PT:3/2
|
||||
K:Provoke
|
||||
K:CantBeBlockedByAmount GT1
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/krosan_vorine.jpg
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
|
||||
Oracle:Provoke (Whenever this creature attacks, you may have target creature defending player controls untap and block it if able.)\nKrosan Vorine can't be blocked by more than one creature.
|
||||
|
||||
@@ -2,6 +2,5 @@ Name:Norwood Riders
|
||||
ManaCost:3 G
|
||||
Types:Creature Elf
|
||||
PT:3/3
|
||||
K:CantBeBlockedByAmount GT1
|
||||
SVar:Picture:http://resources.wizards.com/magic/cards/p2/en-us/card6615.jpg
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
|
||||
Oracle:Norwood Riders can't be blocked by more than one creature.
|
||||
|
||||
@@ -3,7 +3,6 @@ ManaCost:3 G G
|
||||
Types:Creature Giant
|
||||
PT:6/6
|
||||
K:Renown:6
|
||||
K:CantBeBlockedByAmount GT1
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
|
||||
DeckHas:Ability$Counters
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/outland_colossus.jpg
|
||||
Oracle:Renown 6 (When this creature deals combat damage to a player, if it isn't renowned, put six +1/+1 counters on it and it becomes renowned.)\nOutland Colossus can't be blocked by more than one creature.
|
||||
|
||||
@@ -3,6 +3,5 @@ ManaCost:11
|
||||
Types:Creature Eldrazi
|
||||
PT:9/9
|
||||
K:Annihilator:3
|
||||
K:CantBeBlockedByAmount LT3
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/pathrazer_of_ulamog.jpg
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Creature.Self | Min$ 3 | Description$ CARDNAME can't be blocked except by three or more creatures.
|
||||
Oracle:Annihilator 3 (Whenever this creature attacks, defending player sacrifices three permanents.)\nPathrazer of Ulamog can't be blocked except by three or more creatures.
|
||||
|
||||
@@ -4,7 +4,6 @@ Types:Artifact Creature Phyrexian Golem
|
||||
PT:8/8
|
||||
K:CARDNAME doesn't untap during your untap step.
|
||||
A:AB$ Untap | Cost$ PayLife<8> | SpellDescription$ Untap CARDNAME.
|
||||
K:CantBeBlockedByAmount LT3
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Creature.Self | Min$ 3 | Description$ CARDNAME can't be blocked except by three or more creatures.
|
||||
AI:RemoveDeck:All
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/phyrexian_colossus.jpg
|
||||
Oracle:Phyrexian Colossus doesn't untap during your untap step.\nPay 8 life: Untap Phyrexian Colossus.\nPhyrexian Colossus can't be blocked except by three or more creatures.
|
||||
|
||||
@@ -3,6 +3,6 @@ ManaCost:1 B/R B/R
|
||||
Types:Creature Human Warrior
|
||||
PT:2/2
|
||||
K:Menace
|
||||
S:Mode$ Continuous | Affected$ Creature.YouCtrl+withMenace | AddHiddenKeyword$ CantBeBlockedByAmount LT3 | Description$ Each creature you control with menace can't be blocked except by three or more creatures.
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Creature.YouCtrl+withMenace | Min$ 3 | Description$ Each creature you control with menace can't be blocked except by three or more creatures.
|
||||
SVar:PlayMain1:TRUE
|
||||
Oracle:Menace\nEach creature you control with menace can't be blocked except by three or more creatures.
|
||||
|
||||
@@ -2,6 +2,5 @@ Name:Stalking Tiger
|
||||
ManaCost:3 G
|
||||
Types:Creature Cat
|
||||
PT:3/3
|
||||
K:CantBeBlockedByAmount GT1
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/stalking_tiger.jpg
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
|
||||
Oracle:Stalking Tiger can't be blocked by more than one creature.
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Sunder Shaman
|
||||
ManaCost:R R G G
|
||||
Types:Creature Giant Shaman
|
||||
PT:5/5
|
||||
K:CantBeBlockedByAmount GT1
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
|
||||
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDestroy | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, destroy target artifact or enchantment that player controls.
|
||||
SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Artifact.ControlledBy TriggeredDefendingPlayer,Enchantment.ControlledBy TriggeredDefendingPlayer | TgtPrompt$ Select target artifact or enchantment that player controls.
|
||||
Oracle:Sunder Shaman can't be blocked by more than one creature.\nWhenever Sunder Shaman deals combat damage to a player, destroy target artifact or enchantment that player controls.
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Tahngarth, First Mate
|
||||
ManaCost:2 R G
|
||||
Types:Legendary Creature Minotaur Warrior
|
||||
PT:5/5
|
||||
K:CantBeBlockedByAmount GT1
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
|
||||
T:Mode$ AttackersDeclared | AttackingPlayer$ Player.Opponent | Execute$ TrigGainControl | TriggerZones$ Battlefield | OptionalDecider$ You | IsPresent$ Card.Self+tapped | TriggerDescription$ Whenever an opponent attacks with one or more creatures, if CARDNAME is tapped, you may have that opponent gain control of CARDNAME until end of combat. If you do, choose a player or planeswalker that opponent is attacking. CARDNAME is attacking that player or planeswalker.
|
||||
SVar:TrigGainControl:DB$ GainControl | Defined$ Self | NewController$ TriggeredAttackingPlayer | LoseControl$ EndOfCombat | Attacking$ Player.Defending | Chooser$ You | ChoosePlayerOrPlaneswalker$ True
|
||||
AI:RemoveDeck:All
|
||||
|
||||
@@ -3,7 +3,9 @@ ManaCost:3 R
|
||||
Types:Enchantment Saga
|
||||
K:Saga:3:DBGainControl,DBAllAttack,DBDamageTapped
|
||||
SVar:DBGainControl:DB$ GainControl | ValidTgts$ Creature | TgtPrompt$ Select target creature | LoseControl$ LeavesPlay | SpellDescription$ Gain control of target creature for as long as CARDNAME remains on the battlefield.
|
||||
SVar:DBAllAttack:DB$ PumpAll | ValidCards$ Creature.OppCtrl | Duration$ UntilYourNextTurn | KW$ HIDDEN CARDNAME attacks each combat if able. | SpellDescription$ Until your next turn, creatures your opponents control attack each turn if able.
|
||||
|
||||
# Refactor as Effect
|
||||
SVar:DBAllAttack:DB$ PumpAll | ValidCards$ Creature.OppCtrl | Duration$ UntilYourNextTurn | KW$ HIDDEN CARDNAME attacks each combat if able. | SpellDescription$ Until your next turn, creatures your opponents control attack each combat if able.
|
||||
SVar:DBDamageTapped:DB$ EachDamage | ValidCards$ Creature.tapped | NumDmg$ X | DamageDesc$ damage equal to its power | DefinedCards$ Self | SpellDescription$ Each tapped creature deals damage to itself equal to its power.
|
||||
SVar:X:Count$CardPower
|
||||
Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)\nI — Gain control of target creature for as long as The Akroan War remains on the battlefield.\nII — Until your next turn, creatures your opponents control attack each combat if able.\nIII — Each tapped creature deals damage to itself equal to its power.
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Underworld Cerberus
|
||||
ManaCost:3 B R
|
||||
Types:Creature Dog
|
||||
PT:6/6
|
||||
K:CantBeBlockedByAmount LT3
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Creature.Self | Min$ 3 | Description$ CARDNAME can't be blocked except by three or more creatures.
|
||||
S:Mode$ CantTarget | AffectedZone$ Graveyard | Description$ Cards in graveyards can't be the targets of spells or abilities.
|
||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigExile | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, exile it and each player returns all creature cards from their graveyard to their hand.
|
||||
SVar:TrigExile:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | Defined$ TriggeredNewCardLKICopy | SubAbility$ DBChangeZoneAll
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Vigorspore Wurm
|
||||
ManaCost:5 G
|
||||
Types:Creature Wurm
|
||||
PT:6/4
|
||||
K:CantBeBlockedByAmount GT1
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
|
||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPump | TriggerDescription$ Undergrowth - When CARDNAME enters the battlefield, target creature gains vigilance and gets +X/+X until end of turn, where X is the number of creature cards in your graveyard.
|
||||
SVar:TrigPump:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ X | NumDef$ X | KW$ Vigilance
|
||||
SVar:X:Count$TypeInYourYard.Creature
|
||||
|
||||
@@ -2,6 +2,6 @@ Name:Vorrac Battlehorns
|
||||
ManaCost:2
|
||||
Types:Artifact Equipment
|
||||
K:Equip:1
|
||||
S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddKeyword$ Trample | AddHiddenKeyword$ CantBeBlockedByAmount GT1 | Description$ Equipped creature has trample and can't be blocked by more than one creature.
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/vorrac_battlehorns.jpg
|
||||
S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddKeyword$ Trample | Description$ Equipped creature has trample and can't be blocked by more than one creature.
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Creature.EquippedBy | Max$ 1 | Secondary$ True | Description$ Equipped creature can't be blocked by more than one creature.
|
||||
Oracle:Equipped creature has trample and can't be blocked by more than one creature.\nEquip {1} ({1}: Attach to target creature you control. Equip only as a sorcery. This card enters the battlefield unattached and stays on the battlefield if the creature leaves.)
|
||||
|
||||
@@ -6,6 +6,7 @@ SVar:TrigToken:DB$ Token | LegacyImage$ g 2 2 wolf m20 | TokenAmount$ 1 | TokenS
|
||||
SVar:DBAttach:DB$ Attach | Defined$ Remembered | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
DeckHas:Ability$Token
|
||||
S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 1 | AddToughness$ 1 | AddHiddenKeyword$ CantBeBlockedByAmount GT1 | Description$ Equipped creature gets +1/+1 and can't be blocked by more than one creature.
|
||||
S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 1 | AddToughness$ 1 | Description$ Equipped creature gets +1/+1 and can't be blocked by more than one creature.
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Creature.EquippedBy | Max$ 1 | Secondary$ True | Description$ Equipped creature can't be blocked by more than one creature.
|
||||
K:Equip:3
|
||||
Oracle:When Wolfrider's Saddle enters the battlefield, create a 2/2 green Wolf creature token, then attach Wolfrider's Saddle to it.\nEquipped creature gets +1/+1 and can't be blocked by more than one creature.\nEquip {3} ({3}: Attach to target creature you control. Equip only as a sorcery.)
|
||||
|
||||
@@ -3,6 +3,5 @@ ManaCost:4 R
|
||||
Types:Legendary Creature Human Soldier
|
||||
PT:2/3
|
||||
K:Horsemanship
|
||||
S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddHiddenKeyword$ CantBeBlockedByAmount GT1 | Description$ Each creature you control can't be blocked by more than one creature.
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/yuan_shao_the_indecisive.jpg
|
||||
S:Mode$ MinMaxBlocker | ValidCard$ Creature.YouCtrl | Max$ 1 | Description$ Each creature you control can't be blocked by more than one creature.
|
||||
Oracle:Horsemanship (This creature can't be blocked except by creatures with horsemanship.)\nEach creature you control can't be blocked by more than one creature.
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
All creatures able to block CARDNAME do so.
|
||||
Banding
|
||||
CantBeBlockedByAmount LT2
|
||||
CantBeBlockedByAmount LT3
|
||||
CARDNAME's activated abilities can't be activated.
|
||||
CARDNAME attacks each turn if able.
|
||||
CARDNAME attacks each combat if able.
|
||||
CARDNAME blocks each turn if able.
|
||||
CARDNAME blocks each combat if able.
|
||||
CARDNAME can attack as though it didn't have defender.
|
||||
CARDNAME can block any number of creatures.
|
||||
|
||||
Reference in New Issue
Block a user