Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion docs/Card-scripting-API/Card-scripting-API.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:<file.mp3>`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.staticability.StaticAbilityCantSearchLibrary;
import forge.game.trigger.TriggerType;
import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
Expand Down Expand Up @@ -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 = StaticAbilityCantSearchLibrary.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 {
Expand All @@ -1040,9 +1041,9 @@ else if (!origin.contains(ZoneType.Library) && !origin.contains(ZoneType.Hand)
Set<ZoneType> revealZones = Sets.newHashSet();
Iterable<Card> toReveal = null;
if (origin.contains(ZoneType.Library) && searchedLibrary) {
final int fetchNum = Math.min(player.getCardsIn(ZoneType.Library).size(), 4);
Integer fetchNum = StaticAbilityCantSearchLibrary.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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.PlayerCollection;
import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbilityCantSearchLibrary;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.Lang;
Expand Down Expand Up @@ -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 = StaticAbilityCantSearchLibrary.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") + " ");
Expand Down
4 changes: 2 additions & 2 deletions forge-game/src/main/java/forge/game/player/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -3723,11 +3723,11 @@ public boolean canSearchLibraryWith(SpellAbility sa, Player targetPlayer) {
return true;
}

if (hasKeyword("CantSearchLibrary")) {
if (StaticAbilityCantSearchLibrary.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.");
|| !StaticAbilityCantSearchLibrary.cantCauseToSearchLibrary(this);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should just use the same method above so it can be more dynamic

Copy link
Copy Markdown
Contributor Author

@kojotak kojotak Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean:

  • remove the cantCauseToSearchLibrary() method completely
  • add check for CantCauseToSearchLibrary mode into cantSearchLibrary()?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's just a slightly more specific variant, not a different mode
we usually have one entry point, so scripts can just use params for their needs

}

public void addAdditionalVote(long timestamp, int value) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
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 StaticAbilityCantSearchLibrary {

/**
* @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).isPresent();
}

public static boolean cantCauseToSearchLibrary(Player player) {
return findStaticAbilityForValidPlayer(player, CantCauseToSearchLibrary).isPresent();
}

private static Optional<StaticAbility> 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@ public enum StaticAbilityMode {

// StaticAbilityCountersRemain
CountersRemain,

// StaticAbilityCantSearchLibrary
LimitSearchLibrary,
CantSearchLibrary,
CantCauseToSearchLibrary
;

public static StaticAbilityMode smartValueOf(final String value) {
Expand Down
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/a/ashiok_dream_render.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/a/aven_mindcensor.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

technically this is a replacement effect
not sure yet if the static approach is better for both

Oracle:Flash\nFlying\nIf an opponent would search a library, that player searches the top four cards of that library instead.
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/l/leonin_arbiter.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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$ CantSearchLibrary | ValidPlayer$ 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.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn‘t work

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kojotak check "Volrath's Curse" for both Continuous and other modes

in this case, it should be something like this:
Mode$ Continuous,CantSearchLibrary | Affected$ Player | ValidPlayer$ Player | IgnoreEffectCost$ 2

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, good hint. It was also neccessary to check getIgnoreEffectPlayers in cantSearchLibrary so it finally works.

Tested:

  • payed 2R for ignore effect
  • 2W for Stoneforge mystic - confirm trigger
  • select an equipment from library
image

# 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.
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/m/mindlock_orb.txt
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/s/shadow_of_doubt.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/s/stranglehold.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down