diff --git a/docs/Card-scripting-API/Card-scripting-API.md b/docs/Card-scripting-API/Card-scripting-API.md index a928a3455b9..9ecf075abe5 100644 --- a/docs/Card-scripting-API/Card-scripting-API.md +++ b/docs/Card-scripting-API/Card-scripting-API.md @@ -144,7 +144,6 @@ CARDNAME is replaced by the card's name ingame. - CARDNAME must be blocked if able. - Remove CARDNAME from your deck before playing if you're not playing for ante. - You may choose not to untap CARDNAME during your untap step. -- CantSearchLibrary # General SVars * `SoundEffect:` diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java index 93d326957c9..0f19cba9638 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java @@ -20,6 +20,7 @@ import forge.game.replacement.ReplacementType; import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityStackInstance; +import forge.game.staticability.StaticAbilitySearchLibrary; import forge.game.trigger.TriggerType; import forge.game.zone.Zone; import forge.game.zone.ZoneType; @@ -1015,9 +1016,9 @@ else if (!origin.contains(ZoneType.Library) && !origin.contains(ZoneType.Hand) if (origin.contains(ZoneType.Library) && !sa.hasParam("NoLooking")) { searchedLibrary = true; - if (decider.hasKeyword("LimitSearchLibrary")) { // Aven Mindcensor + Integer fetchNum = StaticAbilitySearchLibrary.limitSearchLibraryConsideringSize(decider); + if (fetchNum != null) { fetchList.removeAll(player.getCardsIn(ZoneType.Library)); - final int fetchNum = Math.min(player.getCardsIn(ZoneType.Library).size(), 4); if (fetchNum == 0) { searchedLibrary = false; } else { @@ -1040,9 +1041,9 @@ else if (!origin.contains(ZoneType.Library) && !origin.contains(ZoneType.Hand) Set revealZones = Sets.newHashSet(); Iterable toReveal = null; if (origin.contains(ZoneType.Library) && searchedLibrary) { - final int fetchNum = Math.min(player.getCardsIn(ZoneType.Library).size(), 4); + Integer fetchNum = StaticAbilitySearchLibrary.limitSearchLibraryConsideringSize(decider); // Look at whole library before moving onto choosing a card - toReveal = !decider.hasKeyword("LimitSearchLibrary") ? player.getCardsIn(ZoneType.Library) : player.getCardsIn(ZoneType.Library, fetchNum); + toReveal = fetchNum != null ? player.getCardsIn(ZoneType.Library, fetchNum) : player.getCardsIn(ZoneType.Library); revealZones.add(ZoneType.Library); } if (origin.contains(ZoneType.Hand) && player.isOpponentOf(decider)) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java index 2123ce26be7..4a884855a32 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java @@ -20,6 +20,7 @@ import forge.game.player.PlayerActionConfirmMode; import forge.game.player.PlayerCollection; import forge.game.spellability.SpellAbility; +import forge.game.staticability.StaticAbilitySearchLibrary; import forge.game.zone.ZoneType; import forge.util.Aggregates; import forge.util.Lang; @@ -251,9 +252,8 @@ public void resolve(SpellAbility sa) { } final Player searched = AbilityUtils.getDefinedPlayers(host, sa.getParam("QuasiLibrarySearch"), sa).get(0); - final int fetchNum = Math.min(searched.getCardsIn(ZoneType.Library).size(), 4); - CardCollectionView shown = !p.hasKeyword("LimitSearchLibrary") - ? searched.getCardsIn(ZoneType.Library) : searched.getCardsIn(ZoneType.Library, fetchNum); + final Integer fetchNum = StaticAbilitySearchLibrary.limitSearchLibraryConsideringSize(p); + final CardCollectionView shown = fetchNum != null ? searched.getCardsIn(ZoneType.Library, fetchNum) : searched.getCardsIn(ZoneType.Library); DelayedReveal delayedReveal = new DelayedReveal(shown, ZoneType.Library, PlayerView.get(searched), host.getTranslatedName() + " - " + Localizer.getInstance().getMessage("lblLookingCardIn") + " "); diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index a56ddae9e7c..a13d530e82b 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -3723,11 +3723,11 @@ public boolean canSearchLibraryWith(SpellAbility sa, Player targetPlayer) { return true; } - if (hasKeyword("CantSearchLibrary")) { + if (StaticAbilitySearchLibrary.cantSearchLibrary(this)) { return false; } return targetPlayer == null || !targetPlayer.equals(sa.getActivatingPlayer()) - || !hasKeyword("Spells and abilities you control can't cause you to search your library."); + || !StaticAbilitySearchLibrary.cantCauseToSearchLibrary(this); } public void addAdditionalVote(long timestamp, int value) { diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityMode.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityMode.java index 6795defc3d8..f61859e2c98 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityMode.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityMode.java @@ -200,6 +200,11 @@ public enum StaticAbilityMode { // StaticAbilityCountersRemain CountersRemain, + + // StaticAbilityCantSearchLibrary + LimitSearchLibrary, + CantSearchLibrary, + CantCauseToSearchLibrary ; public static StaticAbilityMode smartValueOf(final String value) { diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilitySearchLibrary.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilitySearchLibrary.java new file mode 100644 index 00000000000..17e533a1c0d --- /dev/null +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilitySearchLibrary.java @@ -0,0 +1,54 @@ +package forge.game.staticability; + +import forge.game.player.Player; +import forge.game.zone.ZoneType; + +import java.util.Optional; + +import static forge.game.staticability.StaticAbilityMode.CantSearchLibrary; +import static forge.game.staticability.StaticAbilityMode.LimitSearchLibrary; +import static forge.game.staticability.StaticAbilityMode.CantCauseToSearchLibrary; + + +public class StaticAbilitySearchLibrary { + + /** + * @return maximum number of cards which can be fetched from a library considering its size and search limit or null if there is no limit + */ + public static Integer limitSearchLibraryConsideringSize(Player player) { + Integer limit = limitSearchLibrary(player); + if (limit != null) { + return Math.min(player.getCardsIn(ZoneType.Library).size(), limit); + } else { + return null; + } + } + + /** + * @return maximum number of cards which can be revealed from a library or null if there is no limit + */ + public static Integer limitSearchLibrary(Player player) { + return findStaticAbilityForValidPlayer(player, LimitSearchLibrary) + .map(stAb -> Integer.valueOf(stAb.getParam("LimitNum"))) + .orElse(null); + } + + public static boolean cantSearchLibrary(Player player) { + return findStaticAbilityForValidPlayer(player, CantSearchLibrary) + .filter(stAb -> !stAb.getIgnoreEffectPlayers().contains(player)) + .isPresent(); + } + + public static boolean cantCauseToSearchLibrary(Player player) { + return findStaticAbilityForValidPlayer(player, CantCauseToSearchLibrary).isPresent(); + } + + private static Optional findStaticAbilityForValidPlayer(final Player player, final StaticAbilityMode mode) { + return player.getGame() + .getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES) + .stream() + .flatMap(card -> card.getStaticAbilities().stream()) + .filter(stAb -> stAb.checkConditions(mode) && stAb.matchesValidParam("ValidPlayer", player)) + .findAny(); + } +} diff --git a/forge-gui/res/cardsfolder/a/ashiok_dream_render.txt b/forge-gui/res/cardsfolder/a/ashiok_dream_render.txt index 3bbe3070aea..a4894bcd49a 100644 --- a/forge-gui/res/cardsfolder/a/ashiok_dream_render.txt +++ b/forge-gui/res/cardsfolder/a/ashiok_dream_render.txt @@ -2,7 +2,7 @@ Name:Ashiok, Dream Render ManaCost:1 UB UB Types:Legendary Planeswalker Ashiok Loyalty:5 -S:Mode$ Continuous | Affected$ Opponent | AddKeyword$ Spells and abilities you control can't cause you to search your library. | Description$ Spells and abilities your opponents control can't cause their controller to search their library. +S:Mode$ CantCauseToSearchLibrary | ValidPlayer$ Opponent | Description$ Spells and abilities your opponents control can't cause their controller to search their library. A:AB$ Mill | Cost$ SubCounter<1/LOYALTY> | Planeswalker$ True | NumCards$ 4 | ValidTgts$ Player | SubAbility$ DBExileGrave | SpellDescription$ Target player mills four cards. Then exile each opponent's graveyard. SVar:DBExileGrave:DB$ ChangeZoneAll | Origin$ Graveyard | Destination$ Exile | Defined$ Opponent | ChangeType$ Card Oracle:Spells and abilities your opponents control can't cause their controller to search their library.\n[-1]: Target player mills four cards. Then exile each opponent's graveyard. diff --git a/forge-gui/res/cardsfolder/a/aven_mindcensor.txt b/forge-gui/res/cardsfolder/a/aven_mindcensor.txt index 312becfc3af..1937e52c62f 100644 --- a/forge-gui/res/cardsfolder/a/aven_mindcensor.txt +++ b/forge-gui/res/cardsfolder/a/aven_mindcensor.txt @@ -4,5 +4,5 @@ Types:Creature Bird Wizard PT:2/1 K:Flash K:Flying -S:Mode$ Continuous | Affected$ Player.Opponent | AddKeyword$ LimitSearchLibrary | Description$ If an opponent would search a library, that player searches the top four cards of that library instead. +S:Mode$ LimitSearchLibrary | ValidPlayer$ Player.Opponent | LimitNum$ 4 | Description$ If an opponent would search a library, that player searches the top four cards of that library instead. Oracle:Flash\nFlying\nIf an opponent would search a library, that player searches the top four cards of that library instead. diff --git a/forge-gui/res/cardsfolder/l/leonin_arbiter.txt b/forge-gui/res/cardsfolder/l/leonin_arbiter.txt index f8c570beeeb..8dbd4f730c7 100644 --- a/forge-gui/res/cardsfolder/l/leonin_arbiter.txt +++ b/forge-gui/res/cardsfolder/l/leonin_arbiter.txt @@ -2,7 +2,7 @@ Name:Leonin Arbiter ManaCost:1 W Types:Creature Cat Cleric PT:2/2 -S:Mode$ Continuous | Affected$ Player | AddKeyword$ CantSearchLibrary | IgnoreEffectCost$ 2 | Description$ Players can't search libraries. Any player may pay {2} for that player to ignore this effect until end of turn. +S:Mode$ Continuous,CantSearchLibrary | ValidPlayer$ Player | Affected$ Player | IgnoreEffectCost$ 2 | Description$ Players can't search libraries. Any player may pay {2} for that player to ignore this effect until end of turn. # TODO: The AI won't activate the effect yet, but then again, it won't activate it even if the human is playing with this card, so it doesn't affect specifically the AI playability of this card. AI:RemoveDeck:Random Oracle:Players can't search libraries. Any player may pay {2} for that player to ignore this effect until end of turn. diff --git a/forge-gui/res/cardsfolder/m/mindlock_orb.txt b/forge-gui/res/cardsfolder/m/mindlock_orb.txt index 8ae30bac01b..d1371fa4afd 100644 --- a/forge-gui/res/cardsfolder/m/mindlock_orb.txt +++ b/forge-gui/res/cardsfolder/m/mindlock_orb.txt @@ -1,5 +1,5 @@ Name:Mindlock Orb ManaCost:3 U Types:Artifact -S:Mode$ Continuous | Affected$ Player | AddKeyword$ CantSearchLibrary | Description$ Players can't search libraries. +S:Mode$ CantSearchLibrary | ValidPlayer$ Player | Description$ Players can't search libraries. Oracle:Players can't search libraries. diff --git a/forge-gui/res/cardsfolder/s/shadow_of_doubt.txt b/forge-gui/res/cardsfolder/s/shadow_of_doubt.txt index a8bf9cf9516..2dcd5039c5c 100644 --- a/forge-gui/res/cardsfolder/s/shadow_of_doubt.txt +++ b/forge-gui/res/cardsfolder/s/shadow_of_doubt.txt @@ -2,7 +2,7 @@ Name:Shadow of Doubt ManaCost:UB UB Types:Instant A:SP$ Effect | StaticAbilities$ STCantSearch | SubAbility$ DBDraw | SpellDescription$ Players can't search libraries this turn. Draw a card. -SVar:STCantSearch:Mode$ Continuous | Affected$ Player | AddKeyword$ CantSearchLibrary | Description$ Players can't search libraries. +SVar:STCantSearch:Mode$ CantSearchLibrary | ValidPlayer$ Player | Description$ Players can't search libraries. SVar:DBDraw:DB$ Draw AI:RemoveDeck:All Oracle:({U/B} can be paid with either {U} or {B}.)\nPlayers can't search libraries this turn.\nDraw a card. diff --git a/forge-gui/res/cardsfolder/s/stranglehold.txt b/forge-gui/res/cardsfolder/s/stranglehold.txt index e40280c030d..b8fe6633780 100644 --- a/forge-gui/res/cardsfolder/s/stranglehold.txt +++ b/forge-gui/res/cardsfolder/s/stranglehold.txt @@ -1,7 +1,7 @@ Name:Stranglehold ManaCost:3 R Types:Enchantment -S:Mode$ Continuous | Affected$ Opponent | AddKeyword$ CantSearchLibrary | Description$ Your opponents can't search libraries. +S:Mode$ CantSearchLibrary | ValidPlayer$ Opponent | Description$ Your opponents can't search libraries. R:Event$ BeginTurn | ActiveZones$ Battlefield | ValidPlayer$ Opponent | ExtraTurn$ True | Skip$ True | Description$ If an opponent would begin an extra turn, that player skips that turn instead. SVar:NonStackingEffect:True AI:RemoveDeck:Random