Part 8: Triggers

Currently viewing this thread:

Winter

Master Knight
Now that we have gained some significant experience working with the module system, we can turn our hands to one of the more complex components; Triggers. Triggers are time-based operations blocks that activate at regular intervals or at specified occasions. All operations in the trigger's operations block will then be executed.

8.1 -- Breakdown, Module_Simple_Triggers

Currently, module_simple_triggers has only one function: to direct the player to an appropriate menu when encountering another party on the overland map. To this effect, its Python list contains only one fairly long tuple.

  (ti_on_party_encounter,
   [
       (store_encountered_party, reg(1)),
       (store_encountered_party2,reg(2)), # encountered_party2 is set when we come across a battle or siege, otherwise it's a minus value
       (try_begin),
         (lt, reg(2),0), #Normal encounter. Not battle or siege.
         (try_begin),
           (ge, reg(1), towns_begin),
           (lt, reg(1), towns_end),
           (jump_to_menu, "mnu_town"),
         (else_try),
         (else_try),
           (ge, reg(1), castles_begin),
           (lt, reg(1), castles_end),
           (jump_to_menu, "mnu_castle"),
         (else_try),
           (eq, reg(1), "p_zendar"),
           (jump_to_menu, "mnu_zendar"),
         (else_try),
           (eq, reg(1), "p_salt_mine"),
           (jump_to_menu, "mnu_salt_mine"),
         (else_try),
           (eq, reg(1), "p_four_ways_inn"),
           (jump_to_menu, "mnu_four_ways_inn"),
         (else_try),
           (eq, reg(1), "p_dhorak_keep"),
           (jump_to_menu, "mnu_dhorak_keep"),
         (else_try),
           (eq, reg(1), "p_training_ground"),
           (jump_to_menu, "mnu_training_ground"),
         (else_try),
           (store_current_hours, reg(7)),
           (le, reg(7), "$defended_until_time"),
           (assign, "$defended_until_time", 0),
           (jump_to_menu, "mnu_in_castle_under_attack"),
         (end_try),
       (else_try), #Battle or siege
       (try_end)
    ]),


This is the first and only trigger in the list. It is a very simple construction, containing only two separate fields.

Breakdown of the tuple fields:

1 ) Check interval: When or how frequently this trigger will be checked
2 ) Operation block: This must be a valid operation block. See header_operations.py for reference.


Tuple examination:

1 ) Check interval = ti_on_party_encounter.
2 ) Operation block. Examination of the operation block will follow.

As we can see by ti_on_party_encounter, this trigger is checked whenever the player encounters a party on the overland map. We will now individually highlight each section of the tuple to examine what it does.


Section 1:

       (store_encountered_party, reg(1)),
       (store_encountered_party2,reg(2)), # encountered_party2 is set when we come across a battle or siege, otherwise it's a minus value


1 ) "store_encountered_party" finds whatever party the player has encountered, and stores that party's identity to the register "reg(1)".
2 ) "store_encountered_party2" does the same, storing the second encountered party to "reg(2)". Obviously this only occurs if the player is encountering a battle or siege in progress. If there is no second party, "reg(2)" will contain a value below 0.


Section 2:

       (try_begin),
         (lt, reg(2),0), #Normal encounter. Not battle or siege.


The condition (lt, reg(2),0) requires "reg(2)" to be below 0. Since "reg(2)" is always below 0 unless the player has encountered a battle in progress, the operations block will continue to Section 3.

The interesting thing to note about this section is the operation (try_begin). This operation opens a try operation, which functions just like a normal operations block, except for one difference. If a try operation contains a condition whose requirements are not met, this doesn't cancel the entire rest of the operations block; it only cancels up to the nearest (try_end). Everything after the (try_end) proceeds as normal. In essence, a try operation functions like an isolated operations block inside an operations block.

There is also the additional try operation (else_try), which can be inserted into an active try operation such as the one in module_simple_triggers. The contents of an else_try block will only be considered for execution if all active try operations before the (else_try) (including other else_trys) have failed to meet their conditions.


Section 3:

         (try_begin),
           (ge, reg(1), towns_begin),
           (lt, reg(1), towns_end),
           (jump_to_menu, "mnu_town"),


This section begins a try operation inside a try operation. This section will succeed if "reg(1)" -- the encountered party -- is between the constant towns_begin ("p_town_1" in module_parties.py) and towns_end ("p_salt_mine" in module_parties). If this condition succeeds, the player will be directed to the menu "mnu_town". If it fails, the block will move on to Section 4.


Section 4:

         (else_try),
           (ge, reg(1), castles_begin),
           (lt, reg(1), castles_end),
           (jump_to_menu, "mnu_castle"),


This section will only be considered if the try operation above it has failed; therefore, it will only be considered if the encountered party is not a town. So, having concluded that the encountered party is not a town, this section checks if it is between castles_begin ("p_castle_1") and castles_end ("p_river_pirate_spawn_point"). If this try operation succeeds, the player is directed to "mnu_castle". If it fails, the block moves on to Section 5.


Section 5:

         (else_try),
           (eq, reg(1), "p_zendar"),
           (jump_to_menu, "mnu_zendar"),


Having concluded that the encountered party is neither a town nor a castle, this try operation checks if the encountered party is "p_zendar". If it succeeds, this try operation will jump the player to "mnu_zendar". If it fails, on to Section 6.


Section 6:

         (else_try),
           (eq, reg(1), "p_salt_mine"),
           (jump_to_menu, "mnu_salt_mine"),


If the encountered party is "p_salt_mine", this try operation will jump the player to "mnu_salt_mine".


Section 7:

         (else_try),
           (eq, reg(1), "p_four_ways_inn"),
           (jump_to_menu, "mnu_four_ways_inn"),


If the encountered party is "p_four_ways_inn", this section will jump the player to "mnu_four_ways_inn".


Section 8:

         (else_try),
           (eq, reg(1), "p_dhorak_keep"),
           (jump_to_menu, "mnu_dhorak_keep"),


If the encountered party is "p_dhorak_keep", this section will jump the player to "mnu_dhorak_keep".


Section 9:

         (else_try),
           (eq, reg(1), "p_training_ground"),
           (jump_to_menu, "mnu_training_ground"),


If the encountered party is "p_training_ground", this section will jump the player to "mnu_training_ground".


Section 10:

         (else_try),
           (store_current_hours, reg(7)),
           (le, reg(7), "$defended_until_time"),
           (assign, "$defended_until_time", 0),
           (jump_to_menu, "mnu_in_castle_under_attack"),


This section stores the number of hours that have passed since the beginning of the game to "reg(7)", and then checks if "reg(7)" is less than or equal to the variable "$defended_until_time". On success, it will set "$defended_until_time" to 0 and jump the player to "mnu_in_castle_under_attack". On faillure, it will move on to Section 11.


Section 11:

         (end_try),

This signifies the end of the second try operation. The first try operation continues as normal, regardless of what happened in the second try operation.


Section 12:

       (else_try), #Battle or siege
       (try_end)


This else_try operation covers every encounter with a party engaged in battle with another party. Since it currently contains no other operations, it will result in a battle encounter -- just like the "new_town" we created in Part 2 of this documentation, which still has no operations linking it to a menu and no pf_auto_start_dialog flag set. We will rectify this in the next segment.


8.2 -- Editing the Party Encounters Trigger

In order to make a town work, we have to make sure that any encounter with this town directs the player to the proper game menu. This leaves us with two choices; either we can direct our new party to use one of the Native menus, such as mnu_town, or we can create a new one.

For our new_town, we will create a custom menu, but later we will examine what's required for a newly-created town to use the Native town menu.

First, any further entries in the party encounters trigger should be added before Section 10:

         (else_try),
           (store_current_hours, reg(7)),
           (le, reg(7), "$defended_until_time"),
           (assign, "$defended_until_time", 0),
           (jump_to_menu, "mnu_in_castle_under_attack"),


If you add any entries after this one, it will create problems when encountering your new parties at midnight. To make sure this doesn't happen, we will place our new entry just above it, and just below Section 9:

         (else_try),
           (eq, reg(1), "p_training_ground"),
           (jump_to_menu, "mnu_training_ground"),


Create some space between Section 9 and Section 10. Once you've done so, we can begin creating the new entry.

Any new entries in this trigger must begin with an else_try. After the else_try, we can add condition operations to specify when this entry should and should not fire. Then, once all the conditions are in place, we can add consequences, such as jumping the player to a game menu. These consequences can be very varied, they're not limited merely to jump_to_menu, but they must all be valid operations.

Copy and paste Section 9 (the training_ground entry) into our newly-created space. When you've done so, replace the new entry's "p_training_ground" with "p_new_town" and "mnu_training_ground" with "mnu_new_town".

Now the only thing left to do to make our town operational is creating a menu for it. Before we move on to module_game_menus, however, there is a lot more functionality waiting to be explored in triggers. Open module_triggers.py now and move on the next segment.


8.3 -- Breakdown, Module_Triggers

There are two types of triggers in the M&B module system: simple triggers and expanded triggers. Expanded triggers are contained in module_triggers.py. They work via the same theory as simple triggers, but have several more options.


  (0.1, 0, ti_once, [(map_free,0)], [(tutorial_box,"str_tutorial_map1")]),

The first trigger in this file is nice and simple. It is the trigger that pops up the map tutorial when the player first spawns on the overland map.


Breakdown of the tuple fields:

1) Check interval: How frequently this trigger will be checked.
2) Delay interval: How long to wait before applying the consequences of the trigger after its conditions block has succeeded.
3) Re-arm interval: How much time must pass after applying the consequences of the trigger for the trigger to become active again.
4) Conditions block. This must be a valid operation block. If the conditions block is empty, it always succeeds, therefore the trigger always fires.
5) Consequences block. This must be a valid operation block.


Tutorial tuple examination:

1) Check interval = 0.1
2) Delay interval = 0
3) Re-arm interval = ti_once
4) Conditions block = (map_free,0)
5) Consequences block = (tutorial_box,"str_tutorial_map1")


As we can see from the examination, this trigger is checked every 0.1 hours of game time; it has no delay interval; it never rearms due to ti_once. This means that, if the conditions in the trigger's conditions block are all met, the trigger fires once and never again. In this case, the trigger will fire if the player is placed anywhere on the map.

We can now begin to make a trigger of our own. Copy the tutorial trigger and paste it to the bottom of the Python list.

What we are going to do with this new trigger is spawn another party on the map; namely, Geoffrey's party. Replace (map_free,0) in the trigger's conditions block with the following operations:

(eq,"$geoffrey_duel",1),
(store_time_of_day,reg(1)),
(gt,reg(1),19),


If you remember what we did in Part 7 of this doscumentation, we assigned the variable "$geoffrey_duel" to 1 after you talk Geoffrey into challenging you to a duel. Therefore, (eq,"$geoffrey_duel",1) checks whether or not Geoffrey has challenged you. If he hasn't, the conditions block will fail.

The following two operations store the time of day to reg(1), and then check if reg(1) is greater than 19; essentially, whether the time of day is later than 19:00. If it is, the conditions block will succeed and allow the trigger to fire.


Now, replace (tutorial_box,"str_tutorial_map1") in the consequences block with the following operations:

(set_spawn_radius,0),
(spawn_around_party,"p_zendar","pt_new_template"),
(assign,"$geoffrey_party_id",reg(0)),
(party_set_ai_behavior,"$geoffrey_party_id",ai_bhvr_track_party),
(party_set_ai_object,"$geoffrey_party_id","p_main_party"),


The first operation sets the spawn radius; you must do this every time you want to spawn a party, or the party will be spawned at the most recently set radius, which can be very unpredictable.

The second operation spawns a party template ("pt_new_template") around a party ("p_zendar"). This operation also outputs the identifier of the spawned party to reg(0). We then assign this identifier to a variable so that it will not be lost when reg(0) is next overwritten.

Lastly, we set the party's AI behaviour and object. These operations are fairly straightforward. We are telling "$geoffrey_party_id" to track "p_main_party". When you set a party to track another party, it will relentlessly hunt down the other party and then attack it, regardless of faction and other considerations.


Save your progress. You cannot compile yet, as we have yet to create a menu for the party encounter we created in segment 8.2. We'll do this next. You are now ready to move on to Part 9 of this documentation.


Changelog, 13 July, 23:59 -- Formatting fixes and a few clarifications.
Changelog, 18 July, 07:26 -- Further formatting and spelling fixes.
Changelog, 14 September, 23:26 -- Fixed accidental Brit spelling in code in segment 8.3.
 
Top Bottom