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
@@ -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()
|
||||
|
||||
@@ -18,10 +18,7 @@ public interface KeywordInterface extends Cloneable {
|
||||
String getReminderText();
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user