Total War: WARHAMMER II

Total War: WARHAMMER II

Skaven as Horde: New Horde Faction
 This topic has been pinned, so it's probably important
mklabs  [developer] 13 Nov, 2018 @ 8:37am
Guide: Quest Battles
Setting up quest battles in TW:W2 can be a daunting task but it's really not that difficult, once you figured out most of the stuff you'll need.

This guide assumes that you have prior modding experience with the Assembly Kit or PFM / RPFM, as such it won't focus on these areas but rather let you know which tables and steps you need to reproduce to setup new quest battles both in the frontend menu and in campaign.

First, you need to know that setting up quest battles doesn't required start_pos edits, but you'll need at some point the start_pos_character ID for which you're writing the quest battles for (to tie it up in campaign). So, if you're wiling to create new quest battles for an existing character, you'll need its start_pos_character ID, if you're willing to create new quest battles for a new character, you'll need to make all necessary edits to the start_pos tables but that goes beyond the scope of the current guide.

Tables

At a glance, here are the tables you'll need to edit:

Tables for character skill and ancillary

  • ancillary_info
  • ancillaries
  • ancillaries_included_agent_subtypes
  • ancillary_included_subcultures
  • ancillary_to_effects
  • character_skills
  • character_skill_nodes
  • character_skills_to_quest_ancillaries

Tables for quest

  • battle_set_pieces
  • battle_set_piece_armies_characters
  • battle_set_piece_armies
  • battle_set_piece_armies_characters_junctions
  • battle_set_piece_armies_junctions
  • battle_set_piece_armies_units_junctions
  • battle_set_piece_frontend_groups
  • battle_set_piece_frontend_groups_to_characters
  • missions
  • scripted_objectives* (not necessarily required)
  • scripted_subtitles* (not necessarily required)
  • cdir_events_mission_issuer_junctions (to get them pop up in campaign)
  • cdir_events_mission_option_junctions (to get them pop up in campaign)
  • cdir_events_mission_payloads (to get them pop up in campaign)

Loc files

(make sure to rename the loc files to something else to not override game values)

  • battle_set_pieces__.loc
  • battle_set_pieces_armies__.loc

Setting up a new quest battle in the frontend menu

First thing first, we'll need to create a new battle_set_piece and have it appear in the Quest Battle menu in the game, this is the first foundation before trying to make it appear in campaign. It'll let you test and ensure your quest battle is working.

That's where https://wiki.totalwar.com/w/TWW_Assembly_Kit_Terry_Quest_Battle guide comes in handy.

Very briefly, setting a quest battles evolves around setting up a new battle_set_pieces (quest battle) with the characters used (Generals), the armies (compositions and units) and adding them to the proper frontend groups.

To make thing easier, choose one of the quest battles that already exist and try to replicate the rows associated with it. Once you have the quest battle working, you can then customize it to your will (such as the battlefield or the battle script).

battle_set_pieces

This table lets you create a new quest battles from which all other tables will depend on (or at least a good amount of them). It lets you customize the battlefield, the battle script (the lua script that will setup the behavior of your battle), the type of the battle etc.

As we'll usually do, search up a quest battle you'd like to replicate, copy the line and create a new one. Make sure to add new custom and unique battle_name key.

Here is a quick description of some of the columns:

  • battle_name - This is the table key and needs to be unique
  • battle_environment - a custom environment to load, I assume it needs to match the battlefield_folder you'll use.
  • battle_script - another important field, that's where you'll link the LUA script that will setup the behavior of the battle (the intro cutscenes / subtitles, how generated armies will behave etc.) You can leave it blank for the moment or reuse one of the scripts.
  • battle_type - The type of the battle, again make sure to match it with the battlefield_folder you're about to use. "classic" is a good choice.
  • frontend_icon_offset_x / frontend_icon_offset_y - I assume this is tied to the location of the battle as it appears in the frontend menu, leave it at 0 (Offset for icon shown in frontend on map).
  • battlefield_folder - Another important field, to make things easier I recommend using one of the battlefield folder already used by the game. But, if you want to be fancy and use a custom map, that's where you'll need to edit the value ("terrain/battles" folder, for vanilla tables look at the terrain.pack file).
  • teleport_cost - Amount of gold to use when using the "Teleport to" button in campaign.

As for the loc files, you'll need 2 new entries, one for the localised name, and one for the localised description.

  • battle_set_pieces_localised_name_[name_of_your_quest_battle] - The name of your quest battle.
  • battle_set_pieces_localised_description_[name_of_your_quest_battle] - The description of your quest battle.

The [name_of_your_quest_battle] needs to be replaced by the value of the battle_name key you put in the table.

I'll go a bit faster on the following tables as it's already documented in the https://wiki.totalwar.com/w/TWW_Assembly_Kit_Terry_Quest_Battle guide.

battle_set_piece_armies_characters

This table lets you define the Generals for this custom quest battle. You'll need at least two entries (if your quest battle only include two opposing armies, eg. no reenforcing armies), one for the player, one for the opposing army.

This table lets you define the character level, its name, the unit type (from main_units), the agent subtypes (from agent_subtypes), the portrait (for loading screen portholes), the character_model (the campaign representation of this character, from campaign_character_arts).

To make things easier, and as usual, try to replicate two of the existing records and give them a unique character_name. A pattern I like is to use the quest battle name and append a "_player" and "_ai" suffix or "_attacker" / "_defender".

battle_set_piece_armies

This table is about setting up the armies in use in the quest battle. As for the characters, you'll need at least two entries.

Replicate two of the entries, and give them a unique and custom name.

Note that you'll also have to create the localisation for these armies, look at the battle_set_piece_armies__.loc file and create two new entries in your custom loc file, one for each army you just created. The loc keys must be battle_set_piece_armies_army_onscreen_name_[name_of_the_army] where [name_of_the_army] is the table's keys.

battle_set_piece_armies_characters_junctions

This table lets you associate the armies and characters you created earlier, with a script_name. If you look in the Assembly Kit, some junctions don't have a script_name but I always used either player_army or enemy_army.

Put in the army_name and character_name the proper values, it needs to match the keys you created before in the battle_set_piece_armies and battle_set_piece_armies_characters tables.

battle_set_piece_armies_junctions

Once the junctions between armies and characters is done, it's time to associate the armies with the battle_set_piece.

This table has two columns one that match the battle_set_piece_armies key, the other that match the battle_set_pieces key.

battle_set_piece_armies_units_junctions

This table is about army compositions. For the player, it won't have any impact in campaign but it will define the army composition when launching the quest battle from the frontend menu.

It has 4 columns that are pretty simple to understand:

- army_name - The key of battle_set_piece_armies
- unit_name - The key of one the units you may find in battle_set_piece_armies_units table, which then links to the main_units table. You normally don't have to edit this table, but if you're adding custom units to the battle, you might have to add records to this table.
- script_name - The script_name of the unit (used by lua and generated_battle). I usually either put "player_army" or "enemy_army".
- number_of_unit - The number of this unit in the army.

To make things easier, you could use one of the existing armies in this table and copy the entire set of units.

battle_set_piece_frontend_groups

The two following tables are about setting up this new quest battle in the frontend menu, under "Quest battles".

For this table, you need to create a new record with a custom and unique key.

The two others columns are sort_order and campaign_map. The sort_order lets you position the character in the frontend_menu (lower the closer to the top). The campaign_map lets you define the correct campaign map to link in the frontend, either Mortal Empires or Vortex. To know which value to use, I recommend looking at the other records and re-using the correct ID (for the time being the campaign map ID for Mortal Empires is 1564135556).

If you're designing a quest battle for an existing character, you may re-use one of the existing frontend_groups as such this table may be left untouched in this case.

battle_set_piece_frontend_groups_to_characters

This table lets you link the frontend group you just created (or are willing to reuse) with the battle_set_piece_armies_characters created earlier.

Missions

Finally, you need to add a mission entry that will link to your battle_set_piece.

Add a new record by copy-pasting one of the existing records (preferably one that is tied to an existing quest battle).

- Create a new custom and unique key for this mission
- mission_type - Set it to "FIGHT_SET_PIECE_BATTLE"
- generated - false
- prioritised - true
- event_category - Quest
- set_piece_battle - The key linking to the battle_set_pieces table.
- location_x / location_y - The map location coordinates. Used in both the frontend_menu and in campaign.
- quest_character - The start_pos_characters ID for this mission. This is where the start_pos_characters table is used.
- quest_mission_final - true
- sticky_by_default - false

As for the loc file, you'll also need a new entry in "missions__.loc" with missions_localised_description_[mission_key] and missions_localised_title_[mission_key] where [mission_key] is the mission table key you just created.

Now, all required DB edits to make a new custom quest battle appear in the frontend_menu have been done, it's time to test the mod and see if it appears.

Campaign director related tables

Before putting this quest battle in the campaign, we need to edit three more tables:

  • cdir_events_mission_issuer_junctions
  • cdir_events_mission_option_junctions
  • cdir_events_mission_payloads

cdir_events_mission_issuer_junctions

Create a new record in this table, and associate the mission you created with "CLAN_ELDERS" in the issuer_key column.

cdir_events_mission_option_junctions

In this table, you need to create 4 records per mission you created. Each record will need to match the mission you created earlier.

This table has 3 columns of importance: mission_key, option_key and value.

As for the options and values to setup you need.

option_key
value
VAR_CHANCE
100
GEN_TARGET_CHARACTER
(leave it empty)
VAR_MISSION_LENGTH_INFINITE
(leave it empty)
GEN_CND_CHARACTER_ID
This is the start_pos_character ID for which the mission must trigger and is associated with

cdir_events_mission_payloads

This table lets you customize the reward ot the mission on success.

Create two new records in this table, with:

- mission_key - the mission key we created previously.
- status_key - SUCCESS
- payload_key - both ADD_ANCILLARY and CHARACTER_EXPERIENCE
- value - depends on the payload key, refer to the table below.

As for the payload_key and value, here are the values to use:

payload_key
value
ADD_ANCILLARY
ANCILLARY_KEY[name_of_the_ancillary];SHOULD_REPLACE
CHARACTER_EXPERIENCE
AMOUNT[1500]

Of course, you can customize the values there, and the "AMOUNT[1500]" is what is used in most of the quest battles. As for "ANCILLARY_KEY[name_of_the_ancillary];SHOULD_REPLACE", the part between [] characters need to match the ancillary we're about to create in the next section.

Creating a new character skill and ancillary (legendary item)

In this section, we'll go through the process of adding a new legendary item to the game. And how to link it to the quest battle we just created.

Modding hero / lord skills goes beyond the scope of this guide, as such make sure to take a look at this most excellent guide: https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=699946724

So, to create a new legendary item (or ancillary), you'll need to edit all the relevant tables related to it:

  • ancillary_info
  • ancillaries
  • ancillaries_included_agent_subtypes
  • ancillary_included_subcultures
  • ancillary_to_effects
  • character_skills
  • character_skill_nodes
  • character_skills_to_quest_ancillaries

And these relevant loc files:

  • ancillaries__.loc
  • character_skills__.loc

Starting with...

ancillary_info

This is the first table to edit, and you need to create a new key for your ancillary. It'll then be used in the ancillaries table.

Give it a unique key, and open up the next table: ancillaries

ancillaries

This is the main table for your ancillary, I'd recommend to copy and paste a record for one of the ancillary you'd like to replicate, preferably one that is tied to a legendary item you know (let's say "wh2_main_anc_armour_warp_shard_armour" which is one of Queek's legendary items).

- Give it a unique name, it needs to match the unique name you just put in the ancillary_info table.

Other columns of interest:

- type => it needs to match one of the types you can find in the ancillary_types. For a talisman for instance, use "wh_main_anc_talisman"
- category => Make it match the type and it needs to match one of the values you can find in ancillaries_category
- uniqueness_score => Put "200" in there. Most of the legendary items I found in the DB seems to be tied to this value.
- turns_before_swap_allowed => Put "0". Same, most of the legendary items uses this value.

For all other columns, make sure to use the same one as your template row you're duplicating.

As for the localisation files, make sure to include two additional loc records: ancillaries_onscreen_name_[ancillary_key] and ancillaries_colour_text_[ancillary_key], where [ancillary_key] is the key you just put in both ancillaries and ancillary_info tables.

ancillaries_included_agent_subtypes

This table lets you associate the ancillary you just created with an "agent_subtype", which is the campaign representation of any given character.

Put the unique name of your ancillary in the "ancillary" column, and the agent_subtype you want to give this ancillary to in the "agent_subtype" column. Make sure to remember the agent there, as its character_node_sets will be used when creating the skill.

ancillary_included_subcultures

In this table, similar to the previous one, we'll associate our ancillary with the subcultures we're interested in. It needs to match the subcultures of the agent you associated it with in the previous table (ancillaries_included_agent_subtypes).

To know which subculture to use, take a look at the "faction_agent_permitted_subtypes" and the "factions" table. The first one lets you know for which faction this agent is active, and the second lets you know which subculture is tied to this faction (in the "subculture" column).

ancillary_to_effects

This is where things start to get interesting. This table lets you associate a set of effects to your newly created ancillary.

This table is similar to other tables related to effects junctions. You need to put:

- ancillary - The key you put in both ancillaries and ancillary_info table
- effect - An effect you can find in the "effects" table
- effect_scope - A scope for this effect to apply. The best way to know which effect scope to use, is to look at the existing records in this table and reuse one of the scope for an effect similar to what you're using. For instance, to enable magic attacks for your ancillary, you would need to use "wh_main_effect_character_stat_enable_magic_attacks" for the effect and "character_to_character_own" for the effect scope. (and finally a 1 value in the last column)
- value - The value to tie to the effect. It depends on the effect you're trying to associate (so try to look for the effect in the DB and how it's associated in the relevant tables).

character_skills

Now, it's time to create the character skills for our new ancillary. Once again, creating character skills goes a bit beyond the scope of this guide, so make sure to checkout the following one: https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=699946724

Here, this table is about creating the necessary entry for your new character skills ancillary.

Similar to what we did for the ancillary itself, copy and paste a row for one of the character skills already existing in game, preferably one that is tied to a legendary item (like for Queek: "wh2_main_skill_skv_queek_quest_1").

Columns of interest:

- unlocked_at_rank - Put there the level / rank you want this character skill to unlock. A character starts from rank 1 in campaign, but that is actually rank 0 model side. So if you want a skill to unlock at rank 5, set the unlocked_at_rank to be 4.
- image_path - One of the image we can find in "ui/campaign ui/skills" folder. For instance, for a talisman, use the "item_talisman.png" image.

This table needs a corresponding loc file with two new entries (take a look at character_skills__.loc): character_skills_localised_name_[character_skill_key] and character_skills_localised_description_[character_skill_key], where [character_skill_key] it the character skill key we just created in the character_skills table.

character_skill_nodes

This is where we'll make our newly created skill correspond to one of the nodes in a character skill tree. Again, take a look at https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=699946724 to know how tier / indent relates to each other.

Briefly, "tier" is about the column you want the skill to appear in, and "indent" is about the row.

Create a new record in this table and give it a unique and custom name.

- character_skill_node_set_key - This is an important field and relates to the character skill tree you want this skill to appear in. To know which value to use, you can then look at the "character_skill_node_sets" table which associate a given character skill tree with any given character (in the agent_subtype key). Obviously, it needs to match the agent you're working on and have previously used in the ancillaries_included_agent_subtypes table.
- character_skill_key - Make it match the character_skill_key you created in the "character_skills" table.
- tier / indent - Make sure to use a slot that is not already taken by another skill for this character_skill_node_set_key, or else you'll need to edit all character_skill_nodes for this given character for a row (or "indent").
- visible_in_ui - true

character_skills_to_quest_ancillaries

Lets you associate a given ancillary with a given character_skills.

- Put the ancillary key we created earlier in the "ancillary" column.
- Put the character skill we just created in the "skill" column.
- use_quest_for_prefix - true

And we're finally done regarding DB tables and edits (and I hope I didn't forget any given table in the process of writing this guide :p).

You can build the mod and test it up in campaign to see if your character skill appear where it should be, and if it's tied to the ancillary you just created. There is still a bit of scripting work to do to make the mission trigger in campaign, and that's what we'll go through in the next section.

Setting up a new quest battle in the campaign

This section will just require scripting, and should be rather smaller that all these DB / Loc files edition we just did, we're almost done!

An important file to look for is the wh_quests.lua file in data_2/script/campaign/main_warhammer/wh_quests.lua. In there, you'll see an important function in use that we'll need to reuse as well: set_up_rank_up_listener()

It lets you establish all the necessary intervention triggers and event listeners for your mission / quest battle to trigger when your agent_subtype reach the desired level.

You have two choice, you can either reuse this file and add the necessary code in there, but I don't recommend it. Or use another and independent file, which is in my opinion cleaner and easier to maintain.

If we look across the script code where this gets executed, we can see that q_setup() is called within the start_game_all_factions() function, in wh_start.lua. So this set_up_rank_up_listener() call needs to happen at the start of the game, or when the campaign is loaded.

To do so, you can use the cm:add_first_tick_callback() function which takes a callback to call whenever the first tick occurs.

As for the file location we're about to create, we have two choices. We can either use script/campaign/main_warhammer/mod folder for the Mortal Empires campaign (or script/campaign/wh2_main_great_vortex/mod for the Vortex campaign), or a faction script with script/campaign/main_warhammer/factions/[faction_key]/[faction_key]_start.lua (where [faction_key] is to replaced with the faction key you can find in the factions table for which you want this script to execute when the campaign start). The later solution (the faction script) is what I used in my Skaven as Horde mod.

The faction script solution has the advantage of be only loaded for the faction we're interested in. Whereas the mod one will get executed for each and every faction.

If you're using the mod solution (not the faction script):

cm:add_first_tick_callback( function() out("First tick callback!"); my_quest_setup(); end );

If you're using the faction script solution, you can use start_game_for_faction() function which gets called by the game on the first tick callback.

function start_game_for_faction() out("start_game_for_faction() called"); my_quest_setup(); end;

Now that we know how and when our snippet of code will get executed, it's time to write our my_quest_setup() function (of course, feel free to rename the function to whatever you prefer, but make sure to use something custom and unique to you).

Note that this example is borrowed from my Skaven as Horde mod, and you'll need to customize the agent_subtype and agent_quests values to match the stuff you put in the DB.

function my_quest_setup() out("Setting up quests for Thanquol"); local agent_subtype = "zmk_main_skv_thanquol"; local agent_quests = { {"mission", "zmk_main_anc_warp_amulet", "zmk_main_skv_thanquol_warp_amulet", 8}, {"mission", "zmk_main_anc_staff_of_the_horned_rat", "zmk_main_skv_thanquol_staff_of_the_horned_rat", 12} }; set_up_rank_up_listener(agent_quests, agent_subtype); end;

Here, we setup two quests to trigger at the appropriate 8 and 12 ranks.

The important bit of information to understand here:

- agent_subtype - is the agent_subtype key you want the mission and ancillary to trigger for
- agent_quests - is a table with 3 values to change after "mission". The first one (zmk_main_anc_warp_amulet) is the mission key, the second one (zmk_main_skv_thanquol_warp_amulet) is the ancillary key, and the final one (8) is the rank or level we want this quest to trigger on.

To know if you're rank up listeners have been appropriately setup, you can look at the scripting log and search for something similar to:

[Quests] establishing rank up listener for char type [zmk_main_skv_thanquol] and rank [8] for mission [zmk_main_skv_thanquol_warp_amulet]

To activate script logging, I recommend using https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=1271877744 or doing it yourself with https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=1230737867 (thanks to thesniperdevil for both resources).

Battle scripting

The battle scripting file is the file you link in the battle_set_pieces table. It is responsible for all the AI behavior, the cutscene that is generated so on and so fourth. This is sometimes referred as the battle_manager.

As for the file location, the recommended script location is in script/battle/quest_battles/[subfolder] where [subfolder] is the character you're writing this battle script for. But ultimately, this script location is determined by and needs to match the location you put in battle_set_pieces.battle_script table / column.

To keep things simple, I'll just link what I've been using for the first quest battles for Thanquol, which is pretty basic. It sets up a cutscene with no scripted_subtitles and no AI behavior, but that alone should let you get a basic understanding of how it works and how to further work on it. Also feel free to browse and take a look at other battle_script already in the game, you'll learn a lot from them.

load_script_libraries(); out("Battle script loaded for Thanquol warp amulet"); bm = battle_manager:new(empire_battle:new()); local gc = generated_cutscene:new(true); gb = generated_battle:new( false, -- screen starts black false, -- prevent deployment for player false, -- prevent deployment for ai -- intro cutscene function function() gb:start_generated_cutscene(gc) end, false -- debug mode ); --generated_cutscene:add_element(sfx_name, subtitle, camera, min_length, wait_for_vo, wait_for_camera, loop_camera) gc:add_element(nil, nil, "gc_orbit_90_medium_ground_offset_north_west_extreme_high_02", 3000, false, false, false); gc:add_element(nil, nil, "gc_orbit_90_medium_commander_back_close_low_01", 3000, false, false, false); gc:add_element(nil, nil, "gc_slow_army_pan_front_left_to_front_right_close_medium_01", 3000, false, false, false); gc:add_element(nil, nil, nil, 3000, false, false, false); gc:add_element(nil, nil, "gc_slow_enemy_army_pan_front_left_to_front_right_far_high_01", 3000, false, false, false); gc:add_element(nil, nil, "gc_orbit_90_medium_commander_front_right_close_low_01", 4000, false, false, false); gc:add_element(nil, nil, "gc_medium_commander_back_medium_medium_to_close_low_01", 4000, true, true, false); gb:set_cutscene_during_deployment(true);

Note that it is possible to setup scripted subtitles for this generated cutscene, each element you add can have a second parameter which would need to match a corresponding entry in both DB and loc files (scripted_subtitles).

Resources used in this guide

- https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=699946724 thanks to Ekzel for this excellent guide.
- https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=1271877744 and https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=1230737867 thanks again to sniperdevil for these valuable modding resource.
Last edited by mklabs; 14 Nov, 2018 @ 6:52am
< >
Showing 1-1 of 1 comments
mklabs  [developer] 14 Nov, 2018 @ 7:10am 
Note: the start_pos ID stuff might need confirmation, there might be a way to trigger quest without it tied to the start_pos_characters table but it needs further testing.
< >
Showing 1-1 of 1 comments
Per page: 1530 50