An Introduction to Module System Syntax and Usage

Users who are viewing this thread

There are some very useful threads and guides that provide an introduction to the Module System and how to begin using it to mod every bit of Mount&Blade (Warband), but I have yet to come upon one that addresses the very basics of the operations and syntax used by the code structure of the Module System. Rather, short single question threads abound and exchanges in the Q&A Thread serve to educate aspiring modders/coders in the workings of the system. While I am by no means the most qualified member of this community to intelligently, comprehensively and effectively explain the syntax used in the Module System, I thought it couldn't do much harm to make an attempt.



ModuleSystem Downloads
Updated header_operations file for Warband by Lav (get it!)



The Basics
A listing of all of the valid commands and operations that can be used in the module system is found in the file header_operations.py. It includes comments (following the # sign) that may give a hint as to each operations use, but also identify what arguments need to be included with each operation. Every operation used in the module system takes the following form:
1. It is enclosed in parentheses: (operation)
2. It is immediately followed by a comma: (operation),
3. Arguments within the operation parentheses are separated by a comma: (operation, argument, argument),

Additionally, the header_operations file assigns a numerical code to each of the operations listed there. For instance, call_script = 1. When the game gives errors, they are in the form of SCRIPT ERROR ON OPCODE ##:....OPCODE number provided refers to the number assigned to the operation in header_operations. So, if the error was with OPCODE1, the line of code in question used the operation call_script.

Items contained within the various module files are indexed, that is assigned an ordinal numeric value based on the order they are found in the file. These indexes begin with 0 and count by 1 to each next value. Thus, troops, items, triggers, scenes, etc, once compiled into .txt files, are referred to by their index value. So, if you see an error code in "trigger 0", that is the first trigger in the indicated file, and if the error is in line 4, you are looking for the fifth operation within that trigger.

Local variables - ":variable" - variables declared with a : are local variables. They only exist in the current code block--enclosed by brackets [ ] --and cannot be referenced in other code blocks unless passed as script parameters. One should initialize them (to 0 or another suitable value) before they are used as they main contain a value already. They are enclosed with quotations " ". Ex: ":agent_id"
Global variables - "$variable" - variables declared with a $ are global variables. They are available to be referenced in any part of the code, from any module_*, once they are defined. Their default value is 0. They are enclosed in quotations " ". Ex: "$g_talk_troop"
Registers - reg(#) OR reg# - are variables used by the engine to store bits of information temporarily. They are global in scope, but may be accessed by a variety of different scripts for their duration and are then left for another bit of code to use them. In header_common, registers reg0 to reg65 are declared.
Strings - s# - are specific 'registers' used to hold a string, rather than an integer. Strings not declared in module_strings (quick strings) begin with the @ symbol. In header_common strings s0 to s67 are declared.
Positions - pos# - Unlike the previous two "registers", positions store more than one piece of information; each position stores X, Y, Z coordinates as well as rotations on each X, Y, and Z axis. In header_common positions pos0 to pos64 are declared, with pos64 beginning an unspecified range of positions used by siege towers (belfries).
Constants - constant - are constants that are declared in module_constants, maintain the numerical value assigned there, and cannot be altered save through editing module_constants. They can be called from any module_* file and any code block. Constants are not enclosed with quotations.

Local and global variables, as well as registers, are declared with the (assign, <variable_name>, <variable_value>), operation. The variable value field can be another variable name. Strings and positions have their own unique operations, as defined in header_operations
Within the Module System (eg module_mission_templates), one can refer to code or information stored in other files of the Module System by using a prefix before the label for the piece of code.
  • module_animations: "anim_"
  • module_factions: "fac_"
  • module_info_pages: "ip_"
  • module_items: "itm_"
  • module_map_icons: "icon_"
  • module_game_menus: "menu_"
  • module_meshes: "mesh_"
  • module_mission_templates: "mst_"
  • module_particle_systems: "psys_"
  • module_parties: "p_"
  • module_party_templates: "pt_"
  • module_pstfx_params: "pfx_"
  • module_presentations: "prsnt_"
  • module_quests: "qst_"
  • module_scene_props: "spr_"
  • module_scenes: "scn_"
  • module_scripts: "script_"
  • module_skills: "skl_"
  • module_sounds: "snd_"
  • module_strings: "str_"
  • module_tableau_materials: "tableau_"
  • module_troops: "trp_"
Most of the module_* files contain self-contained code for the most part. Files such as _skills, _troops, _items etc simply define these various elements for the game. module_game_menus contains all of the various menus, their options etc. Presentations has all of the more complicated presentation-drawing data, etc.

But module_scripts is referenced from many of these files--it contains "common" functions to be called upon by every part of the game code. You can consider (call_script, "script_scriptname", <optional_aruments>), a Goto:, Jump, function call, what have you.

To fascilitate this process, the call script operation allows information to be passed from the current code to the script without using global variables. These arguments or parameters can be local variables generated by the current bit of code. The script code in module_scripts will then begin by storing the parameters passed to it into local variables for use throughout the script. Scripts cannot pass information back to the code they were called from in the same way though...to do this, registers are typically used. In the calling code, once the script has completed, these registers are recorded to local variables for ease of use. Up to 15 script parameters can be passed with any one call.

Example:
Code:
//...statements...//
(assign, ":local_variable_1", 10),
(assign, ":local_variable_2", 5),

(call_script, "script_example_script", ":local_variable_1", ":local_variable_2"),

(assign, ":local_variable_3", reg0), #local_variable_3 = 15

//...further stuff, working with local_variable_3...//
Code:
  # script_example_script
  # Input: variable_1, variable_2
  # Output: Sum in reg0
  ("example_script", [
     (store_script_param, ":num1", 1), #now num1 is 10
     (store_script_param, ":num2", 2), #now num2 is 5

     (store_add, ":sum", ":num1", ":num2"),

     (assign, reg0, ":sum"),
  ]),



Terminology
Troops refer to the entries in module_troops by the name assigned to them there. "trp_player" is the player, "trp_npc##" for the various companions, "trp_knight_#_##" for lords, and "trp_swadian_footman" etc, etc. With more generic soldiers, the entry in module_troops is used as a "template" to create multiple instances of them in each party, etc.

Alternatively, agents refer to the specific actors, by number, in a scene (whether that scene is a battle, a tavern or what have you). Agents are created from the templates in module_troops...for the player, companions, NPC lords, they are still unique, but with generic soldiers multiple agents can be created from a single troop template. Each agent has a unique number for that scene, but when the scene ends, the agent doesn't exist any more so its agent id is meaningless.
A "party" is any entity on the world map, be that the player's party, bandits and their lairs, NPC lords, or a town/castle/village. The different types of parties are separated into categories in the first party slot "slot_party_type" (or party slot 0--see the discussion of slots below), which contains an integer associated with a certain type. These types are defined in module_constants and begin with the heading spt_* (for slot_party_type). Some examples of spt_* values are spt_kingdom_hero_party (for NPC lords) and spt_castle (...for castles).

Each party has an ID number that it is referenced by in the code. For instance, the player's party is "p_main_party" which has an index value or Party ID of 0. Certain parties have constant or static ID numbers, such as the player's party and all towns, castles and villages. Those parties with static IDs are defined in module_parties. You can see their ID number in the file ID_parties.py after you compile your module.

For all parties NOT defined in module_parties, these are created, dynamically, by the game based on a party_template, in a manner somewhat parallel to the agent/troop division above. These party templates are defined in module_party_templates and have names that are referenced with the prefix "pt_*". The template contains, primarily, a list of troops that are contained in that template and a numeric range that is used to determine how many of each troop type will be given to that template in game (the engine randomly picks a number within that range). A party template can be used to create a new party with the command <spawn_party>. This new party is assigned an ID number and then can be classified with a slot_party_type value, etc. Party templates can also be used with already existing parties...one can add a template (the troops defined in that template) to another party to "reinforce" an existing party in this way.
Slots are essentially ways to have variables arrayed by an object (party, troop, agent, team, faction, scene, item, player, scene prop, or party template). Each party, troop, agent, item etc has a number of "slots" (variables) assigned to it. That variable has a common name "slot_item_SLOTNAME" for each version of the object is assigned to (items in this case), which is useful, but stores unique information for each object. Where it looks like a "constant" is the fact that the engine turns the name of the arrayed variable (the slot) into a number in module_constants (slot_item_base_price = 53). This means that the engine looks at the 53rd slot-variable associated with a given item to find that item's base price.

So, when you see operations using slots such as (item_get_slot, ":base_price", ":item_id", slot_item_base_price),
you can see that you need to specify exactly which item you want to get the base price for. But, since the variable is arrayed, you could embed that in a try_for_* loop and iterate through many ":item_id"s to do things to the base price of every item, say.

You can access the price for a specific item with the item_get_slot operation, change it via item_set_slot, and test for equality with item_slot_eq and the like.

If you know C-like programming languages, slots work a lot like this:
slot_troop_spouse
spouse[troop1] = spousename
spouse[troop2] = spousename
spouse[trp_player] = spousename
which the engine reads as troop_variable_30[troop1], troop_variable30[trp_player] etc since slot_troop_spouse is set as slot 30 in constants.

slot_item_base_price
baseprice[item1] = priceA
baseprice[item2] = priceB
baseprice[item3] = priceC
and the engine reads that as item_variable_53[item1], item_variable_53[item2] etc...since it is set as 53 in constants

Before being set with a *_set_slot operation, the value of all slots is 0.



Control and Conditional Operations and Syntax
In general, it is imperitive to note that the game engine treats every conditional operation as an "IF" and all of the code that follows after a conditional operation as the "THEN" in an If-Then statement. As the engine goes through lines of code, it will stop any time a conditional operation fails, and it will not read the remainder of the code.

To isolate If-Then(-Else) statements to allow failure but continue processing the remainder of the code, one must use a "try block" with (try_begin), (else_try), and (try_end), This is discussed in more detail below.
(eq,<value>,<value>), Does Value 1 = Value 2?
(gt,<value>,<value>), Is Value 1 > Value 2?
(ge,<value>,<value>), Is Value 1 >= Value 2?
(lt,<value>,<value>), Is Value 1 < Value 2?
(le,<value>,<value>), Is Value 1 <= Value 2?
(neq,<value>,<value>), Does Value 1 != Value 2?
(is_between,<value>,<lower_bound>,<upper_bound>), Is Lower <= Value < Upper?

Also, any operation that includes "_is_" can be considered a true-false condition test.
For instance, (agent_is_alive,<agent_id>), will do a conditional test to check if the agent ID provided is alive. If the test is true, the code continues. If it is false, the code stops.

To test for "FALSE" rather than "TRUE" in any "_is_" T/F conditional test, use the following:
(neg|<operation>),
From our previous example, (neg|agent_is_alive,<agent_id>), the code will only continue if the agent identified by the ID number is dead (NOT alive).
(try_begin),
(try_end),
(else_try),
(try_for_range,<destination>,<lower_bound>,<upper_bound>),
(try_for_range_backwards,<destination>,<lower_bound>,<upper_bound>),
(try_for_parties,<destination>),
(try_for_agents,<destination>),

Destination is the variable that will be iterated in a loop.
The iteration will begin at the lower bound and go to Upper Bound -1
Since, as discussed above, the engine treats every conditional operation as the "IF" in an If-Then and all the code beneath it as the "THEN", the boolean AND can be accomplished simply by stringing multiple conditional operations after one another.
For instance, if one wanted to locate an agent who is alive AND a horse AND an enemy, the code would simply be:
(agent_is_alive,<agent_id>),
(neg|agent_is_human,<agent_id>),
(neg|agent_is_ally,<agent_id>),

To check for one condition OR another, the following is used:
(this_or_next|<operation>),
By our previous logic, it can be strung together multiple times as well.
For instance, to see if a variable is 1 OR 5 and, if so, continue:
(this_or_next|eq, ":test_variable", 1),
(eq, ":test_variable", 5),
Since all condition operations apply to every line of code beneath them, there needs to be a way to separate portions of code so the condition operation only applies to one section and then the code continues on regardless of the result of the test.
To do this, one uses a "try block", code enclosed by (try_begin) and (try_end) One can think of them as creating a typical If-Then statement, with the first lines of code after the (try_begin) being the "If" in the form of conditions tests and the following lines being the "Then" with consequence operations, which are closed by the "End If" of (try_end)
As in all good If-Thens, (else_try) emerges as the "Else If" statement. At any point that a condition fails in the try before an (else_try) the engine will begin looking in the subsequent (else_try).

Example:
Code:
#Code above, doing whatever
(try_begin),
    (eq, 1, 1), #True, go to next line
    (gt, 2, 5), #False, go to else try
    #consequence operations here
(else_try),
    (eq, 0, 1), #False, go to next else try
    #consequence operations here
(else_try),
    (eq, ":favorite_lance", ":jousting"), # Maybe?
    (assign, ":prisoners", 100),
(try_end),
#Code continuing on, regardless of whether the favorite lance=jousting and prisoners was set to 100
The M&B Module system has a number of different variations of the For-Next loop. The most basic is accomplished with a (try_for_range, ":iterator", <lower bound>, <upper bound>), then looped operations and capped by a (try_end), instead of a "Next iterator" or what have you.
The iteration will begin at the lower bound and go to Upper Bound -1, making single integer steps, counted by the variable assigned as destination, in the example above the local variable ":iterator". The ":iterator" must be used in the code that follows in the try_for_range block, but if it cannot be altered to skip integer-step iterations toward upper bound -1. If the ":iterator" is not used in the code that follows the variable ":unused" MUST be used in its place.

Example:
Code:
(try_for_range, ":i", 0, 10),
    (store_add, ":count", ":i", 1),
    (assign, reg0, ":count"),
    (display_message, "@{reg0}"),
(try_end),
#This code will display a message on screen counting from 1 to 10.

There is also (try_for_range_backwards, ":iterator", <lower bound>, <upper bound>), where the iteration will begin at upper bound-1 and count down to lower bound. Ignore the comments in header_operations that say to switch the order of the lower and upper bounds. They are wrong. This is useful for removing something from a list (members from a party, for instance) without messing up the indexing of that list.
Note, that both the lower bound and the upper bound need not be a "plain number"; they can easily be variables that are set earlier in the code. Examples of this in the next section.

Two more specific (try_for_*) loops exist: (try_for_agents, <agent_id>), and (try_for_parties, <party_id>). These will cycle through all agents in a scene or all parties in the game, respectively. The iterator represents the Agent's (or Party's) ID number and should be stored in a local variable.

Example:
Code:
(get_player_agent_no, ":player"),
(agent_get_team, ":playerteam", ":player"),
(try_for_agents, ":agent"), #Loops through all agents
    (agent_is_alive, ":agent"), #Checks if current agent is alive
    (agent_is_human, ":agent"), #If alive, checks if current agent is human (not a horse)
    (agent_is_non_player, ":agent"), #If alive and human, checks that the current agent isn't the player
    (agent_get_team, ":team", ":agent"), #If alive, human and non-player, gets the current agent's team.
    (eq, ":team", ":playerteam"), #Checks that the current alive, human, non-player agent is on the player's team.
    #Consequence operations go here to do stuff with this alive, human, non player, ally agent.
(try_end), #Go to next agent
Many times you may want to loop through all agents to locate one particular agent, or loop through another range of things (items for instance) to locate one particular weapon, and there is no need to loop through the rest--you've already found what you needed. Enter the loop break. The M&B Module System does not have any operations specifically to accomplish this, but there are a few simple tricks to effectively break a loop. The trick to use depends on the type of Module System (try_for_*) loop you are using.

Method 1: Changing the Loop's End
-Breaking a try_for_range loop
You can break a try_for_range loop easily by changing the upper bound so it equals the lower bound. That way, when the engine tries to iterate to the next instance of the loop, it appears the loop has already reached the last iteration and is complete. The loop will exit without running the code again.
To do this, the loop's upper bound must be a variable defined before the loop begins, and it must be variable that can be modified without loss of data--make a new variable or a copy of another if necessary. Once the code has been executed enough times, something has been found, etc, set the variable for the end of the loop to equal the loop's lower bound (either assign it the variable value or the constant value that the loop began with).

Example:
Code:
#Within a larger (try_for_agents) loop
    (assign, ":end", "itm_glaive"),  #Set the loop's upper bound
    (try_for_range, ":item", "itm_jousting_lance",":end"), #Loop through the lances in the game
        (agent_has_item_equipped, ":agent", ":item"), #Check if the agent has the current lance equipped
        (agent_set_wielded_item, ":agent", ":item"), #If the current lance is equipped, make the current agent wield it
        (assign, ":end", "itm_jousting_lance"), #loop breaker - make the upper bound equal the lower bound - stop looking through the lances
    (try_end),
#Continue on within the try_for_agents loop
-Breaking a try_for_range_backwards loop
The same method is used for a backwards loop. This time, however, the lower bound is the "loop's end" so the lower bound will need to be the variable changed, and it should be changed to equal the upper bound.

Example:
Code:
#Code above...sets the variable ":troop" to some value
    (assign, ":array_begin", 0), #Set the loop's lower bound
    (try_for_range_backwards, ":i", ":array_begin", 10), #Loop from 0 to 10
        (party_slot_eq, "p_main_party_backup", ":i", 0), #Check if the ith party slot for the player's party backup equals 0
        (party_set_slot, "p_main_party_backup", ":i", ":troop"), #If it does, set that slot to the variable troop's value
        (assign, ":array_begin", 10), #Loop breaker - make lower bound equal the upper bound - don't check other slots
    (try_end),
Method 2: Using an Condition Test
-Breaking a try_for_agents or _parties loop
With try_for_agents and try_for_parties loops, it is not possible to change the loop's "upper bound". Instead, an conditional test (typically for equality) is used at the start of the code to execute within the loop. So long as it is true, the code within the loop will run. Once it is set false, the loop will keep going, but nothing inside the loop will happen since the first condition always fails--the loop will end quickly.
To do this, set a variable up before the loop with a given value (it is easiest to create a new variable with the value 0). Then, just inside the loop, set up an conditional operation testing for that value. After the block of code you want only ran once, change the value of the variable so the condition test fails the next time the loop runs.

Example:
Code:
#Code above...sets pos1 to some value   
    (assign, ":break_loop", 0), #Set up variable to break loop and a value to test
    (try_for_agents, ":agent"), #Loop through all agents
        (eq, ":break_loop", 0), #See if the loop-breaker is still true
        (agent_is_alive, ":agent"), #If so, keep going; check if the current agent is alive
        (agent_is_non_player, ":agent"), #If alive, check it isn't the player
        (agent_is_human, ":agent"), #If alive and not the player, check that is is human
        (agent_get_position, pos0, ":agent"), #If alive, human and non-player, get it's location and record to pos0
        (get_distance_between_positions_in_meters, ":distance_to_target_pos", pos0, pos1), #See how far pos0 is from pos1 and record that in the local variable
        (lt, ":distance_to_target_pos", 10), #Check if the alive, human non-player agent is within 10 meters of pos1
        (assign, ":agent_at_target", ":agent"), #If so, record this current agent's ID to the local variable "agent_at_target"
        (assign, ":break_loop", 1), #Agent Loop Breaker - change the test variable so the equality test fails on the next try
    (try_end),
#The loop will break the first time an alive, human, non-player agent is found within 10 meters of the target position pos1
#Further code can now do stuff with the agent that was found



Other Topics
A trigger in mission templates (and in module_triggers for that matter) takes the following form
First Part: how often the trigger is checked, either in seconds* or as a hard-coded event such as "ti_on_agent_hit"
Second part: the delay, in seconds*, between reading the conditions block and executing the consequences block. This does not work if you are using a "word"/hard-coded event interval, such as "ti_on_agent_hit"
Third part: the re-arm delay, in seconds*, between when the consequences block was finished and when the trigger can be checked again. A special re-arm delay "ti_once" is used for triggers that should only complete themselves (have their condition pass and then execute their consequence) once and then never fire again.
Fourth part: the conditions block, always executed when the trigger is called
Fifth part: the consequences block, only executed if the conditions block does not fail, and after the delay interval has passed.
*Note, unlike other numbers in the module system, the 3 intervals/delays of triggers do NOT need to be integers.

In practice, it looks like this:
Code:
(10, 2, 60,
   [
    (eq, 1, 1),
   ],
   [
    (val_add, "$global_variable", 10),
   ]),
In this instance, the check interval is every 10 seconds, so the trigger will be checked every 10 seconds. The delay interval is 2 seconds, so if the conditions block is true, the consequences block will fire 2 seconds later. The re-arm interval is 60 seconds, so after the conditions block is found to be true, it will be one minute until this trigger can be checked again.

The conditions block is a simple equality test 1==1, that will always pass, so the consequences block will always fire 2 seconds after the trigger is checked. The consequences block then takes a global variable and increments it by 10.

Timed Triggers
Understanding the check interval/delay/re-arm intervals of timed triggers can be tricky so here is an illustration:
Code:
(2, 1, 3, [<a condition that sometimes fails, sometimes passes>],
   [
    #some consequences
   ]),

This trigger, like all timed triggers with a check interval >0, will start to be checked around the first 1 second of the mission:
Code:
Second      Event  
1                Trigger checked; condition fails--apply check interval (+2 seconds)
3                Trigger checked; condition fails--apply check interval (+2 seconds)
5                Trigger checked; condition passes--apply consequence delay (+1 second)
6                Consequence fires and completes--apply check interval (+2); apply re-arm delay (+3)
11              Trigger checked; condition fails--apply check interval (+2 seconds)
13              Trigger checked; condition passes--apply consequence delay (+1 second)
14              Consequence fires and completes--apply check interval (+2); apply re-arm delay (+3)
19              Trigger checked....
So, although we have specified a "check interval" of 2 seconds, we see that the trigger is not checked only on even seconds; instead it is checked in seconds 1, 3, 5, 11, 13, 19.

If one wants a completely "scheduled" trigger, both consequence and re-arm delays cannot be used.

Two Triggers of the Same Type/Interval
When there are two triggers of that have the same check interval (be that in seconds, or on an event ti_*) the order they appear in the mission template matters. The trigger that appears first in the template will fire first, followed by the next trigger of the same interval, and so on. That means if you have two triggers that fire ti_on_agent_spawn, the one that appears in the file first will execute before the second one.

Triggers near the beginning of a Mission
Logically, the trigger ti_before_mission_start takes place before the scene is set up and before agents are spawned into the scene. Next, spawning takes place before any other triggers fire--ti_on_agent_spawn triggers are the only triggers firing at this point. Next, ti_after_mission_start triggers fire, as well as any triggers with a check interval of 0 (every frame) as the mission timer starts. Event-based triggers will not be called until their ti_* event occurs; other timed triggers begin being called somewhere in the first second of the mission, though after ti_after_mission_start triggers and every frame (check interval 0) triggers.

If you have triggers that do not need to fire that close to mission start, adding something like
Code:
(store_mission_timer_a, reg0),(gt, reg0, <second-to-start-checking>),
to the conditions block will help ease the CPU load at mission start.

Neither ti_before_mission_start nor ti_after_mission start need "ti_once" in their re-arm delay as they will only ever fire one time.

To summarize, at the start of a mission we have:
ti_before_mission_start
--Spawning-- (ti_on_agent_spawn)
--Time Begins--
ti_after_mission_start & triggers with check intervals of "0" (which fire @ a mission time of approximately 0.2 seconds)
--Timed Triggers (still within the first 1 second of the mission)
--Event-based triggers
xenoargh said:
The first section like this:
Code:
"[ ...some code here... ]"
...is a block that exists to test a condition or series of conditions. It's only purpose is to return TRUE or FALSE. And every single Dialog gets checked until a TRUE result occurs.

Secondly, the parts after "start" can only be accessed IF "start" happened, their branch is active, AND their own condition block is TRUE. So, if you want something that always happens when Generalissimo EvilGuy meets your player on a Tuesday, but you want him to do something else on Wednesday, his "start" might look like so:

Code:
#Start of Dialog block
[
#See header_dialogs for what can be here besides "anyone".  All Dialogs must have a "start".
anyone, "start",
#Start condition block here
[
(eq, "$g_talk_troop", "trp_generalissimo_evilguy"),#ONLY RUN ME IF EVILGUY
(try_begin),
(eq, "$g_day_of_week", 2),#DO THIS TUESDAYS
(assign, "$g_conversation_temp",1),
(str_store_string, s17, "@This is my Tuesday conversation start"),
(else_try),
(eq, "$g_day_of_week", 3),#WEDNESDAYS
(assign, "$g_conversation_temp",2),
(str_store_string, s17, "@This is my Wednesday conversation start"),
(else_try),
(assign, "$g_conversation_temp",3),#IF ALL ELSE FAILS...
(str_store_string, s17, "@This is what I say any other day of the week"),
(try_end),
],
#Condition is TRUE; we're talking to EvilGuy, now we need our custom text:
"s17",
#Now we the Player to be able to say something, even if it's something prosaic like, "leave", to exit this Dialog.
"player_response_or_responses_here",
#Consequences, if any:
[],
#End of dialog block
],
xenoargh said:
I don't like doing Dialogues using a Troop in that first block; it makes it easier to accidentally create logic errors. I prefer to do it in the Conditions block, like so:

Code:
[anyone,"start", [(eq, "$g_talk_troop", "trp_player_castellan"),], "What can I do for you {playername}", "castellan_talk",[]],

That means that I can have multiple starts for that Troop, depending on the conditions block returning TRUE, like this:

Code:
[anyone,"start",
[
(eq, "$g_talk_troop", "trp_player_castellan"),
(eq, "$g_some_game_global", 1),
], "What can I do for you {playername}", "castellan_talk",[]],

[anyone,"start",
[
(eq, "$g_talk_troop", "trp_player_castellan"),
(eq, "$g_some_game_global", 2),
], "Oh, it's {playername}, that scummy devil, come to bother me again!", "castellan_talk",[]],

The only major exception to this is dealing with Party encounters where you may be talking to any random guy in the Party, for example, the Looter's start:

Code:
[party_tpl|pt_looters|auto_proceed,"start", [(eq,"$talk_context",tc_party_encounter),(encountered_party_is_attacker),], "{!}Warning: This line should never be displayed.", "looters_1",[
    (str_store_string, s11, "@It's your money or your life, {mate/girlie}. No sudden moves or we'll run you through."),
    (str_store_string, s12, "@Lucky for you, you caught me in a good mood. Give us all your coin and I might just let you live."),
    (str_store_string, s13, "@This a robbery, eh? I givin' you one chance to hand over everythin' you got, or me and my mates'll kill you. Understand?"),
    (store_random_in_range, ":random", 11, 14),
    (str_store_string_reg, s4, ":random"),
    (play_sound, "snd_encounter_looters")
  ]],

Note there that there are not two but four things that must be TRUE:

1. It's a Party encounter on the overworld.
2. The Party Template is pt_looters.
3. $talk_context == tc_party_encounter.
4. encountered_party_is_attacker must be TRUE



Other Reference Threads
Scattered across the sub-forum are numerous threads that address these topics. While I didn't take the time to isolate each of these discussions and create yet another thread of links, the following are the crucial pre-existing collections of information:



This is a work in progress. Addenda, suggestions, corrections, and similar are welcome.
 
Last edited by a moderator:
Very well written - it will probably be helpful to many people.
Also, you may also want to include a short section on party templates - their relation to parties is analogous to those of troops to agents. Expanding on strings (instead of stating look in header_operations) would also be nice - you could also lead into dialogs if you are so inclined.
 
Very nice. I looked for this sort of thread when learning how it worked and didn't find much, so mostly learnt by reading the native code... I've wondered about posting a thread like this, but have been plenty busy enough as it is; so thanks :smile:


A few more things that occur to me you might want to mention (maybe you just haven't got to them, and it's a bit jumbled sorry):
While you are correct that global variables ("$var") seem to be initialized to 0, in my experience local variables (":var") are not; you must assign them values in some way before reading from them, or they could contain some random number.
When code refers to entries from another file using a prefixed string (like "itm_saddle_horse" or "trp_looter") the module system converts it to a number at build time, which can be found in the corresponding ID_*.py file (like ID_items.py). So when the game gives an error message, a few things will be represented by strings, like the name of the script, but most things will be represented by their id number. You can add debugging messages to show the id of a troop or whatever, like:
Code:
(assign, reg10, ":troop_id_being_checked"),
(display_message, "@troop being checked is {reg10}"),
Then if the output was "troop being checked is 30" you can look in ID_troops.py and find the line "trp_hired_blade = 30". In this particular example you could also use:
Code:
(str_store_troop_name, s10, ":troop_id_being_checked"),
(display_message, "@troop being checked is {s10}"),
if you wanted it to show the name, though for debugging I mostly don't bother. When sending things over the network with a multiplayer mod, you can use this knowledge to avoid adding a new event type for every little thing you want to do (there are only 128 types available): by making the events generic, and sending the involved troops, items, strings (preset only, of the type "str_something"), sounds, or whatever else as int parameters.
Additional to the comments at the top of the module_* files, there are sometimes comments in the corresponding header_* file, or sometimes in another header; in addition to header_operations, header_triggers and header_common are examples of files with information in them relevant to a lot of different files.
 
Yeah, nice. I'm afraid it might be a bit too complicated to a beginner modder, but then again, it can't get any simpler...
 
As a rookie programmer, I'm finding this guide to be very helpful. I have no doubt that I'll be coming back to it many times in the near and far future.

Sinisterius said:
Yeah, nice. I'm afraid it might be a bit too complicated to a beginner modder, but then again, it can't get any simpler...

Something that may help people with little to no initial programming knowledge could be adding a basic terms and explanations section in spoiler form at the end (e.g. explaining that '!=' means 'not equal to'). In this way excessive explanation that would only hinder knowledgeable programmers wouldn't clog up the main part of the guide, but beginners could have a ready reference, allowing them smoother progress.
 
Extremely helpful! I vote sticky (or at least a reference in the stickies)!

Now if I could just find a list of what the different operations do...
 
This thread should be stickied.  It's helped me greatly in mentally transforming the MS into C#.  Thank you very much! 
 
13exa said:
So what is the function of that Global Variables??
The function of global variables depends on the context. Often they are used to track player-specific values across the game, cf. "$player_honor" throughout the mod sys, or some other single-dimension value that needs to be used in many places, cf. "$g_battle_won" in mission templates.

If you are trying to track a value for only a short period between code blocks, then a register is fine (so long as you pick one that you know won't be over-written). If the value is multi-dimensional, or needs to be tracked for many troops/parties/items, then a slot is a far better choice.
 
Caba`drin said:
13exa said:
So what is the function of that Global Variables??
The function of global variables depends on the context. Often they are used to track player-specific values across the game, cf. "$player_honor" throughout the mod sys, or some other single-dimension value that needs to be used in many places, cf. "$g_battle_won" in mission templates.

If you are trying to track a value for only a short period between code blocks, then a register is fine (so long as you pick one that you know won't be over-written). If the value is multi-dimensional, or needs to be tracked for many troops/parties/items, then a slot is a far better choice.
Well. I want to make a description of my problem.
Examples, I want to make a Game Menus that will give player an Item, but they are must be waiting for some hours. So i take Trade Assessment as Basic.

The Concept:
First, The Confirmation Menu about Waiting for 3 Hours
                                          V

            Rest for Hours = Waiting for Getting Item
                                          V
Last, The Confirmation that Player have the item on the Inventory


My problem is at the Red word. I can't make from Rest Hour to back to "Last, The Confirmation that Player have the item on the Inventory"

Have a solution, Caba' drin?
 
Looking in some module py files I see slots skip some numbers so there may be slot 21 and slot 23 but nothing slot 22.

Are unused slots "open" or is it okay to add slots?

 
if there is no slot = 22 then you can add a new one.
there can be a troop_slot = 22 and a party_slot = 22 without problems, the problem is two troop_slots with the same number
 
this thread is so useful, thank you very much, i've recently translated this to Turkish, for two reasons: to help Turkish people learn coding, to help myself learn it while translating :grin: (i was too lazy to read it in first place)
 
You could add a note on the mission template part, something like: in module_triggers, the check/delay and re-arms interval are in hours instead of seconds.
 
Caba`drin said:
 
Slots are essentially ways to have variables arrayed by an object (party, troop, agent, team, faction, scene, item, player, scene prop, or party template).  Each party, troop, agent, item etc has a number of "slots" (variables) assigned to it. That variable has a common name "slot_item_SLOTNAME" for each version of the object is assigned to (items in this case), which is useful, but stores unique information for each object. Where it looks like a "constant" is the fact that the engine turns the name of the arrayed variable (the slot) into a number in module_constants (slot_item_base_price = 53).  This means that the engine looks at the 53rd slot-variable associated with a given item to find that item's base price.

So, when you see operations using slots such as (item_get_slot, ":base_price", ":item_id", slot_item_base_price),
you can see that you need to specify exactly which item you want to get the base price for. But, since the variable is arrayed, you could embed that in a try_for_* loop and iterate through many ":item_id"s to do things to the base price of every item, say.

You can access the price for a specific item with the item_get_slot operation, change it via item_set_slot, and test for equality with item_slot_eq and the like.

If you know C-like programming languages, slots work a lot like this:
slot_troop_spouse
spouse[troop1] = spousename
spouse[troop2] = spousename
spouse[trp_player] = spousename
which the engine reads as troop_variable_30[troop1], troop_variable30[trp_player] etc since slot_troop_spouse is set as slot 30 in constants.

slot_item_base_price
baseprice[item1] = priceA
baseprice[item2] = priceB
baseprice[item3] = priceC
and the engine reads that as item_variable_53[item1], item_variable_53[item2] etc...since it is set as 53 in constants

Before being set with a *_set_slot operation, the value of all slots is 0.

You appear to be giving conflicting information in here. According to the description in the paragraph you're saying that slots can be thought of as an array contained in an individual object where each slot entry is an index in said array, but the examples you give are showing the slots as global arrays with the objects in question being used as the indexes.


I think you may have meant something like this:

troop1[slot_troop_spouse] = spousename
troop2[slot_troop_spouse] = spousename
trp_player[slot_troop_spouse] = spousename

and

item1[slot_item_base_price] = priceA
item2[slot_item_base_price] = priceB
item3[slot_item_base_price] = priceC


Just ignore me if I simply misread that and great topic by the way.
 
Kudos for this thread Caba'drin it's helped me a freaking lot when your not online to bug :razz:
 
Back
Top Bottom