Have you ever wanted to create the Battle of Winterfell in Total War: Attila? The Battle of Agincourt? The Sack of Rome? To provide players with a scenario full of cutscenes, reinforcements, and unique objectives? Well now you can, with a new tutorial created by Caligula on how to script 'historical' battles. This tutorial was made possible by DETrooper, Farfadet, Elessar, DrunkFlamingo, and Case who provided immeasurable support by creating the unique scripts in the accompanying Historical Battle Template. Check out the examples below, and create something unique!
Scripted Battle Examples
The Dawnless Days (Battle of Five Armies and Battle of the Fords of Isen)
[1] Refer to the post below this one to learn what each table/entry does, and how they affect your battle. Constantly refer back to this post throughout this tutorial.
[2] Download the Historical Battle Template that DETrooper, Farfadet, Elessar, DrunkFlamingo, Case and I have created. Use PFM or RPFM to open it up and use it as the basis for your mod.
[3] Go to all of the following tables/entries and replace the template_ and tut2_ prefix for each table with the prefix for your mod. battle. Eg change template_historical_battles to mktw_historical_battles and tut2_battle.xml to agincourt_battle.xml db
This box is 619x335 pixels big. If we look at data.pack - db - historical_battles_ui_locations_tables we can see that Utus is located at x=619, y=342. It is not 100% pixel perfect, so a bit of trial and error is required to get it in exactly the right position.
Anyways now we go to historical_battles_ui_locations_tables, input the exact same name as you did in battles_table and put the x, y and height percent values in.
This lua script contains a list of the historical battles that may be played. Below is the relevant section. Change tut2_template to the name of your battle.
This is a 512x256 png file that acts as a little preview image of your historical battle. Here are three examples I've used, and two pictures of them actually in-game. Example 1 Example 2 Example 3
This is a text file where you put in the name, and the description of your historical battles. For example...
battles_localised_name_battleofthebastards
Battle of the Bastards
battles_description_battleofthebastards
The Bastard of the Dreadfort and Warden of the North, Ramsay Bolton, holds Winterfell – the ancestral seat of House Stark. Jon Snow, the Bastard of Winterfell has come to reclaim his home at the head of a ragged army of Wildlings, and bannermen loyal to his cause. The bastards will clash outside the walls of Winterfell, where only one may emerge victorious.
[5] Go back to (prefix)_battle.xml and we're going to set up what factions are involved in the historical battle; what map the battle will be fought on; what weather and lighting will be present; what the victory condition is; where the deployment area is; and how big the playable area is. script
tut2_template
(prefix)_battle.xml
The Factions
The following example comes from the Battle of the Bastards. Look at the alliance id and <army> tags below. You can see that House Stark and the Vale are two factions on the same team (alliance 0). The Vale is controlled by the AI. You can also see that the two factions have different deployment areas.
The faction/alliance that is listed FIRST in the battle.xml will always be controlled by the player.
You may want to check out other historical battles from Rome 2 or Attila to discover different victory conditions, or ways of doing the deployment area. I generally just leave the victory condition as to kill or rout all enemy units and the deployment area to be bigger than the entire map. This means units can spawn anywhere, and the loading screen won't show a specific deployment area which allows the location of reinforcements/enemies to be a surprise. This makes it impossible to position units with the guerrilla deployment trait however.
It is recommended that you just test random custom battles to find the battle map you’d like to use.
If you use a vanilla siege map, you need to decide whether you want to use an upgraded version of the map, and whether there should be siege escalation (destroyed buildings/walls).
If you want to use a type of battle map other than a normal land battle (for example a port, ambush, river crossing, siege), you need to change the following entry
You also need to consider how big you want the playable area dimensions to be. CA's documentation says the maximum size is 1950. You may tweak this if you want to remove access to certain parts of a pre-existing map, eg for "Muster at Dunharrow" I made the playable area smaller and slightly offset so the player couldn't use a side passage to get behind Dunharrow.
I don’t know what the prevailing wind does, it’s probably just for naval battles. Anyways, to choose the environment key you go to data.pack – weather and choose what type of weather you want. You have to test them one by one within the historical battle if you’re trying to get a precise one.
The time_of_day, season and precipitation_type settings under <battle_description> do not seem to have any effect.
Step 2 (Friendly Units)
Spoiler Alert, click show to read:
Background Information
Look at the code below. This example comes from the Battle of the Fist of the First Men from my Battles of the North mod.
As you can see here, the unit is a custom general unit for Jeor Mormont. It's x and y co-ordinates; orientation radians; width; experience level; and unique name are al listed. You can also do other things such as give it special abilities via script, or make it start at 50% strength.
Width determines how stretched out the unit is/how many rows.
In the Battle of the Bastards historical battle script, you may have noticed that the units look like this.
hide_prebattle="true" means that the unit will not appear on the loading screen at the start, so when they appear (eg due to a reinforcement script), it will come as a surprise to the plawyer.
The reason I have unit script_name rather than just plain <unit> is because I have referenced this unit in a reinforcement script in that battle.
Decide how your battle will work.
How many units will each side have? Eg 10 infantry, 4 cavalry, 5 archers.
What specific units will each side have? Eg 5 Palatina Guard, 5 Cohors, 2 Scout Equites, ect.
Once you have worked that out go back to [prefix]_battle.xml
[1] Go to the deployment area for your faction (the very first faction), and change the width and height metres to something insane, this will mean you can position units anywhere on the map. Eg:
[3] Copy and paste this exact line of code for the amount of units you want, eg 19 units. Change the unit script_name for each, eg template_01, template_02, template_03, ect.
[4] Then replace the unit_type with the specific units you want, eg Cohors or Scout Equites
[5] Then give the enemy faction a single unit. This is a custom unit I've made that cannot move, and has very high health. This means it won't interfere with you setting up your battle/cutscenes, and won't be killed off - prematurely ending the battle.
[6] Now go to [prefix]_declarations.lua and create the SUnit script. This will tie all of the units together, and allow you to create orders for the units and position them.
"Player" refers to the name of the army, and template_01 to the unit script_name in [prefix]_battle.xml. Ensure that the SUnit group, eg "SUnits_Template_All", matches the SUnit group referenced in [prefix]_start.lua function Start_Battle(). An example is provided below:
Lets do the battle co-ordinates now.
[1] Launch the mod, and your historical battle. After you finish loading, you will see all of your units on top of each other and a big "Start Battle" button. DO NOT click it.
[2] Move all of your units to the positions you want them to be in at the start of your battle.
[3] Once they are all positioned, click the "Start Battle" button, alt tab and go to C:\Program Files (x86)\Steam\steamapps\common\Total War Attila\data, and open Historical_Battle_Camera_log.txt. Each unit's position, orientation, and width will have been logged. The log will look like this:
[4] Now close Attila, and go to [prefix]_battle.xml of your mod.
[5] Replace each unit’s position, orientation radians, and width metres code with the ones that have been generated in Historical_Battle_Camera_log.txt.
[6] Save your mod.
Step 4 (Enemy Units + Positioning Enemy Units)
Spoiler Alert, click show to read:
Now we will do the same for the enemy units.
[1] Go to [prefix]_battle.xml and copy everything between the <army> </army> tags for your faction. Save this in a separate text folder.
[2] Now move everything between YOUR <army> tags to the bottom, so it is in <alliance id="1"> and everything from there to <alliance id="0">. This will make it so you are playing as the enemy.
[3] Now repeat Step 2 (Friendly Units) and Step 3 (Positioning Friendly Units) for the enemy, except make sure that the position and orientation radians are as below:
[4] In case you have forgotten, this means copy and paste a unit x amount of times on the same position, give the “enemy” faction a single tutorial placeholder unit, go in-game and set up the units, get their co-ordinates + orientation + width and paste this data into [prefix]_battle.xml.
[5] Once you have done that, swap the <army> tags around again so you have your entire army with its co-ordinates, and the enemy has their entire army with their co-ordinates.
Step 5 (Cutscenes)
Spoiler Alert, click show to read:
[1] The way I set up a cutscene is to make all of the units spawn 50m backwards from the position I set them to in Steps 3 and 4, and then make them walk to the start position during the cutscene whilst I chain together shots of them walking. Alternatively, you can chain together a path of co-ordinates for units to walk (eg see Ambush in Ithilien cutscene.lua and the code below)
[2] To set up a cutscene my way, go to [prefix]_cutscenes.lua and look at the following entries:
Code:
local Cutscene_Intro = cutscene:new(
"Cutscene_Intro", -- unique string name for cutscene
ga_player_01:get_unitcontroller(), -- unitcontroller over player's army
2000, -- duration of cutscene in ms
function() Start_Battle() end -- what to call when cutscene is finished
);
[3] 2,000 is the length of the cutscene in milliseconds (so 2 seconds). Change this to 100 (1/10th of a second) so that the cutscene will end immediately, but the units will still be walking to their position. This will let us put together the shots for our own cutscene.
[4] Now look at "teleport_to_start_location_offset". I have added "--" to disable this function. Remove "--" to re-enable it. The -50 in location offset means the units start 50m behind their start position. Keep this (or make it longer/shorter, eg 100m). It will depend on the units of course, but it takes roughly 40 seconds for vanilla infantry to walk 50m. A 40s cutscene seems a good length.
[5] Now go to Cutscene_Intro:action(function() cam:fade(false, 0.5) end, 0); and disable this line by adding -- to the front of it. i.e. -- Cutscene_Intro:action(function() cam:fade(false, 0.5) end, 0);
This will mean the camera does not take half a second to fade in. Once you have finished your cutscene, remember to remove the -- so it functions again.
[6] Now save the mod and launch it. Click Start Battle, and immediately click “P” to pause it.
[7] Move the camera to where you want the cutscene to start, and looking in the direction you want; click the button in the middle bottom of the screen to trigger the camera log script which will write down the co-ordinates that the camera was in at that time; start the battle; pause again after a certain amount of time, eg 5 seconds, and move the camera to where you want it to end up; and click the button again.
These logs will be saved to "Historical_Battle_Camera_log.txt" in C:\Program Files (x86)\Steam\steamapps\common\Total War Attila\data in a format looking like this:
[8] It may be useful to open Historical_Battle_Camera_log.txt and write notes such as those below. These will remind you how long the cutscene should be. Make sure to save the file after each edit you make, so your notes are not overridden by the next set of co-ordinates. Eg:
[9] Rinse and repeat for the cutscene co-ordinates you want.
[10] The final co-ordinates you need are for the shot that the cutscene will end on, and that players will be teleported to if they decide to skip the cutscene. This is broken into two parts – where the camera actually is, and where its looking. Eg the first set of code should be near the top, the 2nd set of code is the final cutscene shot.
[11] Finally, change the duration of the cutscene at the start to however long your cutscene is. eg:
Code:
local Cutscene_Intro = cutscene:new(
"Cutscene_Intro", -- unique string name for cutscene
ga_player_01:get_unitcontroller(), -- unitcontroller over player's army
22000 -- duration of cutscene in ms
);
Optional Extras
Step 6 (Subtitles + Advisor)
Spoiler Alert, click show to read:
[1] You can add subtitles to your cutscenes by editing scripted_subtitles_tables and scripted_subtitles.loc, then putting the cutscenes in [prefix]_cutscenes.lua.
It is very self explanatory and easily worked out.
[2] You can also add messages that pop-up mid-battle from the advisor. Eg when a general dies, when you’re winning, when the battle starts. You do this by editing advice_levels_tables; advice_threads_tables; advice_levels.loc; and [prefix]_start.lua.
As above, this is very self-explanatory and easily worked out. Look at Att.HB.AD.001 for example. You MAY need to do advice_levels_tables in the Assembly Kit so it will automatically generate a key for you. Otherwise, just override a vanilla advice entry.
[4] If you want the change what the advisor looks like, make sure the Porthole Quality is set to 3D in Graphics Settings and change att_advisor_monk.variantmeshdefinition.
Unfortunately EVERY historical battle will use the SAME advisor, you can’t have a different advisor per battle.
Step 7 (Difficulty Levels)
Spoiler Alert, click show to read:
There are a number of interesting scripts that we have created and saved in script - _historical_battle_library. I will explain difficulty_script_remove_units_per_difficulty.lua and difficulty_script_prevent_reinforcements.lua
[1] Decide whether you want:
a) all the units to spawn at the start, but for the amount of units to be dependent on the difficulty you choose (eg -4 enemy units on easy difficulty; 0 change on normal difficulty; -4 friendly units on hard difficulty)
OR
b) You want extra units to arrive as reinforcements after a certain amount of time (which can also be dependent on difficulty, eg reinforcements only arrive on easy difficulty)
[1] If you want all the units to spawn at the start, but for certain units to be automatically killed at the start of the battle depending on the difficulty you choose, then add difficulty_script_remove_units_per_difficulty.lua to your battle, eg tut_tutorial_battle, alongside all your other scripts. Make sure to add your prefix to the start, eg tut_difficulty_script_remove_units_per_difficulty.lua
[3] Now determine what units you want to remove on each difficulty level. Eg:
Easy (1)
Remove 2 Enemy Units
Normal (0)
Remove 0 Units
Hard (-1)
Remove 2 Friendly Units
Very Hard (-2)
Remove 4 Friendly Units
Legendary (-3)
Remove 6 Friendly Units
[4] I have provided an example of what the above would look like in the template pack.
[5] I bundle Easy and Normal together, and Very Hard and Legendary together by giving them the same options, and renaming the difficulty levels in random_localisation_strings.loc. You may want to do the same.
[6] Make sure you do NOT remove the first unit in an army, this is the general. If you remove it, that army will immediately suffer a morale penalty as their general has died.
[1] If you instead want extra units to arrive as reinforcements after a certain amount of time, then:
- replace [prefix]_reinforcements.lua with reinforcements_script.lua from _historical_battle_library; and
- add difficulty_script_prevent_reinforcements.lua to your battle, eg tut_tutorial_battle, alongside all your other scripts. Make sure to add your prefix to the start, eg tut_difficulty_script_prevent_reinforcements.lua
[3] This script is a bit more complicated.
At the top of the new reinforcements script, it says "Isengard_Reinforcements_1_Countdown = 45000". This means that after 45 seconds this function (Isengard reinforcements) are called. You can also see that it takes another 0.5 seconds for the units to spawn. And then, after another 25 seconds the Isengard reinforcements will go to x103, z-171 and attempt to defend it in a 200m radius. This is a way of directing the AI towards a particular destination and telling it to attack anyone nearby.
[4] If you want you can make a cutscene trigger when the reinforcements spawn.
[5] If you look at [prefix]_difficulty_script_prevent_reinforcements you’ll see that:
a. Isengard Reinforcements 1 ALWAYS spawn;
b. Isengard Reinforcements 2 (which I have deleted) only spawn on Easy or Normal difficulty (1 or 0);
c. Rohan Reinforcements only spawn on Very Hard or Legendary difficulty (-2 or -3).
Last edited by Commissar Caligula_; January 07, 2024 at 05:22 AM.