Official Module System Documentation: Part 03
In this chapter of the documentation, we cover module_troops.py and its functions. Module_troops is where all regular troops, Heroes, chests and town NPCs are defined, complete with faces, ability scores and inventory. Whenever you wish to make a new character or troop type, this is the file you'll be modding.
Contents |
3.1 -- Breakdown of Module_Troops
The file begins with a small block of code that calculates weapon proficiencies and some other non-moddable code. Since this whole block falls outside the Python list and we will not be editing it, it needn't concern us yet. Skip ahead to the list troops = [.
Here we find tuples for our friend, the player, and several other troops important to the game. Just below that are the various fighters we encounter in the Zendar trainer. We'll study a few of these, as they are excellent examples of regular troops' level progression.
Observe:
["novice_fighter","novice_fighter","novice_fighters",tf_guarantee_boots|tf_guarantee_armor,no_scene,reserved,fac_commoners, [itm_sword,itm_hide_boots], str_6|agi_6|level(5),wp(60),knows_common,swadian_face1, swadian_face2],
This is a bog-standard troop called "novice fighter". "novice fighter" is low-level, not very good at fighting, has low ability scores, and is otherwise unremarkable.
Breakdown of the tuple fields:
1 ) Troop id. Used for referencing troops in other files.
2 ) Troop name.
3 ) Plural troop name.
4 ) Troop flags. tf_guarantee_* flags must be set if you want to make sure a troop always gets equipped with a certain category of inventory. If you do not, the troop may appear without armour of that category. Only melee weapons are guaranteed to be equipped, if there are any in the troop's inventory.
5 ) Scene. This is only applicable to Heroes; it governs at which scene and entry point the Hero will appear. For example, scn_reyvadin_castle|entry(1) puts the troop at entry point 1 in Reyvadin Castle.
6 ) Reserved. Not currently used; must be either reserved or 0.
7 ) Faction. The troop's faction, used with the fac_ prefix.
8 ) Inventory. A list of items in the troop's inventory. Regular troops will choose equipment from this list at random.
9 ) Attributes. The troop's attribute scores and character level. These work exactly as they do for the player.
10 ) Weapon proficiencies. The weapon proficiency scores of this troop. The function wp(x) will create random weapon proficiencies close to value x, but you can also add extra definitions to specifically designate certain proficiency scores. For example, to make an expert archer with other weapon proficiencies close to 60 you could use:
wp_archery(160) | wp(60)
11 ) Skills. These are the same as the player's skills. Note that, in addition to the attributes and skills you've defined, the troop also gets 1 random attribute point and 1 random skill point per character level.
12 ) Face code. The game will generate a face according to this code. You can export new face codes from the game by pressing CTRL+E in the face editor screen while Edit Mode is active.
13 ) Face code 2. Only applicable to regular troops, can be omitted for Heroes. The game will create random faces between face code 1 and face code 2 for each individual of this troop type.
Novice_fighter tuple examination:
1 ) Troop id = "novice_fighter"
2 ) Troop name = "novice_fighter"
3 ) Plural troop name = "novice_fighters"
4 ) Troop flags = tf_guarantee_boots|tf_guarantee_armor
5 ) Scene = no_scene
6 ) Reserved = reserved
7 ) Faction = fac_commoners
8 ) Inventory = [itm_sword,itm_hide_boots]
9 ) Attributes = str_6|agi_6|level(5)
10 ) Weapon proficiencies = wp(60)
11 ) Skills = knows_common
12 ) Face code = swadian_face1
13 ) Face code 2 = swadian_face2
There are three things worth noting about this tuple.
Our "novice fighter" has tf_guarantee_armor, but no armour of his own. However, this does not make tf_guarantee_armor redundant; the troop will put on any armour he receives during the game.
To begin with (ie, at Level 1), "novice_fighter" has a STR of 6 and an AGI of 6. Upon start of the game, he is bumped up to Level 5, with all the usual stat gains that implies.
He has the skill knows_common. knows_common is a collection of skills that was defined at the beginning of module_troops; scroll up and observe this collection now.
knows_common = knows_riding_1|knows_trade_2|knows_inventory_management_2|knows_prisoner_management_1|knows_leadership_1
A troop that has knows_common will have every skill listed here; a Riding skill of 1, a Trade skill of 2, an Inventory Management skill of 2, a Prisoner Management skill of 1, and a Leadership skill of 1. knows_common is what is known as a constant; a phrase that represents something else, be it a number, an identifier, another constant, or any other valid object. A constant can represent any number of objects, as long as those objects are in the right order for the place where you intend to use this constant.
In this case, knows_common is defined as knows_riding_1|knows_trade_2|knows_inventory_management_2|knows_prisoner_management_1|knows_leadership_1. So in effect, by putting knows_common in the Skills field, the module system will function just as if you'd typed out knows_riding_1|knows_trade_2|knows_inventory_management_2|knows_prisoner_management_1|knows_leadership_1 in the Skills field.
Now let us look at the next entry in the list.
["regular_fighter","regular_fighter","regular_fighters",tf_guarantee_boots|tf_guarantee_armor,no_scene,reserved,fac_commoners, [itm_sword,itm_hide_boots], str_8|agi_8|level(11),wp(90),knows_common|knows_ironflesh_1|knows_power_strike_1|knows_athletics_1|knows_riding_1|knows_shield_2,swadian_face1, swadian_face2],
In this example, you can see the slightly stronger "regular fighter"; this one has higher ability scores, is level 11, and knows some skills beyond knows_common. In-game, if some "novice fighters" in our party had reached sufficient experience to reach level 11, we could upgrade them into "regular fighters".
3.2 -- Upgrading Troops
The list of which troops can be upgraded into what is contained at the bottom of module_troops. Please scroll down to the bottom now.
As you can see, each troop's upgrade choices must be defined here through the operation upgrade(troops). The first string is the ID of the troop to be upgraded, the second string is the ID of the resulting troop. For example, upgrade(troops,"farmer", "watchman") will allow a "farmer" to upgrade into a "watchman", when the "farmer" has accrued enough experience.
There are two types of upgrade operations.
upgrade(troops,"source_troop", "target_troop_1") offers only one upgrade choice; "source_troop" to "target_troop_1".
upgrade2(troops,"source_troop", "target_troop_1", "target_troop_2"), however, offers the player a choice to upgrade "source_troop" into either "target_troop_1" or "target_troop_2". 2 is currently the maximum number of possible upgrade choices.
There is currently no entry for "novice_fighter" in this block, so let's make one. Copy upgrade(troops,"farmer", "watchman") and paste it to the bottom of the block. Then change "farmer" to "novice_fighter" and "watchman" to "regular_fighter". Any "novice fighters" in our party will now be able to upgrade to "regular fighters" as described in the last segment.
Next, we'll take it a bit further. Make another entry at the bottom of the list, with the source troop "new_troop" and the target troop "regular_fighter". Then scroll up to the phrase: # Add Extra Quest NPCs below this point . Here you will see ], the closing bracket of the Troops Python list. New troops should be added before the bracket, which is what we're going to do now.
3.3 -- Adding New Troops
Move the bracket down two lines, then copy/paste the following code into the empty space:
["new_troop","new_troop","new_troops",tf_guarantee_boots|tf_guarantee_armor,no_scene,reserved,fac_commoners, [itm_sword,itm_hide_boots], str_6|agi_6|level(5),wp(60),knows_common,swadian_face1, swadian_face2],
This is the entry we're going to play with to make our new troop.
First let's give him some armour and a helmet.
["new_troop","new_troop","new_troops",tf_guarantee_boots|tf_guarantee_armor,no_scene,reserved,fac_commoners, [itm_sword,itm_hide_boots,itm_leather_jerkin,itm_skullcap], str_6|agi_6|level(5),wp(60),knows_common,swadian_face1, swadian_face2],
From now on, every troop of the type "new_troop" will be wearing itm_leather_jerkin. However, only some of them will have itm_skullcap, because of the entries in the Flags field; this troop only has guaranteed armour and boots. In order to make sure our new troops will all have helmets, we must add tf_guarantee_helmet to the Flags field.
["new_troop","new_troop","new_troops",tf_guarantee_boots|tf_guarantee_armor|tf_guarantee_helmet,no_scene,reserved,fac_commoners, [itm_sword,itm_hide_boots,itm_leather_jerkin,itm_skullcap], str_6|agi_6|level(5),wp(60),knows_common,swadian_face1, swadian_face2],
For our next edit, we will make some changes the troop's stats. Put its STR to 9, and AGI to 9. Once that's done, change Level to 4 and weapon proficiency to 80.
Our "new troop" should now look like this:
["new_troop","new_troop","new_troops",tf_guarantee_boots|tf_guarantee_armor|tf_guarantee_helmet,no_scene,reserved,fac_commoners, [itm_sword,itm_hide_boots,itm_leather_jerkin,itm_skullcap], str_9|agi_9|level(4),wp(80),knows_common,swadian_face1, swadian_face2],
It's now ready to be placed into the game, as an experiment.
3.4 -- Mercenaries
Save your progress, then open module_parties.py. Scroll down until you see the party "zendar_mercs".
("zendar_mercs","zendar_mercs",pf_disabled, no_menu, pt_none, fac_commoners,0,ai_bhvr_hold,0,(0,0),[(trp_farmer,15,0)]),
This is a mercenary party; a party that isn't placed on the map, but whose troops can be hired as mercenaries from the town's tavernkeeper. This particular party is linked to the tavernkeeper in Zendar. EDIT: Zendar was removed in patch 0.950
"zendar_mercs" currently contains 15 farmers. If you started the game right now, that is what you would be able to hire. However, if we change "trp_farmer" to "trp_new_troop", we will be able to hire 15 "new troops" instead. Make this change now.
Save your progress, close module_parties, and double-click on build_module.bat. If the build finishes without problems, you will now find your new troops available for hire with the Zendar tavernkeeper. (You'll need to start a new game for the new troops to show up. You need to start a new game for changes to parties to take effect.)
Start the game and hire some "new troops". Then go out of town, fight some battles, and notice how you are able to upgrade the new troops to "regular fighters" when they accumulate enough XP.
Congratulations! You now know how to make and manipulate regular troops. We will cover Heroes, Merchants and other NPCs in the next segment.
3.5 -- NPCs
The various merchants and NPCs you see in the game are very similar to regular troops. The most significant element setting them apart is the flag tf_hero; this flag is what causes Marnid and Borcha to achieve their special status. Every unique NPC you encounter in the game is a Hero, even the merchants. The main differences between Heroes and regular troops are:
1 ) Heroes are unkillable. Their health is represented by a percentage value, and you can have only one of each Hero unless they are cloned by error or by design. Even by design, however, cloning Heroes is a bad idea. 2 ) Heroes each take up a full party stack. 3 ) Heroes show up properly in a scene when they are assigned to one in their troop tuple. 4 ) Heroes stay with the player when he is defeated by an enemy party -- the Heroes are not captured by the enemy -- but the player can capture enemy Heroes as normal.
Because there should be only one specimen of each Hero, they have no plural troop name. Field 3 of the Hero tuple is therefore identical to Field 2.
Example of a Hero tuple:
["Marnid","Marnid","Marnid", tf_hero, scn_the_happy_boar|entry(4),reserved, fac_commoners,[itm_linen_tunic,itm_hide_boots,itm_club],def_attrib|level(6),wp(60),knows_trade_3|knows_inventory_management_2|knows_riding_2,0x00000000000c600301c2510211a5b292],
Here we have our friend Marnid, a faithful companion over the course of the game. He is marked as a Hero by the tf_hero in his Flags field. We can find him at entry point 4 in the Happy Boar inn. He's a bit of a pushover in combat, but his fairly good Trade skill comes in handy during our early adventures, and as a Hero he will never die unless removed by some scripted event. You will also note that Marnid has his own unique face code. It is possible to design faces using the in-game face editor and then retrieve their face codes for use in your module; this is covered in Part 10 of the documentation, Using the In-Game Edit Mode.
Another important thing to note is that even though Marnid's troop identifier in this file -- "Marnid" -- uses an uppercase letter M, we must always reference the identifier with lowercase letters. The module system will throw an error if you try to use an uppercase letter when referencing an identifier from another file. So, in order to reference "Marnid", we must use the identifier "trp_marnid".
Our last point of interest is the entry you may have noticed in Marnid's Attributes field; def_attrib. def_attrib is a constant much like knows_common, but defined in header_troops.py. Its function is similar to knows_common -- it sets default attributes for the troop it's used on. Quoted from header_troops:
def_attrib = str_5 | agi_5 | int_4 | cha_4
This tells us that any troop with def_attrib will have STR 5, AGI 5, INT 4 and CHA 4 to start with. Any attributes set after def_attrib will overwrite the relevant attribute setting from def_attrib.
We can now begin to make our own hero. Copy Marnid's tuple, and paste it just below the "new_troop" tuple we created earlier.
["Geoffrey","Geoffrey","Geoffrey", tf_hero, scn_the_happy_boar|entry(4),reserved, fac_commoners,[itm_linen_tunic,itm_hide_boots,itm_club],def_attrib|level(6),wp(60),knows_trade_3|knows_inventory_management_2|knows_riding_2,0x00000000000c600301c2510211a5b292],
In this example, we've changed the new hero's identifier and names to "Geoffrey". Please make this change for yourself now.
At this point, if we were to click on build_module.bat, the module system would compile without problems. Our new tuple does not conflict with anything that the module system can see. Yet if we did, we would have one major problem -- both Geoffrey and Marnid are currently using the same entry point in the same scene. Marnid would get placed because his tuple is further up the file, and Geoffrey would not show up at all because the entry point is already taken.
In order to solve this, we will assign Geoffrey to entry point 6 instead. This is right at the back of the inn, where he won't be in anyone's way.
Your tuple should now look like this:
["Geoffrey","Geoffrey","Geoffrey", tf_hero, scn_the_happy_boar|entry(6),reserved, fac_commoners,[itm_linen_tunic,itm_hide_boots,itm_club],def_attrib|level(6),wp(60),knows_trade_3|knows_inventory_management_2|knows_riding_2,0x00000000000c600301c2510211a5b292],
Feel free to assign him some new equipment or stats, as we did with "new_troop". Then click on build_module.bat, open the game, and go to the Zendar inn. If all went welll, you should now find Geoffrey standing against the far walll of the inn.
Attempting a dialogue with Geoffrey will cause him to give a stock response, as he currently has no dialogue associated with him. This is another example of interrelation.
We will leave Geoffrey where is for now, and come back to him later as we explore module_quests and module_dialogs.
3.6 -- Merchants
Merchants are a special type of Hero. In addition to tf_hero, they also have the flag tf_is_merchant. This flag causes them to not equip any of the items in their inventory, except what they are originally assigned in their troop tuple. In other words, these merchants can receive all sorts of items over the course of the game, but they will not wear or use the items, and the items will properly show up for sale.
Example of a merchant:
["zendar_weaponsmith","Dunga","Dunga",tf_hero|tf_is_merchant, scn_zendar_center|entry(3),0, fac_commoners,[itm_linen_tunic,itm_nomad_boots],def_attrib|level(2),wp(20),knows_inventory_management_10, 0x00000000000021c401f545a49b6eb2bc],
This is the weapon merchant in Zendar, named Dunga. He is almost identical to the other merchants in Native M&B. If you look closely, the only differences are their identifiers, names, scene placement, and faces.
To add a merchant, however, can be slightly complex. They are gathered into groups for a reason. There are scripts in M&B to update merchant inventories every day for each type of merchant -- to do this, these scripts use a range, a number of subsequent tuples between a starting point of choice (the lower bound) and a stopping point of choice (the upper bound). For example, the range of armour merchants includes everything from "zendar_armorer" (the lower bound) up to -- but not including -- "zendar_weaponsmith" (the upper bound). The upper bound of a range is not included in the range, so the upper bound needs to be set one entry further down (to "zendar_weaponsmith") if we want "town_14_armorer" to be included in the armour merchant range.
For this reason, new armour merchants must be added before "zendar_weaponsmith". New weapon merchants must be added before "zendar_tavernkeeper". New goods merchants must be added before "merchants_end".
3.7 -- Chests
Chest troops are special troops which serve as inventories for chests inside the game with which the player can interact. These chest troops are not the chests themselves, only their inventories. Chests as you see them in the game are part scene prop, part scene information, part troop and part hardcoded. New chests are somewhat complex to create and cross various module files; here we will cover only the information relevant to module_troops.
Example of a chest:
["zendar_chest","zendar_chest","zendar_chest",tf_hero|tf_inactive, 0,reserved, fac_vaegirs,[],def_attrib|level(18),wp(60),knows_common, 0],
All chests must follow this example. The only things you should consider changing on a new chest troop are the troop's name and identifier, (possibly) troop level and troop skills, and the inventory. As mentioned, chest troops serve as inventory for in-game chests; therefore, any items you add to the chest troop's inventory will be inside the chest at the start of the game.
Chests require a chest troop to function. However, they also require several other modifications to different module files, which we will cover in the files' respective documentation.
Having learned this, you now know all there is to know about module_troops. There is a list of available flags in header_troops.py that you can use for the creation of further troops. Feel free to experiment, and when you're ready, please move on to the next part of this documentation.