Official Module System Documentation: Part 07
In this Part we will be examining module_dialogs.py, by far the largest file in the module system and one of the most important, as it contains all dialogue in Mount&Blade. Any new dialogue that you wish to create will go into this file. It also contains placeholder dialogue at the very bottom, which is used for troops that do not (yet) have their own dialogue.
Contents |
[edit] 7.1 -- Breakdown of Module_Dialogs
The file begins immediately with a Python list, followed by the first piece of dialogue in the game -- Constable Hareck's announcement regarding the river pirates. An important fact to remember is that the game scans module_dialogs from top to bottom, using the first line that meets all the criteria of its situation. It differentiates between lines used in different situations, from a party encounter on the map to chatting with an NPC in a scene.
You will note, each line of dialogue is an individual tuple. The only things connecting them are their dialog-states, which we will review in a moment.
Example of a dialogue tuple:
[trp_constable_hareck,"start", [], "What do you want?", "constable_hareck_talk",[]],
This is a wonderfully simple tuple, which will serve well as our example. It does everything a line of dialogue might do; it opens by accosting Constable Hareck in a scene, displays the text "What do you want?", and then segues into the starting dialog-state "constable_hareck_talk" upon mouse-click.
Tuple field breakdown:
1 ) Dialogue partner. This should match the person player is talking to. 2 ) Starting dialog-state. Determines how this line is opened. 3 ) Conditions block. The list of conditions that must be met for this line to be considered. This must be a valid operation block. 4 ) Dialogue text. The actual line as it will appear in-game. 5 ) Ending dialog-state. Determines what happens after this line. 6 ) Consequences block. The operations that take place after the player has clicked through this line. This must be a valid operation block.
Constable Hareck example tuple examination:
1 ) Dialogue partner = trp_constable_hareck 2 ) Starting dialog-state = "start" 3 ) Conditions block = [] 4 ) Dialogue text = "What do you want?" 5 ) Ending dialog-state = "constable_hareck_talk" 6 ) Consequences block = []
The most important things to note in this tuple are the dialog-states, mentioned in the opening paragraph of this segment. We will delve into these more deeply now.
The ending dialog-state ("constable_hareck_talk") is what leads a conversation from one line to the next. The ending dialog-state can be anything you like -- but there must be another tuple with a matching starting dialog-state. For example, if we were to make a tuple with the ending state "blue_oyster", this would lead into any tuple with the starting state "blue_oyster". There must be an exact match; if no match exists, build_module.bat will throw an error upon trying to build.
If there are multiple tuples with the starting state "blue_oyster", something special happens. If the tuples are spoken by the player, they result in a menu where the player can choose between the provided tuples. If spoken by an NPC, the module system will use the first tuple in module_dialogs for which all conditions are met -- even if there are multiple lines that qualify.
To end a conversation, you must use the ending dialog-state "close_window".
To start a conversation, there are several special starting dialog-states from which you can choose. We will refer to these as initial dialog-states. Here is the full list of initial dialog-states:
"start" -- Considered when speaking to an NPC in a scene or when a conversation is triggered inside a scene.
"party_encounter" -- Considered when encountering another party on the overland map.
"party_relieved" -- Considered when assisting a battling party on the overland map, once the player has won the fight.
"prisoner_liberated" -- Considered when the player defeats an enemy party with one or more Hero prisoners.
"enemy_defeated" -- Considered when the player defeats an enemy party led by a Hero.
"event_triggered" -- Considered when a dialogue is triggered by an operation while not in a scene.
"member_chat" -- Considered when speaking to a member in your party.
"prisoner_chat" -- Considered when speaking to a prisoner in your party.
As you can see, each initial dialog-state is designed for a specific situation. It will not be considered anywhere outside its situation.
Since our example tuple is initiated by speaking to Constable Hareck in Zendar, its initial dialog-state is "start".
[edit] 7.2 -- Requirements And The Conditions Block
The dialogue interface is very flexible, and can be used in a great number of ways. It handles both events on the overland map and events in scenes. It allows dialogues to be triggered whenever you want them.
In the following segments, we examine how to use the dialogue interface to its fullest, and after that we will learn how to create complex dialogues of our own.
As we have outlined in the last segment, a dialogue line will only be considered if all its requirements are met. First of all, the player must be speaking to the correct troop. A line with trp_constable_hareck will not be considered if the player is addressing trp_ramun_the_slave_trader. The constant anyone may be used if the line is to be spoken by anyone the player is addressing at the time.
Second, the starting dialog-state must be in accordance with the situation -- either the dialogue line is initiated by an initial dialog-state, or the line follows from a matching ending dialog-state in another line. If the starting dialog-state does not meet specifications, it won't be considered for use.
Thirdly, the same logic of the previous two points applies to the conditions block. Unless all of a line's conditions are met, the line will not be considered. If either the conditions or the starting dialog-state are erroneous, you will experience problems; either build_module.bat will throw an error, or the erroneous dialogue lines will simply freeze in-game, since their ending dialog-states will not be able to find another tuple to activate.
This is the reason why you must be careful with conditions blocks. Make sure you don't break your own dialogue by planting conditions which are not properly set.
However, when everything is working in harmony, conditions blocks can be very powerful. They can contain try blocks. You can call slots from inside a conditions block, and then use the result in a condition operation inside the same block. You can set registers and string registers in order to use them in the actual dialogue. We will do all of these things in this Part of the documentation, one at a time.
You can observe the use of a conditions block in the very first tuple of module_dialogs:
[trp_constable_hareck,"start", [(eq,"$constable_announcement",0)]
This block contains only one condition, which requires the variable "$constable_announcement" to be equal to 0. This is because all registers and variables are equal to 0 at the beginning of a new game. And if you look at the end of this tuple, you will notice the consequences block:
"constable_hareck_introduce_1",[(assign,"$constable_announcement",1)]]
The assign operation in this block sets the variable "$constable_announcement" to 1 after the line has been displayed. In other words, after this line has been displayed once, it will never be displayed again -- because afterwards, the variable "$constable_announcement" will no longer be equal to 0. The dialogue system will then ignore this line and instead go to the next tuple in the file which meets requirements:
[trp_constable_hareck,"start", [], "What do you want?", "constable_hareck_talk",[]],
The only requirements on this line are that the player must be speaking to Constable Hareck in a scene. It will always be selected when speaking to Constable Hareck in the Zendar centre after he has delivered his speech.
[edit] 7.3 -- Adding New Dialogue
Here we finally have a chance to give our new troop Geoffrey some unique dialogue. However, like module_troops, module_dialogs is another file where you cannot simply add more tuples at the bottom of the list. The end of module_dialogs's Python list contains placeholder conversations that will trigger for anyone at all, and because the file is scanned from top to bottom for a match, any dialogue that you add after the placeholder conversations will be completely ignored.
It is recommended that you add new dialogue before the following comment:
###COMPANIONS
Our first goal is to create an introduction for Geoffrey. This will allow us to gain a little bit of experience before we throw ourselves into the creation of a full-blown quest.
Copy the following tuple and paste it into the appropriate location in your module_dialogs:
[trp_geoffrey,"start", [], "What? What do you want? Leave me be, {sir/wench}, I have no time for beggars.", "geoffrey_talk",[]],
This is the first tuple in our new conversation. It's activated by the initial dialog-state "start", spoken by Geoffrey, and it leads into the starting dialog-state "geoffrey_talk". Also, the dialogue system will display the word 'sir' or 'wench' depending on the player's gender.
Next we will make a follow-up line, spoken by the player. Copy the following tuple and paste it into module_dialogs, just below the new tuple:
[trp_geoffrey|plyr,"geoffrey_talk", [], "Nothing, never you mind.", "close_window",[]],
This line will be displayed right after the first. It is spoken by the player. Due to the ending dialog-state "close_window", this conversation will end after the line has been displayed.
As we have covered before, adding multiple tuples with the same starting dialog-state will result in an option menu from which the player can choose the preferred line. Again, remember that this only works for tuples that are spoken by the player, and not for any other troop. Copy the following tuples and paste them into your module_dialogs file:
[trp_geoffrey|plyr,"geoffrey_talk", [], "And who are you supposed to be?", "geoffrey_talk_2",[]], [trp_geoffrey,"geoffrey_talk_2", [], "Why, I'm Geoffrey Eaglescourt, son of the Baron Eaglescourt! Leader of the Red Riders, bane of bandits, and crusher of pirates!", "geoffrey_talk_3",[]], [trp_geoffrey|plyr,"geoffrey_talk_3", [], "Oh, I see. And how many pirates have you killed?", "geoffrey_talk_4",[]], [trp_geoffrey,"geoffrey_talk_4", [], "See for yourself! I scalp every one of the dogs I kill. They are my battle trophies.", "geoffrey_talk_5",[]], [trp_geoffrey|plyr,"geoffrey_talk_5", [], "That's nice. I'll be going now.", "close_window",[]],
This is all part of one possible conversation path -- a small back-and-forth between Geoffrey and the player. Nothing important is happening yet, but we're about to add a little more variety to this exchange. Add the following two tuples to the dialogue:
[trp_geoffrey|plyr,"geoffrey_talk_5", [(check_quest_active,"qst_speak_with_troublemakers"),(eq,"$geoffrey_duel",0)], "Really? Those scalps look suspiciously like horse tails to me.", "geoffrey_hostile",[]], [trp_geoffrey|plyr,"geoffrey_talk", [(check_quest_active,"qst_speak_with_troublemakers"),(eq,"$geoffrey_duel",0)], "You look familiar. Haven't I seen your face in a pigsty before?", "geoffrey_hostile",[]],
These two lines will create extra menu choices for the starting dialog-states "geoffrey_talk_5" and "geoffrey_talk", respectively. It doesn't technically matter where in module_dialogs you place tuples with the same starting dialog-state. The dialogue system will find them all and, if conditions are met, add them to the menu options. However, you should try to keep menu tuples together in order to keep your code straightforward and readable.
The most notable feature of these lines is their condition blocks. Either line will only ever appear as a dialogue option if the quest "qst_speak_with_troublemakers" is currently active and if "$geoffrey_duel" is equal to 0. If either of these conditions is not met, the line won't be considered for display. Now, copy and paste these last few tuples:
[trp_geoffrey,"geoffrey_hostile", [], "What?! I'll see you dead for that insult, peasant! Don't you know who I am?", "geoffrey_hostile_2",[]], [trp_geoffrey|plyr,"geoffrey_hostile_2", [], "No . . . Was I supposed to remember?", "geoffrey_hostile_3",[]], [trp_geoffrey,"geoffrey_hostile_3", [], "Why, I'm Geoffrey Eaglescourt, son of the Baron Eaglescourt, and you have delivered the gravest insult to my family's honour! I demand satisfaction! Meet me outside the town walls tonight, or you will be known a coward to every man in this countryside. Good day, {sirrah/wench}.", "geoffrey_hostile_4",[]], [trp_geoffrey|plyr,"geoffrey_hostile_4", [], "Charming lad. Tonight, eh . . . ? I shouldn't miss it . . .", "close_window",[(assign,"$geoffrey_duel",1)]],
With that done, we now have a conversation with several quest-related conditions and consequences, as well as a ready-made quest tuple; but we have yet to create the conditions that will allow the player to activate the quest in the first place. This we will do in the next segment.
[edit] 7.4 -- Dialogue And Quests
When adding new lines to an existing conversation, we must be very careful that our new tuples play nicely with the existing ones. Remember that the dialogue file is scanned from top to bottom; remember to check your conditions blocks; remember to keep a close eye on your syntax. One misspelling can throw off entire blocks of code. Build your module often so that you can catch any syntax errors early.
First, we're going to add one more tuple for Geoffrey, and we're going to add it before any of his other lines. This means it will be considered before his other lines. If this tuple's conditions are present, it will be selected for use regardless of any others that might have also met conditions.
Currently, Geoffrey's first tuple is this:
[trp_geoffrey,"start", [], "What? What do you want? Leave me be, {sir/wench}, I have no time for beggars.", "geoffrey_talk",[]],
Now, copy the following tuple and paste it into your dialogs file above the aforementioned tuple:
[trp_geoffrey,"start", [(eq,"$geoffrey_duel",1)], "Begone! I have nothing to say to you, varlet.", "close_window",[]],
With this tuple in place, Geoffrey's normal conversation will no longer be displayed once he has challenged you to a duel. This way, you can change conversations depending on their situation, and create infinite varieties of dialogue with conditions blocks to manage when each line appears.
The last tuple in Constable Hareck's conversations is this:
[trp_constable_hareck|plyr,"constable_hareck_talk", [], "Nothing. Good-bye.", "close_window",[]],
"constable_hareck_talk" is a menu dialog-state with several options, of which this tuple is the last. In order for a dialogue option to appear in the menu above this one, we have to add it into module_dialogs above this tuple.
Copy the following tuple and paste it into your dialogs file above the aforementioned tuple:
[trp_constable_hareck|plyr,"constable_hareck_talk", [(neg|check_quest_active,"qst_speak_with_troublemakers"),(neg|check_quest_finished,"qst_speak_with_troublemakers")], "Is something wrong? You look worried.", "constable_hareck_troublemakers",[]],
This tuple will only be displayed if the quest "qst_speak_with_troublemakers" is not active and not completed. This is due to the negation-prefix neg|, which causes a condition operation to require the opposite of what it normally requires. For example, the condition operation eq requires two values to be equal; neg|eq requires the values to be inequal.
Now, just below this new tuple, copy and paste the following:
[trp_constable_hareck,"constable_hareck_troublemakers", [], "Oh, it's nothing, just . . .", "constable_hareck_troublemakers_2",[]], [trp_constable_hareck|plyr,"constable_hareck_troublemakers_2", [], "You can tell me, sir.", "constable_hareck_troublemakers_3",[]], [trp_constable_hareck,"constable_hareck_troublemakers_3", [], "No harm in it, I suppose. The trouble is, a few of the town's young nobles . . . spoiled dandies and fops, the lot of them . . . they've decided that suddenly they're men to be respected, and that they should 'take matters into their own hands', to 'take action where the official government has failed'. They say they're going to kill all the river pirates that have been troubling Zendar of late. Of course, they've not actually gone out to fight any river pirates, but they've been making a great ruckus in town and there's not a thing I can do about it.", "constable_hareck_troublemakers_4",[]], [trp_constable_hareck|plyr,"constable_hareck_troublemakers_4", [], "Hmm . . . Would there be a reward for solving this problem?", "constable_hareck_troublemakers_5",[]], [trp_constable_hareck,"constable_hareck_troublemakers_5", [], "What? What are you saying?", "constable_hareck_troublemakers_6",[]], [trp_constable_hareck|plyr,"constable_hareck_troublemakers_6", [], "Nothing, sir. However, it sounds to me like a neutral third party might be just what you need. I could talk to them.", "constable_hareck_troublemakers_7",[]], [trp_constable_hareck,"constable_hareck_troublemakers_7", [], "Heh. Well, you can try, friend. If you manage to do any good, I'll even throw in a few coins for getting the sand out of my breeches. Their leader is a boy named Geoffrey, spends most of his time on watered-down ale and whores in the Happy Boar. Chances are you'll find him there.", "constable_hareck_troublemakers_8",[]], [trp_constable_hareck|plyr,"constable_hareck_troublemakers_8", [], "Thank you, constable. I shall return.", "close_window",[(setup_quest_text,"qst_speak_with_troublemakers"),(start_quest,"qst_speak_with_troublemakers")]],
[trp_constable_hareck|plyr,"constable_hareck_talk", [(check_quest_active,"qst_speak_with_troublemakers"),(eq,"$geoffrey_duel",2)], "Constable, I've taken care of the toublemakers for you. They shouldn't be a worry any longer.", "constable_hareck_troublemakers_10",[]], [trp_constable_hareck,"constable_hareck_troublemakers_10", [], "Truly? Thank God! A few more days and I would've thrown them all into a cell and thrown away the key. Here, take this. You've earned it.", "constable_hareck_troublemakers_11",[(troop_add_gold,"trp_player",100),(add_xp_as_reward,750),(succeed_quest,"qst_speak_with_troublemakers")]], [trp_constable_hareck|plyr,"constable_hareck_troublemakers_11", [], "My pleasure, constable. If you've any other jobs that need doing, please let me know. Farewell.", "close_window",[]],
[trp_constable_hareck|plyr,"constable_hareck_talk", [(check_quest_active,"qst_speak_with_troublemakers"),(eq,"$geoffrey_duel",3)], "Constable, I failed. I'm sorry.", "constable_hareck_troublemakers_15",[]], [trp_constable_hareck,"constable_hareck_troublemakers_15", [], "Oh . . . Oh well. I suppose you did the best you could. Thanks anyway, friend. Perhaps some other job will suit you better. I shall let you know when I have any. Farewell.", "close_window",[(fail_quest,"qst_speak_with_troublemakers")]],
The first block sets up the quest, explaining the details and how to start it. The second block finishes the quest with a nice little reward of gold and experience points, once the variable "$geoffrey_duel" has been set to 2, which we will do upon defeating Geoffrey. The third block finishes the quest if the player fails to defeat Geoffrey, which means he will receive no rewards.
As you can see, it can take some doing to cover all possible aspects of a quest. Ours is only half-finished. All the dialogue is now in place, however, so we are now ready to move on to the next part of this documentation.