-
-
Notifications
You must be signed in to change notification settings - Fork 70
5 ‐ Filter Functions
A filter function is a function that takes a card as its first parameter and returns a boolean (either true or false).
A simple example would be:
function s.lvfilter(c)
return c:IsMonster() and c:IsLevel(4)
endThis returns true if the card c is a Level 4 monster.
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 filter return true). Some of the most common functions that use a filter are listed below. These 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 statement will be true if there is at least 1 Level 4 monster in either player's GY:
Duel.IsExistingMatchingCard(s.lvfilter,tp,LOCATION_GRAVE,LOCATION_GRAVE,1,nil)We can also make a player select cards that fulfill a filter. Here, a player will select 1 Level 4 monster in their hand, and the selected card will be stored in a group called g:
local g=Duel.SelectMatchingCard(tp,s.lvfilter,LOCATION_HAND,0,1,1,nil)NOTE: You can check the scripting library for more functions that use filters, and to learn what their other parameters do. By tweaking the other parameters, you can check cards in a different location, make the player select more than 1 card, etc.
Let's look at an official script, Foolish Burial, and see how it uses a filter function. The card text is simple:
Send 1 monster from your Deck to the GY.
The script uses the following filter function, which returns true for cards that are monsters and can be sent to the GY:
function s.tgfilter(c)
return c:IsMonster() and c:IsAbleToGrave()
endTo activate Foolish Burial, a player must be able to send a monster from their Deck to the GY. So before activation, the script must check if a valid card exists in the player's deck. This is its "activation legality check" and is done inside the if chk==0 then block in the target function (s.target):
if chk==0 then return Duel.IsExistingMatchingCard(s.tgfilter,tp,LOCATION_DECK,0,1,nil) endHere, Duel.IsExistingMatchingCard is used with the following arguments:
-
s.tgfilteris the filter function explained earlier -
tpis the player activating the card. -
LOCATION_DECKis the location that will be checked intp's side of the board -
0is the location intp's opponent's side of the board (0denotes that we're not checking any location in the opponent's side) -
1is the minimum number of cards that must fulfills.tgfilter -
nilis the card(s) to be exempted from the check (nildenotes that there are no exceptions)
This means the entire line can be read as: the activation of this card is legal if there is at least 1 card in the activating player's deck that is a monster and can be sent to the GY
NOTE: You can read Understanding a card script to learn more about the different parts of a script (such as the
if chk==0 thenblock). This page will only focus on the usage of filter functions.
Now that the script knows when it's legal to activate the card, let's look at what happens when the card is actually activated and the effect resolves. This is in the effect's operation function (s.operation):
function s.activate(e,tp,eg,ep,ev,re,r,rp)
Duel.Hint(HINT_SELECTMSG,tp,HINTMSG_TOGRAVE)
local g=Duel.SelectMatchingCard(tp,s.tgfilter,tp,LOCATION_DECK,0,1,1,nil)
if #g>0 then
Duel.SendtoGrave(g,REASON_EFFECT)
end
endFirst, it gives the player a "hint" that the selection is for cards that will be sent to the GY. Then, it calls Duel.SelectMatchingCard with the following arguments:
- the first
tpis the player that will perform the selection -
s.tgfilteris the filter that the card choices must fulfill - the next
tpis the player who activated the card -
LOCATION_DECKis the location intp's side of the board wheretpwill select -
0is the location intp's opponent's side of the board (0denotes thattpwill not be selecting opponent cards) - the first
1is the minimum number of cards to select - the second
1is the maximum number of cards to select (since it's equal to the minimum, the player will select exactly 1 card) -
nilis the card(s) to be exempted (nildenotes that there are no exceptions)
This means Duel.SelectMatchingCard(tp,s.tgfilter,tp,LOCATION_DECK,0,1,1,nil) can be read as: make the player who activated this card select exactly 1 card in their deck that is a monster and can be sent to the GY.
The selected card is stored in a group that is assigned to a local variable named g. If the group is not empty (#g>0), it is sent to the GY and Foolish Burial's effect finishes resolving.
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 parameters comes in.
Filter functions always take a card as their first parameter (usually named c by convention), but they can take any amount of parameters 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.
function s.atkfilter(c,minatk)
return c:IsFaceup() and c:IsAttackAbove(minatk)
endNow, 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 (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:
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)
endHere, 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).
When Special Summoning monsters through a card effect, we need filters to check for cards that can be Special Summoned. Those filters will need to call Card.IsCanBeSpecialSummoned inside them, which in turn needs to know what effect and player is attempting the Special Summon. The filter wouldn't know what those are, so we need to pass them as additional arguments. Hence, Special Summon filters typically look like this:
function s.spfilter(c,e,tp)
return c:IsCanBeSpecialSummoned(e,0,tp,false,false) --and other checks we want to perform
endThis filter is fulfilled by cards that can be Special Summoned by the player tp through the effect e.
NOTE: You'll notice that we're actually calling
Card.IsCanBeSpecialSummonedwith more arguments (there's a0and twofalses). However, those are just plain values that we don't need to pass to the filter. For the purposes of this guide, we'll only focus on howeandtpare being passed, but you can read more aboutCard.IsCanBeSpecialSummoned's other parameters in the scripting library.
Let's look at the script for Deep Sea Diva to see a Special Summon filter in action. Deep Sea Diva has this effect:
When this card is Normal Summoned: You can Special Summon 1 Level 3 or lower Sea Serpent monster from your Deck.
For this, it uses the following filter, which returns true for Level 3 or lower Sea Serpent monsters that can be Special Summoned by player tp using effect e:
function s.spfilter(c,e,tp)
return c:IsLevelBelow(3) and c:IsRace(RACE_SEASERPENT) and c:IsCanBeSpecialSummoned(e,0,tp,false,false)
endFor the effect to be activated legally, the player must have a valid monster in their deck that they can Special Summon (meaning, they must have a monster in their deck that fulfills s.spfilter). The player must also have a free Main Monster Zone that they can Special Summon to. These two things are checked by the script inside the if chk==0 then block of the target function (s.sptg).
function s.sptg(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return Duel.IsExistingMatchingCard(s.spfilter,tp,LOCATION_DECK,0,1,nil,e,tp)
and Duel.GetLocationCount(tp,LOCATION_MZONE)>0 end
--etc.
endHere, the script calls Duel.IsExistingMatchingCard with two extra arguments, e and tp. Again, they come after the "exception" which is nil.
e is this effect of Deep Sea Diva that is being checked for activation legality, and tp is the activating player. Both values are automatically supplied through s.sptg's own parameters (e,tp,eg,ep,ev,re,r,rp,chk).
Since e will also be the effect that would perform the Special Summon,
the script passes it to Duel.IsExistingMatchingCard as an extra argument. Similarly, the activating player will also be the player that would perform the Special Summon, so it is also passed. The order matters when passing additional arguments. The first extra argument will become the filter's first extra argument, the second extra argument becomes the filter's second extra argument, and so on.
In this particular script, it so happens that the extra parameters of s.spfilter are also called e and tp, but the names don't really need to match. The extra arguments just have to be in the proper order that the filter expects them to be. The following usage is perfectly valid:
function s.spfilter(c,the_effect_that_would_summon,the_player_that_would_summon)
return c:IsLevelBelow(3) and c:IsRace(RACE_SEASERPENT)
and c:IsCanBeSpecialSummoned(the_effect_that_would_summon,0,the_player_that_would_summon,false,false)
end
function s.sptg(e,tp,eg,ep,ev,re,r,rp,chk)
local this_effect_of_diva=e
local the_activating_player=tp
if chk==0 then return Duel.IsExistingMatchingCard(s.spfilter,tp,LOCATION_DECK,0,1,nil,this_effect_of_diva,the_activating_player)
and Duel.GetLocationCount(tp,LOCATION_MZONE)>0 end
--etc.
endThis illustrates that once passed to Duel.IsExistingMatchingCard (which passes it to s.spfilter), this_effect_of_diva becomes the_effect_that_would_summon because it's the first extra argument, while the_activating_player becomes the_player_that_would_summon because it's the second extra argument.
Now let's move to the effect resolution:
function s.spop(e,tp,eg,ep,ev,re,r,rp)
if Duel.GetLocationCount(tp,LOCATION_MZONE)<=0 then return end
Duel.Hint(HINT_SELECTMSG,tp,HINTMSG_SPSUMMON)
local g=Duel.SelectMatchingCard(tp,s.spfilter,tp,LOCATION_DECK,0,1,1,nil,e,tp)
if #g>0 then
Duel.SpecialSummon(g,0,tp,tp,false,false,POS_FACEUP)
end
endFirst, it checks if tp has a free Main Monster Zone. If not, the effect stops resolving by having an early return. Then, it gives a "hint" to tp that the following card selection is for a Special Summon. Much like in s.sptg, the effect and player are already supplied (e and tp). This time, they are passed as extra arguments to Duel.SelectMatchingCard which passes them to s.spfilter. This allows tp to select a Level 3 or lower Sea Serpent monster that they can Special Summon from their deck. The selection is stored in a group called g, and g is finally Special Summoned if it's not empty (#g>0).
There are also cases where a filter needs additional information that depends on another card that fulfills a separate filter. 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 filters for this, but more importantly, the first monster's filter will need to call Duel.IsExistingMatchingCard inside it using the second monster's filter.
--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)
endHere, 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):
if chk==0 then return Duel.IsExistingMatchingCard(s.rmfilter,tp,LOCATION_HAND,0,1,nil,tp) endEssentially, 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.
Reveal 1 Dragon-Type monster in your hand, add 1 Dragon-Type monster with the same Level from your Deck to your hand, then shuffle the revealed monster into the Deck.
Draconnection's script uses nested filters to make sure that a Dragon monster in their hand can only be revealed if and only if the player also has a Dragon monster in their Deck with a matching level. It uses the following filter for the card to reveal:
function s.revfilter(c,tp)
return c:IsRace(RACE_DRAGON) and c:IsAbleToDeck() and not c:IsPublic()
and Duel.IsExistingMatchingCard(s.thfilter,tp,LOCATION_DECK,0,1,nil,c:GetLevel())
endThis returns true for Dragon monsters that can be sent to the Deck and are not currently public information (so they can be revealed), and such that there exists at least one card in the Deck that satisfies s.thfilter given c:GetLevel() as additional argument. s.thfilter is defined as follows:
function s.thfilter(c,lv)
return c:IsRace(RACE_DRAGON) and c:IsLevel(lv) and c:IsAbleToHand()
endThis checks for Dragon monsters that can be added to the hand whose level is exactly the lv passed to it.
For the activation to be legal, there needs to be at least one card in tp's hand that fulfills s.revfilter, so the script checks for that using Duel.IsExistingMatchingCard in the activation legality block:
if chk==0 then return Duel.IsExistingMatchingCard(s.revfilter,tp,LOCATION_HAND,0,1,nil,tp) endIt passes tp because s.revfilter will need it to call Duel.IsExistingMatchingCard inside when checking if there's a corresponding Dragon monster in the Deck that can be added to the hand.
Now let's go through what happens during resolution (s.operation):
Duel.Hint(HINT_SELECTMSG,tp,HINTMSG_TODECK)
local g1=Duel.SelectMatchingCard(tp,s.revfilter,tp,LOCATION_HAND,0,1,1,nil,tp)
if #g1==0 then return endFirst, the script makes the player select 1 card in their hand to reveal. It must satisfy s.revfilter. This selection is stored in a group called g1. If the group is empty (no selection can be made), the effect stops resolving here. Next, we have the following:
Duel.ConfirmCards(1-tp,g1)
Duel.Hint(HINT_SELECTMSG,tp,HINTMSG_ATOHAND)
local g2=Duel.SelectMatchingCard(tp,s.thfilter,tp,LOCATION_DECK,0,1,1,nil,g1:GetFirst():GetLevel())
if #g2==0 then return endHere, the script reveals g1 to the opponent (1-tp) using Duel.ConfirmCards. Next, it makes the player select a card in their Deck that satisfies s.thfilter. At this point, there's no longer a need for nested filters since we already have access to the level that s.thfilter needs to compare to. The script simply passes g1:GetFirst():GetLevel() as the additional argument, which is essentially the level of the revealed monster. This selection is stored in a group called g2, and just like before, the script stops resolving the effect if g2 is empty. Finally, we have:
Duel.BreakEffect()
if Duel.SendtoHand(g2,nil,REASON_EFFECT)>0 and g2:GetFirst():IsLocation(LOCATION_HAND) then
Duel.ConfirmCards(1-tp,g2)
Duel.BreakEffect()
Duel.SendtoDeck(g1,nil,SEQ_DECKSHUFFLE,REASON_EFFECT)
endDuel.BreakEffect is used to separate parts of an effect so they're not treated as if they happened at the same time (the difference between the "then" and "and if you do" conjunctions). After calling that function, the script attempts to send g2 to the hand using Duel.SendtoHand. It also makes sure that the search was successful by checking if Duel.SendtoHand returns a number greater than 0, and that the card in g2 is now located in LOCATION_HAND. If it's successful, g2 is confirmed to the opponent and Duel.BreakEffect is called again, before finally shuffling g1 to the Deck.
TBA
TBA
Duel.IsExistingMatchingCardDuel.SelectMatchingCardDuel.IsExistingTargetDuel.SelectTargetGroup.FilterGroup.IsExists