FilterWML/Examples - How to use Filter

From The Battle for Wesnoth Wiki
< FilterWML
Revision as of 21:44, 8 June 2013 by Adamant14 (talk | contribs) (added page 10 and page 11 / 22)

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 create 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:

# in the die event 
[set_variables]  
    name=weapons  
    mode=append  
    [value]  
        x=$unit.x  
        y=$unit.y  
        … anything else, for instance the image name and item id.  
    [/value]  
[/set_variables]  

# drop item, etc…  


# and in a moveto event
[event]  
    name=moveto  
    first_time_only=no  
    [filter]  
        side=1  
        [filter_location]  
            find_in=weapons  
        [/filter_location]  
    [/filter]  
    …  

Let’s give another example. The scenario’s map features three temples at 10,10 20,20 30,30. We want to give some bonus gold to side 1 if any side 1 unit visits temple 1,2,3 exactly in that order. Here is a solution using result sets arrays :

[event]  
    name=moveto  
    first_time_only=no  
    [filter]  
        side=1
        x,y=10,10  
        [not]  
            find_in=temple_1  
        [/not]  
    [/filter]  
    [store_unit]  
        mode=append  
        variable=temple_1  
        [filter]  
            id=$unit.id  
        [/filter]
    [/store_unit]  
[/event]

In temple_1, we store all side 1 units visiting temple_1, but only once (that’s why the [not] find_in tag, because units can visit the temple more than once).

________________________________________

3) It’s surprising because this array doesn’t contain locations, but it’s a feature, not a side effect.
[event]  
    name=moveto  
    first_time_only=no  
    [filter]  
        side=1  
        x,y=20,20  
        find_in=temple_1  
        [not]  
            find_in=temple_2  
        [/not]  
    [/filter]
    [store_unit]  
        mode=append  
        variable=temple_2  
        [filter]  
            id=$unit.id  
        [/filter]  
    [/store_unit]  
[/event]   

This time, we store only units previously stored in temple_1 (= they already visited that temple). Then the last event is obviously :

[event]  
    name=moveto  
    first_time_only=yes  
    [filter]  
        side=1  
        x,y=30,30  
        find_in=temple_2  
    [/filter]  
    [gold]  
        side=1  
        amount=1000  
    [/gold]  
    {CLEAR_VARIABLE temple_1,temple_2}  
[/event]


Ordering and writing

We said earlier that criteria order is not significant. That's right from a theoretical point of view. But, for syntactic reasons and particularly because the radius key in location filters, this is not fully true in WML. Actually conditions are applied to candidate objects until one proves to be false or all conditions are checked. Then, if some condition proves to be false, remaining conditions are not evaluated (so if there's some radius there, it will not be executed). In some cases, this may be important. One can assume conditions are evaluated in the order they are found except logical operators (and, or, not) which are evaluated after other conditions. It’s very important to note that evaluation order of top level conditions (those not embedded in and/or/not tags) is undocumented, which means your code shouldn’t rely on it. On the contrary, and/or/not tags are always executed last, in the order they are written.

So in this example:

[filter]  
   has_weapon=sword  
   side=1  
   [or]  
       side=2  
   [/or]  
   gender=female  
[/filter]  

will be executed using this order:

[filter]  
    has_weapon=sword  
    side=1  
    gender=female  
    [or]  
        side=2  
    [/or]  
[/filter]

One should be aware of this for some reasons:

Clarity: your code will be easier to understand and to debug if you avoid meddling conditions, nested filters and logical blocks, and write them in the order they are evaluated.

Performance. Most of time, performance is not an issue. But it can be if you have a lot of units and use [filter_wml]. More generally, it’s good programming practice to execute the more restrictive test first. Consider this example:

[filter]
    race=orc
    x,y=16,22
[/filter]

The first condition will be evaluated on all units. But the second one will be evaluated on all orcs. Then if we write:

[filter]
    x,y=16,22
    race=orc
[/filter]

obviously, the second condition will be evaluated once at most, and filtering will be faster. (Strictly speaking, I should have wrapped second condition in an and tag to force evaluation order). Remember too that logical operators (and, or, not) are not mandatory, but are allowed. So one can use them for clarity sake or to force an evaluation order. It’s particularly important when using or tags.

In the example above one could expect the result set contains all women of sides 1 and 2 wielding a sword. But it contains actually all side 1 women wielding a sword plus all side 2 units.

Filters work actually as if top level criteria were enclosed in an implicit and tag: so we should read:

[filter]
   [and] #implicit
       has_weapon=sword
       side=1
       gender=female
   [/and]
   [or]
       side=2
   [/or]
[/filter]

and what we probably wanted is:

[filter]
    has_weapon=sword
    gender=female
    [and]
        side=1
        [or]
            side=2
        [/or]
    [/and]
[/filter]

Note that it could be written:

[filter]
    [and]
        has_weapon=sword
        gender=female
    [/and]
    [and]
        side=1
        [or]
           side=2
        [/or]
    [/and]
[/filter]

This syntax is correct and you can use it if you find it clearer. Remembering this implicit and tag (and writing it explicitly at will) is very important to understand or design complex filters.


Writing complex filters.

Here are some guidelines one can use when writing complex filters. Suppose we want to set up some kind of disease aura harming units standing close to some villages. We shall start writing a sentence describing the feature.

We want to select units who:
    Are enemy to side 1
        stand on hexes which
            are near to
                villages
                    with side 1 units standing on it

Mark we put only one condition on each line to clearly separate them. Mark we sorted them, because some apply to units to be filtered, others to locations, and finally to other (enemy) units standing on locations. Next, we shall add logical operators and parenthesis to clearly specify what we want:

Units, who (
    Are enemy to side 1 AND 
    stand on locations which (
        (	Are villages AND
            have unit on it who (
                Belongs to side 1
            )
         ) OR
         are adjacent to THOSE villages radius 3
    )		
)

Now, we are ready to start building the filter. Since we want units who… it shall be a unit filter. In the standard unit filter, there’s no condition directly allowing to state the unit is enemy to side 1. So we have to replace this with something valid in the SUF context. Using parenthesis to avoid errors, we can replace the condition with a side filter because it’s valid in unit filters.

Units, who (
    (belongs to a side which (
        is enemy to side 1) ) AND 
    stand on locations which (
        (	Are villages AND
            have unit on it who (
                Belongs to side 1
            )
        ) OR
        are adjacent to THOSE villages radius 3
     )		
)

Now we can write the filter. Here we do it step by step to show how the translation is rather straightforward and how our parenthesis match exactly the subtags.

[filter]
    [filter_side]
        [enemy_of]
            side=1
        [/enemy_of]
    [/filter_side]
           AND 
    stand on locations which ( # we start to filter locations there
           (	Are villages AND
               have unit on it who (
                   Belongs to side 1
               )
           ) OR
           are adjacent to THOSE villages radius 3
    )		
[/filter]
[filter]
    [filter_side]
        [enemy_of]
            side=1
        [/enemy_of]
    [/filter_side]
    [and] 
        [filter_location]
            terrain=*^V*
                AND
            have unit on it who (# to unit filter again
                Belongs to side 1
            )
            radius=3 # radius is a special case, see below
        [/filter_location]
    [/and]		
[/filter]
[filter]
    [filter_side]
        [enemy_of]
            side=1
        [/enemy_of]
    [/filter_side]
    [and] 
        [filter_location]
            terrain=*^V*
            [and]
                [filter]
                    side=1
                [/filter]
            [/and]
            radius=3 # radius is a special case, see below
        [/filter_location]
    [/and]		
[/filter]

Here, we are done. The filter should work as it is, but it looks rather unusual because all these [and] blocks. Actually we can delete most of them using a simple rule: [and] tags are not needed when they contain one single criterion or a single block, (except if you want to set up an evaluation order). In our example, the filter_location block is alone in its and tag, and the embedded filter as well, so we can avoid those and tags and get finally:

[event]
    name=moveto
    first_time_only=no
    [filter]
        [filter_side]
            [enemy_of]
                side=1
            [/enemy_of]
        [/filter_side]
        [filter_location]
            terrain=*^V*
            [filter]
                side=1
            [/filter]
            radius=3
        [/filter_location]
    [/filter]
    [harm_unit]
        [filter]
            id=$unit.id
        [/filter]
        amount=10
        animate=yes
    [/harm_unit]
[/event]


Filters uses: events actions and conditions.

Here, we shall deal with filter uses in WML. The language is not fully consistent, mainly to simplify its syntax, so some points can be misleading.

Using in actions

Most actions take a filter as first parameter. The main difficulty here is to know if [filter] tags must be used or not. Actually, they’re used to avoid confusing keys and criteria when they have the same name4. For instance, the [kill] action needs a unit filter and has these keys:

  • animate: if 'yes', displays the unit dying (fading away).
  • fire_event: if 'yes', triggers any appropriate 'die' events (See EventWML). Note that events are only fired for killed units that have been on the map (as opposed to recall list).
  • [secondary_unit] with a StandardUnitFilter as argument. Do not use a [filter] tag. Has an effect only if fire_event=yes. The first on-map unit matching the filter.

As we can see, none of these keys and tags are shared with unit filter keys and tags. This means the code parser needs no [filter] tag to know which key belongs to the filter and which to the action. But in the code, there’s no obvious distinction.

________________________________________

4) Note there’s no specific top level tag for location filters.