Log in

DotP 2014: Filters and Interrogations

Contents

Filters

Filters are the main way to isolate cards or players, possibly based on criteria. For instance, "target creature", "target player", "target opponent", "blue creatures you control", "creatures with flying", "all cards with the same name", and "creature you control with a +1/+1 counter on it" all are done using filters. There are two types of filters: players and cards. You change a filter's type from the default (which is FILTER_TYPE_CARDS) using "oFilter:SetFilterType()".

Before you can do anything else, including setting the type, you need to clear the filter. The game only seems to have one filter. You can change that filter by calling various functions on it, but any code called on a filter is working on the same underlying, abstract object. This means that if your code runs a filter, and then another section of code runs another filter, your filter will have been affected as well. Short version: all filters interfere with each other. This means you must do everything you need to do with your filter immediately. If any unknown code has had a chance to run, you cannot use that filter anymore. You must remake it from scratch.

OBSCURE NOTE: This also means it might be possible to do some interesting things with filters, such as getting the cards a filter is currently looking at, even without knowing where the filter itself is from.

From the above, you might understand why clearing the filter is the first thing you do with it. In fact, ClearFilter() is practically (though not technically) the only function which can give a filter object, so it's the only way you even can start a filter. When you clear the filter, like I said, it returns a filter object. You store that filter object in a variable to change the filter further.

local oFilter = ClearFilter()

Now, "oFilter" can have other filter functions called on it. Filter functions include:

  • Clear
  • Add
  • AddSubFilter
  • AddSubFilter_And
  • AddSubFilter_Or
  • Set_And!!
  • Set_Or!!
  • SetFilterType
  • SetZone
  • May
  • Count
  • CountStopAt
  • SetHint
  • SetPortion
  • SetReversePortion
  • SetStackObjectType
  • SetUnique
  • SetMarkedObjectsOnly
  • SetUnmarkedObjectsOnly
  • EvaluateObjects
  • EvaluatePlayers
  • GetNthEvaluatedObject
  • GetNthEvaluatedPlayer
  • GetRandomEvaluatedObject
  • ChromaCount
  • Invalidate

!!Do not use Set_And() or Set_Or(). They break stuff.

We'll discuss each of these as we go along, although most filters will only use Add, SetFilterType, and either Count or CountStopAt.

SetFilterType will be used if your filter should include players. If it'll be just players, you'll call "SetFilterType(FILTER_TYPE_PLAYERS)". If it will include both players and cards (usually for dealing damage to either type), add the types together in the parameter list: "SetFilterType(FILTER_TYPE_CARDS + FILTER_TYPE_PLAYERS)". Since they are technically number constants, adding them together simply produces another number, which the function also understands.

Count simply returns how many objects are currently valid for the filter. CountStopAt returns the same number, but it stops counting when it reaches a certain number. So, for example, if you need to count how many forests you have on the battlefield, (Coiling Woodwurm), you'd use Count.

...oFilter already exists and contains exactly all forests on the battlefield...
local iCount = oFilter:Count()

If, however, you need to do something if you control two or more basic lands (Canopy Vista), you'd use CountStopAt.

...oFilter already exists and contains exactly all basic lands you control...
if oFilter:CountStopAt(2) then
 ...You control 2 or more basic lands...
end

Anywhere you use CountStopAt, you could also use Count, so why does it even exist? Our best guess is efficiency. When you can tell the count function to stop running early, it doesn't have to evaluate as much. Since counting it presumably means it has to loop through every object in the game and determine if it's valid, this could possibly be a fairly large difference, though it'd take a heavily scripted section of code for it to really matter.

Add

Add is the main filter function. This is the function you'll use to add criteria to your function. Remember, Add does not add objects to a filter, it adds criteria/restrictions. By default, a filter contains all objects of the current type in the current zone. Those default to cards and battlefield, but they can be changed with SetFilterType and SetZone. Adding criteria will eliminate invalid objects.

Add is called with at least 2 parameters, but it will usually have 3. The first parameter is a filter expression. These are in the Decompilable LOL Contents with the prefix "FE_". The second parameter is an operator, which are also found there, this time with the prefix "OP_". Some filter expressions require only a true or false, and that boolean value will take the place of the operator. See FE_IS_ATTACKING below. The third parameter is the condition. If the filter expression, operator, and condition are all in agreement for a given object, that object is considered valid for the filter and is not eliminated.

The filter expression will decide whether there are 2 or 3 parameters. If it has the prefix "FE_IS_", then it will have 2 parameters; otherwise, it will have 3.

Here are a few examples, most of which will be self explanatory.

oFilter:Add(FE_CONTROLLER, OP_IS, EffectController()) --You control
oFilter:Add(FE_TYPE, OP_IS, CARD_TYPE_CREATURE) --Creature
oFilter:Add(FE_SUBTYPE, OP_NOT, CREATURE_TYPE_HUMAN) --Nonhuman
oFilter:Add(FE_CARD_INSTANCE, OP_NOT, EffectSource()) --"another" or "other"
oFilter:Add(FE_CARD_NAME, OP_IS, oTargetCard) --Same name as oTargetCard
oFilter:Add(FE_CARD_NAME, OP_NOT, oTargetCard:GetCardName()) --Not the same name as oTargetCard
--FE_CARD_NAME can take either a card pointer or a string.

oFilter:Add(FE_IS_ATTACKING, true) --Attacking creature.
oFilter:Add(FE_IS_TAPPED, false) --Untapped

And just in case that isn't not enough leeway, there's also the filter expression FE_LUA_CONDITION. This one is unique in two ways. 1: It has 4 parameters. 2: It adds a FILTER_CONDITION block to the evaluation process.

Filter Condition Blocks

This is a very rarely used XML tag. However, if you need to do advanced filtering, this can be invaluable. A FILTER_CONDITION block contains a section of code that will run, just like an RTA. It needs a single int attribute: id, which should be the number used as the second parameter of an FE_LUA_CONDITION criteria. Since this is an XML tag/block, it goes outside whatever block contains the filter that will use this block.

A filter condition block is given an object pointer by the filter that calls it which can be retrieved with FilteredCard() or FilteredPlayer() (see Filter Blocks). If the block then returns "true", then the object is considered valid for the filter that called the condition; otherwise, it's considered invalid and is eliminated. Adding the lua condition to the filter is done with 4 parameters.

oFilter:Add(FE_LUA_CONDITION, ID, Player, DC)
oFilter:Add(FE_LUA_CONDITION, 0, EffectController(), EffectDC())

The purpose of the player parameter hasn't be determined yet, but the fourth parameter, DC, is the FILTER_CONDITION's EffectDC(). When you call "EffectDC()" from within the FILTER_CONDITION's code, it will return whatever chest was passed to it in the fourth parameter. When you learn about delayed triggered abilities, you'll see this concept again.

So, for instance, consider this filter:

<FILTER filter_id="0">
 local oSubChest = EffectDC():Make_Chest(0)
 oSubChest:Set_Int(0, 1)
 oFilter:Add(FE_LUA_CONDITION, 0, EffectController(), oSubChest)
</FILTER>
<FILTER_CONDITION id="0">
 return EffectDC():Get_Int(0) == 1
</FILTER_CONDITION>

That condition will return true because EffectDC() on line 7 will return the chest that was passed in as oSubChest. Since oSubChest's 0th register is an int with value 1, "EffectDC():Get_Int(0) == 1" is true.

The filter condition block must be inside the ability that contains the filter calling the condition. It must, however, not be inside of any other block (it must be a DIRECT child of the ability block). That means that both of these abilities are valid: (These are essentially nonsense abilities and filters.)

<STATIC_ABILITY>
 <FILTER id="0">
  local oFilter:Add(FE_LUA_CONDITION, 1, EffectController(), EffectDC())
 </FILTER>
 <FILTER_CONDITION id="1">
  return FilteredCard():IsTapped()
 </FILTER_CONDITION>
</STATIC_ABILITY>

<ACTIVATED_ABILITY>
 <COST type="Generic">
  <PREREQUISITE>
   local oFilter = ClearFilter()
   oFilter:Add(FE_LUA_CONDITION, 2, EffectController(), EffectDC())
   return oFilter:CountStopAt(1) == 1
  </PREREQUISITE>
 </COST>
 <FILTER_CONDITION id="2">
  return FilteredCard():IsTapped()
 </FILTER_CONDITION>
</ACTIVATED_ABILITY>

This one, however, will not work.

<ACTIVATED_ABILITY>
 <COST type="Generic">
  <PREREQUISITE>
   local oFilter = ClearFilter()
   oFilter:Add(FE_LUA_CONDITION, 1, EffectController(), EffectDC())
   return oFilter:CountStopAt(1) == 1
  </PREREQUISITE>
  <FILTER_CONDITION id="1">
   return FilteredCard():IsTapped()
  </FILTER_CONDITION>
 </COST>
</ACTIVATED_ABILITY>

Because FILTER_CONDITION is not directly within ACTIVATED_ABILITY, it will fail.

SubFilters

More common than FILTER_CONDITION blocks are subfilters. A subfilter is just a filter within a filter, meaning it's the type of thing you'd use to find an object that is either a blue creature or a red enchantment. Unlike ClearFilter(), this doesn't return the actual filter, but rather a subfilter. Subfilters have many of the same functions, but do at least lack a few. The only ones I can verify at the moment are that they do contain Add and they do not contain Count. So, you can't count the number of objects that satisfied a subfilter. (Though you should never need to, it's a good piece of information for determining that the underlying identity of filters and subfilters is different.)

There are two functions for getting a subfilter: "oFilter:AddSubFilter_Or()" and "oFilter:AddSubFilter_And()". Or is used a lot more often than and, since the normal filter's natural state is already "and". All criteria must be met in order to be valid. In an "or" subfilter, if an object is valid for any of its criteria, then it is valid for the subfilter. So, "target artifact or enchantment" would use an "or" subfilter. As a couple of examples, here are the ones I've mentioned.

--artifact or enchantment
local oFilter = ClearFilter()
local oOrFilter = oFilter:AddSubFilter_Or()
 oOrFilter:Add(FE_TYPE, OP_IS, CARD_TYPE_ARTIFACT)
 oOrFilter:Add(FE_TYPE, OP_IS, CARD_TYPE_ENCHANTMENT)
--blue creature or tapped land
local oFilter = ClearFilter()
local oOrFilter = oFilter:AddSubFilter_Or()
 local oBlueCreatureFilter = oOrFilter:AddSubFilter_And()
  oBlueCreatureFilter:Add(FE_TYPE, OP_IS, CARD_TYPE_CREATURE)
  oBlueCreatureFilter:Add(FE_COLOUR, OP_IS, COLOUR_BLUE)
 local oRedEnchantmentFilter = oOrFilter:AddSubFilter_And()
  oRedEnchantmentFilter:Add(FE_TYPE, OP_IS, CARD_TYPE_LAND)
  oRedEnchantmentFilter:Add(FE_IS_TAPPED, true)

Filter Blocks

If you need to perform a specific action on each object in a filter, you can usually use a FILTER block to do so. The FILTER block itself is only for the filtering portion of the code, but it gets an int attribute, "filter_id", which can be used to link an action, such as an RTA, to that filter. An RTA with the same "filter_id" as a FILTER block will run once for each object in the filter, and that RTA block will get the current object to work on. The current object is retrieved via either FilteredCard() or FilteredPlayer(). So, let's say you're supposed to destroy all creatures. You could make a FILTER block that filters for all creatures, and then use an RTA to destroy them all. This is the proper way to do such an action, though there aren't any real downsides to doing it another way.

<FILTER filter_id="0">
 local oFilter = ClearFilter()
 oFilter:Add(FE_TYPE, OP_IS, CARD_TYPE_CREATURE)
</FILTER>
<RESOLUTION_TIME_ACTION filter_id="0">
 FilteredCard():Destroy()
</RESOLUTION_TIME_ACTION>

In the code above, the line "FilteredCard():Destroy()" is called once for each object that was valid in the FILTER block. In each instance, "FilteredCard()" returns a different one of those valid objects. So, by the time it finishes running on them all, it will have destroyed every creature.

FILTER blocks can be used in PTAs and CAs, as well. Of particular note with CAs is the fact that a FILTER block runs one time. It runs the filter when the ability that uses the filter runs. So, when the CA first runs, it will run the FILTER block it's associated with, and will then run on those same objects forever. If the filtered objects should update rather than sticking with whichever ones are valid when the CA starts, then the FILTER block needs the bool attribute "reevaluates". This is unnecessary for CAs inside of STATIC_ABILITYs, though. For instance,

--Skyshaper: Creatures you control gain flying until end of turn. 
<FILTER filter_id="0">
 local oFilter = ClearFilter()
 oFilter:Add(FE_TYPE, OP_IS, CARD_TYPE_CREATURE)
 oFilter:Add(FE_CONTROLLER, OP_IS, EffectController())
</FILTER>
<CONTINUOUS_ACTION layer="6" filter_id="0">
 FilteredCard():GetCurrentCharacteristics():Bool_Set(CHARACTERISTIC_FLYING, 1)
</CONTINUOUS_ACTION>
<DURATION simple_duration="UntilEOT" />
--Encircling Fissure: Prevent all combat damage that would be dealt this turn by creatures target opponent controls.
<FILTER filter_id="1" reevaluates="1">
 local Player = EffectDC():Get_Targets(1):Get_PlayerPtr(0)
 if Player ~= nil then
  local filter = ClearFilter()
  filter:Add(FE_TYPE, OP_IS, CARD_TYPE_CREATURE)
  filter:Add(FE_CONTROLLER, OP_IS, Player)
 end
</FILTER>
<CONTINUOUS_ACTION layer="8" filter_id="1">
 if FilteredCard() ~= nil then
  FilteredCard():GetCurrentCharacteristics():Bool_Set(CHARACTERISTIC_DOESNT_DEAL_COMBAT_DAMAGE, 1)
 end
</CONTINUOUS_ACTION>
<DURATION simple_duration="UntilEOT" />

Note that Encircling Fissure filters for creatures, but the target is the player. That's why it affects creatures even if they enter the battlefield after the ability has resolved.

Filtering Cards

When you filter for cards, you should keep a few things in mind.

  • The battlefield is the default. There is no need to call "SetZone(ZONE_BATTLEFIELD)".
  • FILTER_TYPE_CARDS is the default, negating the need for "SetFilterType(FILTER_TYPE_CARDS)".

Filtering Players

To filter for players, call either "SetFilterType(FILTER_TYPE_PLAYERS)" or "SetFilterType(FILTER_TYPE_CARDS + FILTER_TYPE_PLAYERS)".

Evaluating Object

Sometimes, you need to work on the filtered objects without using a FILTER block. To do so, you use "EvaluateObjects()".

<RESOLUTION_TIME_ACTION>
 local oFilter = ClearFilter()
 oFilter:Add(FE_TYPE, OP_IS, CARD_TYPE_CREATURE)
 local iCount = oFilter:EvaluateObjects()
 ...Do stuff...
</RESOLUTION_TIME_ACTION>

In the above example, it filters for all creatures on the battlefield, and then evaluates them. The evaluation runs through all objects and determines if they're valid just like Count. Unlike Count, it also makes those cards accessible via "oFilter:GetNthEvaluatedObject(i)". "EvaluateObjects()" also returns the number of valid objects it found.

So, "...Do stuff..." above: if we then wanted to tap each of those creatures, we could iterate through them.

<RESOLUTION_TIME_ACTION>
 local oFilter = ClearFilter()
 oFilter:Add(FE_TYPE, OP_IS, CARD_TYPE_CREATURE)
 local iCount = oFilter:EvaluateObjects()
 for i=0,iCount-1 do
  oFilter:GetNthEvaluatedObject(i):Tap()
 end
</RESOLUTION_TIME_ACTION>

Note that for filters, players are not a subset of objects like they usually are considered. Typically, an "object" is a card or player. For filters, you must evaluate players separately from objects using "EvaluatePlayers()" and "GetNthEvaluatedPlayer()".

Important CW Notes

Filtering with the CW installed can be a little bit tricky. There are a few things to keep in mind.

Unlike the base game, the CW includes planeswalkers. The game was very much not designed for planeswalkers, and in order for them to work, they are internally enchantments. As such, if you call "Add(FE_TYPE, OP_IS, CARD_TYPE_ENCHANTMENT)", you'd get planeswalkers, too. In order to filter for any card types, you can use "CW_Filter_Type(Filter, Type, Operator, Zone)".

Because of Urborg, Tomb of Yawgmoth, you can't use the normal swamp filter, either. The same function is used as the above, except SubType.

When you call one of these CW_Filter_* functions, the first parameter is usually a filter to modify.

So, here are some examples:

--Filter for all swamps.
local oFilter = ClearFilter()
CW_Filter_SubType(oFilter, LAND_TYPE_SWAMP) --OP_IS is the default. Include the zone for efficiency.

--All non-islands.
local oFilter = ClearFilter()
CW_Filter_SubType(oFilter, LAND_TYPE_ISLAND, OP_NOT)

--All swamps in your graveyard.
local oFilter = ClearFilter()
oFilter:SetZone(ZONE_GRAVEYARD, EffectController())
CW_Filter_SubType(oFilter, LAND_TYPE_SWAMP, OP_IS, ZONE_GRAVEYARD)

--Also all swamps in your graveyard.
local oFilter = ClearFilter()
oFilter:SetZone(ZONE_GRAVEYARD, EffectController())
CW_Filter_SubType(oFilter, LAND_TYPE_SWAMP) --Explicitly stating the zone more efficient, but this is acceptable.

--All enchantments you don't control.
local oFilter = ClearFilter()
oFilter:Add(FE_CONTROLLER, OP_NOT, EffectController())
CW_Filter_Type(oFilter, CARD_TYPE_ENCHANTMENT)
--All non-planswalker permanents.
local oFilter = ClearFilter()
CW_Filter_Type(oFilter, CARD_TYPE_PLANESWALKER)
CW_Filter_Permanents(oFilter) --Didn't discuss this, but it works like the others.

For enchantments, planeswalkers, and swamps, you'll also see some filter functions specific to them, but use these more recent ones if possible.

Whenever you need to check whether a card is a specific type or subtype, you can use the corresponding CW_General_IsType or CW_General_IsSubType functions. They take two parameters: card and type/subtype.

--if oCard is a vampire creature.
if CW_General_IsType(oCard, CARD_TYPE_CREATURE) and CW_General_IsSubType(oCard, CREATURE_TYPE_VAMPIRE) then
 ...Do stuff...
end

Interrogations

When you need to determine if something happened in the past, you might need to track those events as they happen. However, for some events, you can use interrogations. Like filters, there's likely only one underlying interrogation object within the game, and we simply manipulate that object; and thus, interrogations would likely interfere with one another just like filters do. There isn't a whole lot of info on interrogations, and they're not nearly as comprehensive as filters, but here are the basics.

Unlike with filter, the initial clear function isn't called on its own, but rather on MTG().

local oInterrogation = MTG():ClearInterrogationQuery()

Once it's cleared, you can maniuplate it using the various interrogation functions.


Here are a couple of examples.

--If EffectSource() deal damage to TriggerObject() this turn.
local oInterrogation = MTG():ClearInterrogationQuery()
oInterrogation:SetObject( EffectSource() )
oInterrogation:SetSecondaryObject( TriggerObject() )
if oInterrogation:Test( INTERROGATE_DAMAGE_DEALT, INTERROGATE_THIS_TURN ) then
 ...Do stuff...
end

--The number of spells you've cast this turn.
oInterrogation:SetPlayer(oController)
local iSpellsCast = oInterrogation:Count(INTERROGATE_SPELLS_CAST, INTERROGATE_THIS_TURN)

For the most part, interrogations aren't well explored. If you need help with one, it's best to ask around or look in cards for interrogations already in use.

CW Notes

In addition to interrogations, the CW has a decent manager token (an invisible token you can't interact with normally that's made at the beginning of the game and exists throughout the entire match) to track various events during a game. To see some of its uses, search the Deck Builder for cards containing the XML "_MANAGER_TRACKING". It can be a bit complicated sometimes, but other times very simple. (Temporary note: some of the cards using some of the functions are much simpler to do now, as functions were recently made to uncomplicate things.)