Skip to content

CardFilter

subtype of function

A function used to filter Cards.

Functions that use this type as parameter

Functions that use this type as parameter

Signature

function(Card c, any ...) → bool

lua
function(c,...)

Parameters

  • Card c

    The Card to check.

  • any ...

    Additional arguments, usually passed by the function using the CardFilter.

Returns

  • bool

    If the Card passed the filter.

A CardFilter is a function that takes a Card as its first parameter and returns a boolean (either true or false). A simple example would be:

lua
function s.lvfilter(c)
  return c:IsMonster() and c:IsLevel(4)
end

This returns true if c is a Level 4 monster.

Using filters

Filters are mostly used with certain Duel and Group functions that collect or check for Cards that fulfill the filter (in other words, Cards that make the function return true). Those functions will call the filter passed to them for every card they are concerned with.

For example, we can use the s.lvfilter we have earlier to check if there are Cards that fulfill it in certain locations. The following expression will be true if there is at least 1 Level 4 monster in either player's GY:

lua
Duel.IsExistingMatchingCard(s.lvfilter,tp,LOCATION_GRAVE,LOCATION_GRAVE,1,nil)

We can also make a player select Cards that fulfill a CardFilter. Here, a player will select 1 Level 4 monster in their hand, and the selected Card will be stored in a Group called g:

lua
local g=Duel.SelectMatchingCard(tp,s.lvfilter,LOCATION_HAND,0,1,1,nil)

Additional CardFilter Arguments

Sometimes, filters need additional details other than the Card they're checking. For example, we may need to write a filter for a face-up monster with ATK higher than the activating player's LP - a value that changes throughout the duel. This is where having additional filter arguments comes in.

Filter functions always take a Card as their first parameter (usually named c by convention), but they can take any amount of arguments after that. For the earlier example, we can create a filter that has an additional parameter called minatk. We then check inside the filter if c's ATK is greater than or equal to minatk.

lua
function s.atkfilter(c,minatk)
  return c:IsFaceup() and c:IsAttackAbove(minatk)
end

Now, for the filter to know what minatk is, it needs to be passed to it. This part is important. Otherwise, minatk would just be nil. Functions that use filters can take additional arguments. These functions will pass the additional arguments they receive (usually after the "exception" argument) to the filter they're using, and they become additional arguments of that filter.

To illustrate, say we have an effect with an activation condition that says If you control a face-up monster with ATK higher than your LP:. For this, we can use the s.atkfilter earlier like so:

lua
function s.condition(e,tp,eg,ep,ev,re,r,rp)
  local lp=Duel.GetLP(tp)
  return Duel.IsExistingMatchingCard(s.atkfilter,tp,LOCATION_MZONE,0,1,nil,lp)
end

Here, we first obtained tp's LP and assigned it to the variable named lp. Then, we passed that lp as an extra argument to Duel.IsExistingMatchingCard. Note that it counts as an extra argument since it comes after the "exception" argument, which is nil in this case.

Internally, Duel.IsExistingMatchingCard will call s.atkfilter using lp as an extra argument. This will correspond to the minatk parameter of the filter. In other words, the first extra argument passed to Duel.IsExistingMatchingCard (called lp) becomes the first argument passed to s.atkfilter (called minatk).

Nesting CardFilter functions

There are also cases where a filter needs additional information that depends on another Card that fulfills a separate CardFilter. A simple example is an effect that needs to banish 1 monster from a player's hand, and send another monster from that player's Deck to the GY with the same level as the first monster.

This means that the second monster's level will depend on the first monster's level. Naturally, we would need two CardFilters for this, but more importantly, the first monster's filter will need to call Duel.IsExistingMatchingCard inside it using the second monster's filter.

lua
--filter for the first monster (the card to be banished)
function s.rmfilter(c,tp)
  return c:IsMonster() and c:HasLevel() and c:IsAbleToRemove()
    and Duel.IsExistingMatchingCard(s.tgfilter,tp,LOCATION_DECK,0,1,nil,c:GetLevel())
end
--filter for the second monster (the card to be sent to the GY)
function s.tgfilter(c,lv)
  return c:IsMonster() and c:IsAbleToGrave() and c:IsLevel(lv)
end

Here, s.rmfilter is fulfilled if c is a monster that has a level and can be banished, and that there exists a monster in tp's Deck which satisfies s.tgfilter. It passes c:GetLevel() (the monster's level) as an additional argument to the Duel.IsExistingMatchingCard call, which will in turn pass it to s.tgfilter as its additional argument called lv.

s.tgfilter will then be fulfilled by monsters that can be sent to the GY and has a level equal to the passed lv. The following legality check can be used to see if we have a valid monster to banish (such that we'll also have a valid monster to send to the GY):

lua
if chk==0 then return Duel.IsExistingMatchingCard(s.rmfilter,tp,LOCATION_HAND,0,1,nil,tp) end

Essentially, we're using a function that uses a filter that uses a function that uses a filter. Hence, "nested". In theory, there's no limit to how deep such nesting could go (ignoring technical stuff like memory, performance, and stack size), but in practice, most cards that require nesting filters will only need single nesting. For a card that uses nesting filters two levels deep, you can check out Small World.