Card: refactor hidden keywords into Table Structure

This commit is contained in:
Hans Mackowiak
2021-09-12 15:48:34 +00:00
committed by Michael Kamensky
parent 83a7cc8a3e
commit bfc938be57
60 changed files with 424 additions and 420 deletions

View File

@@ -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();
}
}
}
}

View File

@@ -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

View File

@@ -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));
}

View File

@@ -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()) {

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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")) {

View File

@@ -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;

View File

@@ -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)) {

View File

@@ -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

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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()

View File

@@ -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);

View File

@@ -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."));
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}
}

View File

@@ -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