FilterWML/Examples - How to use Filter

From The Battle for Wesnoth Wiki
Revision as of 19:39, 3 June 2013 by Adamant14 (talk | contribs) (added the first four pages of Pyrophorus HowTo)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

WML Filtering

Filters are a very important part of WML language, and a fairly complex too for various reasons. Here, we shall try to explain how to use them with more details than in the reference wiki pages. But the goal is not to replace these pages, and we assume you have at least some knowledge of the various WML filters, namely unit and location filters. The examples given below aren’t always the best way to do things: the goal here is understanding filtering, not to give a complete WML course.


Basics: How filters work.

Filtering is narrowing a set of objects to a result set using criteria. Let’s give an example : given a set of cards, if one is asked to select the spades, (s)he will probably check the cards one by one and create two stacks : one containing only the spades, and another containing the unselected cards. This is a very simple filter, where the criterion is « this card has spades colour » and the result set is a card stack. The spades cards are said ‘matching’ the filter. If next we’re asked to find the king of spades, most probably, we will not restart from the beginning but only scan the spades stack to find the right card. This is an example of two criteria filter. In WML we would write something like :

[filter]
    colour=spades
    value=king
[/filter]

to describe the operation. Criteria are expressions evaluating to true or false. Is this card colour spades ? That’s how we must read the sentence: ‘colour=spades’. Now, stating both criteria must be met is not the only way to combine them:

[filter]  
    colour=spades,diamonds  
    value=king  
[/filter]

the first expression will be true if a card colour is ‘spades OR diamonds’. It would make no sense to state they should be ‘spades AND diamonds’, of course. In many languages, you must specify how criteria combine using the special keywords ‘OR’ and ‘AND’. In WML, you can do so, but most often, you’re not requested to do so. Writing explicitly the logical operators would give something like that:

[filter]  
    [and]  
        colour=spades  
        [or]  
            colour=diamonds  
        [/or]  
    [/and]  

    [and]  
        value=king  
    [/and]  
[/filter]

or in natural speech: “is card (colour=spades OR colour=diamonds) AND value=king ?” Note here the use of parenthesis. As in algebra, they mean their content must be evaluated prior to apply the last criterion. [and] and [or] subtags are WML equivalents of parenthesis. They are not mandatory (like in some other languages), and this is a cool feature, but can be misleading in complex filters. The rule is:

  • listed criteria are ANDed, in other words, they must all be true for the object to match the filter.
  • comma separated lists in a criterion are equivalent to ORed criteria.
    In other words, one only is enough for the object to match the filter.


There are some things important to note:

  • A complex filter can always be split into simpler filters applied in chain, each filter taking as starting set the result set of the former one. In our example, we applied the filter « value=king » to the result set of filter « color=spades ». This is important when building or debugging filters, because complex ones can easily be reduced to a chain of simpler ones.
  • Criteria order is not important from a logical point of view. We could have searched the kings first, obtaining the four kings in our result set, and the card of color spade next. The final result is the same. But in WML, for some reasons we shall study later, order can be important.
  • Result sets can contain any number of objects. One can’t assume the result set of our filter will contain a single card ‘THE king of spades’. A human being would probably stop the search when finding a king of spades, but filters don’t. If our starting card packet is not a complete set (some cards were lost, a cheater introduced some more, or anything else), you’ll find one, none or many kings of spades. It’s a common error in WML to assume filters will select a single object.1)
  • Any unit or location will match an empty filter like this one:
[event]  
    name=moveto  
       [filter]  
       [/filter]  
    …  
[/event]

This event will be triggered on every move of every unit on the map.

________________________________________

1) One can be sure only when filtering with ID’s because they are unique.


Here now are two versions of the same action, using filters and not.

[modify_unit]  
    [filter]  
        side=2  
        [not]  
            race=orc  
        [/not]  
    [/filter]  
    side=1  
[/modify_unit]  

This moves to side 1 all side 2 units except orcs. Please note how filter syntax is after all very close to natural speech. This is why we shall see a good way to design complex filters is writing an accurate sentence describing them first. Using no filter (and assuming ‘all_units’ is an array containing all created units in a scenario) we should write :

{FOREACH all_units i}  
    [if]  
        [variable]  
            name= all_units[$i].side  
            equals=2  
        [/variable]  
        [and]  
            [variable]  
                name= all_units[$i].race  
                not_equals=orc  
            [/variable]  
        [/and]  
        [then]  
            [set_variable]  
                name= all_units[$i].side  
                value=1  
            [/set_variable]  
            [unstore_unit]  
                variable= all_units[$i]  
            [/unstore_unit]  
        [/then]  
    [/if]  
    {NEXT i}  

Of course, the first version is much more concise. One should mark :

  • Filters are hidden loops2) fetching all elements of the starting set.
  • Criteria composition is more explicit in the second version. We have here an [and] tag which is missing in the first one. It shows clearly both condition must be met. In filters, the [and] tag is most often implicit, as we already seen.

________________________________________

2) Internally, it’s not always the case, but we’ve not to deal with internal implementations.
Conceptually, filters can always be seen as hidden loops.


At this point, a question arises : where are the starting and result sets we talked about? They show nowhere. The reply is most often we don’t need to see them, because we don’t need to create result sets explicitly. We need to use them to specify actions targets or conditions. In the ‘modify_unit’ example, we apply the action « change side » to the result set of the filter and then need it no more. The starting set is implicit too. Most of time, it’s the larger available set of objects : i.e. all created units (sometimes including the recall list) or all hexes in the map. In the moveto event, it contains only the moving unit. Once again, we are not telling these sets really exist in Wesnoth engine code. But these are an accurate model of how the filters work in WML.

Result sets and arrays.

A good way to get explicitly the result set is to use the ‘store_...’ tags. These actions eate arrays containing the result set of the filter they take as parameter. This is very useful in many ways. The common use is of course to store units and locations for some processing or later use. But one can use this to split complex filters and inspect intermediate results. The former ‘modify_unit’ example could be written as:

[store_unit]  
    variable=temp1  
    [filter]  
        side=2  
    [/filter]  
[/store_unit]  

[store_unit]  
    variable=temp2  
    [filter]  
        find_in=temp1  
        [not]  
            race=orc  
        [/not]  
    [/filter]  
[/store_unit]  
[modify_unit]  
    [filter]  
        find_in=temp2  
    [/filter]  
    side=1  
[/modify_unit]

This trivial example shows not only how to debug complex filters (inspecting the content of temp1 and temp2 arrays), but how to specify a starting set with the ‘find_in’ key. Without it, the second ‘store_unit’ tag would store all units except orcs. With it, we ask to apply the filter to the content of temp1 array only (all side 2 units). It’s like our card example where we selected the spades first and next the king(s) in the spades stack. The ‘find-in’ key is really precious in many cases : often it’s easier to build explicitly an array containing the objects we want to select than creating complex filters to retrieve them. For example, if we want dying units to drop weapons and other units to retrieve them, it can be very difficult to create a filter allowing to select locations where the weapons where dropped. Instead, we can build an array containing their locations (and other informations at will). Since this array have x and y members, the find_in key of a location filter can use it3). It would be: