Results 1 to 2 of 2

Thread: Scripted 'Historical' Battles

  1. #1
    Commissar Caligula_'s Avatar The Ecstasy of Potatoes
    Join Date
    Dec 2013
    Location
    The alcoves in the Koningin Astridpark
    Posts
    5,866

    Default Scripted 'Historical' Battles

    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, 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!

    Previews





    Other resources you should look at include:




    • DETrooper: Ruination of the Nomads, Crossing the Danube and Fall of Constantinople
      Requires The Long Night to play


    What You Can Do
    So as I said previously, DETrooper and I have worked together on two historical battles for the (now dead) mod Seven Kingdoms: Total War. The Battle of the Bastards and the Battle of the Fist of the First Men.

    For the Battle of the Bastards we had the following things...
    • A custom battle map
    • A two-minute long cutscene which had the script from the Battle of the Bastards episode with Jon, Davos and Tormund talking about their battle plan, and a visual overview of the map.
    • Stark units positioned in the treeline on one side of the field whilst the Bolton soldiers advanced towards them from the other side
    • Knights of the Vale who arrived as reinforcements after about 5 minutes.

    You can do a lot of other stuff, check the vanilla historical battles from Attila and Rome 2; and I would highly advise that you check out this documentation created by CA.


    Step-by-Step Tutorial
    Step 1 (Basic Data Entry)
    Spoiler Alert, click show to read: 
    [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, 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 tutorial_ and tut_ prefix for each table with the prefix for your mod. battle. Eg change tutorial_historical_battles to mktw_historical_battles and tut_battle.xml to agincourt_battle.xml
    db
    battles_tables
    historical_battles_ui_locations_tables
    scripted_subtitles_tables
    script
    tut_battle.xml
    tut_cutscenes.lua
    tut_script_camera_log.lua
    tut_script_difficulty_selector.lua
    tut_script_reinforcements.lua
    tut_script_reinforcements_difficulty_selector.lua
    tut_script_units.lua
    tut_start.lua
    text
    tutorial_historical_battles.loc
    tutorial_historical_battles_scripted_subtitles.loc
    tutorial_historical_battles_uied_components_texts.loc


    [4] Go to all of the following tables and edit them.
    campaign_maps
    main_attila_map
    main_attila_map.png
    If you want a custom map as the background of the historical battle selection screen, then you need to follow the following steps.
    db
    battles_tables
    In this table you put the name of the battle, where the battle.xml is found and where the preview screenshot is found.

    The only entries in this table you will need to change are:
    • key; (the name of the battle)
    • specification; (the path of the battle.xml)
    • screenshot_path (the path of the preview screenshot).
    historical_battles_ui_locations_tables
    So now that you have done that, you want to get your historical battles located in the right area on the map.

    lua_scripts
    frontend_hbs.lua
    This lua script contains a list of the historical battles that are available in your mod. Below is the relevant section. Change tut_tutorial_battle to the name of your battle.
    Code:
    Historic_Battle_List = {
    	"Catalaunian_Plains",
    	"Utus",
    	"Ravenna",
    	"Frigidus_River",
    	"Cap_Bon",
    	"Samarra",
    	"Cartagena",
    	"Soissons",
    	"Ad_Decimum",
    	"Adrianople",
    	"Dara",
    	"tut_tutorial_battle"
    };
    script
    tut_tutorial_battle
    screenshot_small.png
    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

    In Game 1
    In Game 2
    (prefix)_battle.xml
    Scroll down to the very bottom and change
    Code:
    (<battle_script prepare_for_fade_in="true">tut_start.lua</battle_script>)
    to the name of your battle.

    text
    db
    random_localisation_strings.loc
    This is a text file where you can change what each difficulty level is called (if you want to). The default difficulties are Easy, Normal, Hard, Very Hard, Legendary. In “The War of the Ring” I changed the difficulties as follows:
    random_localisation_strings_string_difficulty_level_1Normal
    random_localisation_strings_string_difficulty_level_2Normal
    random_localisation_strings_string_difficulty_level_3Hard
    random_localisation_strings_string_difficulty_level_4Legendary
    random_localisation_strings_string_difficulty_level_5Legendary Plus
    battles.loc
    This is a text file where you put in the name, and the description of your historical battles. For example...

    battles_localised_name_battleofthebastardsBattle of the Bastards
    battles_description_battleofthebastardsThe 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.
    uied_component_texts.loc
    This is a text file where you can change the text displaying "Historical Battles" to whatever custom text you want, for example "Battles of the North" or "The War of the Ring". These are the three strings that you would change:

    uied_component_texts_localised_string_string_NewState_Text_3a000cm
    uied_component_texts_localised_string_button_txt_NewState_Text_49000c
    uied_component_texts_localised_string_string_NewState_Text_3a000c


    [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
    tut_tutorial_battle
    (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.
    Code:
    	<alliance id="0">
    	
    		<army>
    		
    			<faction>historical_house_stark</faction>
    			
    			<deployment_area>
    				<centre x="160.00" y="-170.00"/>
    				<width metres="250.00"/>
    				<height metres="125.00"/>
    				<orientation radians="0.00"/>
    			</deployment_area>
    			
    			<unit script_name="stark1" hide_prebattle="true">
    				<unit_type type="Historical_Jon"/>
    				<position x="160.70" y="-130.35"/>
    				<orientation radians="0.00"/>
    				<width metres="21.70"/>
    			</unit>
    		</army>
    
    		<army>
    			<faction>historical_vale</faction>
    			
    			<deployment_area>
    				<centre x="-330.00" y="-131.00"/>
    				<width metres="250.00"/>
    				<height metres="125.00"/>
    				<orientation radians="1.00"/>
    			</deployment_area>
    			
    			<unit script_name="valereinforcements1" hide_prebattle="true">
    				<unit_type type="Vale_Arryn_2_Heavy_Cavalry"/>
    				<position x="-250" y="-206"/>
    				<orientation radians="1"/>
    				<width metres="30"/>
    				<unit_experience level="4"/>
    			</unit>
    		</army>		
    		<victory_condition>
    			<kill_or_rout_enemy></kill_or_rout_enemy>
    		</victory_condition>
    		<rout_position x="0.00" y="0.00"/>
    		
    	</alliance>
    Battle Map
    Now you need to choose what exact battle map you want to use for your historical battle. You can choose to use a custom battle map, such as
    Code:
    	<battle_map_definition>
      <name>terrain/tiles/battle/assembly_kit/battle_of_the_bastards/battlefield</name>
    	</battle_map_definition>
    Or you can use a vanilla battle map, such as
    Code:
    	<battle_map_definition>
    		<name>Terrain/battles/main_attila_map/</name>
    		<tile_map_position x="0.089" y="0.094">/</tile_map_position>
    		<tile_upgrade>level1</tile_upgrade>
    		<tile_upgrade>escalation3</tile_upgrade>
    	</battle_map_definition>
    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
    Code:
    	<battle_description>
    		<type>land_normal</type>
    	</battle_description>
    The options available to you are:
    Code:
    coastal_battle
    land_ambush
    land_bridge
    land_normal
    naval_blockade
    naval_breakout
    naval_normal
    port_assault
    settlement_relief
    settlement_sally
    settlement_standard
    settlement_unfortified
    unfortified_port
    You also need to consider how big you want the playable area dimensions to be. You may tweak this if you want to remove access to certain parts of a pre-existing map, eg for Fords of Isen I made the playable area smaller so the player couldn't use the river crossing to the north of the map.
    Code:
    	<playable_area dimension="1924"></playable_area>
    Weather
    You can also choose the precise weather that you want to occur in the battle, for example
    Code:
    	<weather>
    		<environment_key>weather\default\default\land\day\snow\snow_3_heavy.environment</environment_key>
    		<prevailing_wind x="1.00" y="0.00"/>
    	</weather>
    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.
    Code:
    			<unit>
    				<unit_type type="Wall_Watch_1_Jeor_Mormont"/>
    				<position x="-21.70" y="400.35"/>
    				<orientation radians="3.14"/>
    				<width metres="21.70"/>
    				<unit_experience level="0"/>
    				<general>
    					<name>2147363108</name>
    					<star_rating level="0"/>
    				</general>
    			</unit>
    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. For infantry units I generally give them 21.70 width, archers get 27.00 and cavalry get 35.00

    In the Battle of the Bastards historical battle script, you may have noticed that the units look like this.
    Code:
    			<unit script_name="stark1" hide_prebattle="true">
    
    			<unit script_name="stark3">
    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] Then go up to the top, delete all of the existing <unit> </unit> code, and paste in the following code.
    Code:
    			<unit>
    				<unit_type type="att_rom_palatina_guards"/>
    				<position x="-400" y="360"/>
    				<orientation radians="0.00"/>
    				<width metres="27.91"/>
    			</unit>
    [2] Copy and paste this exact line of code for the amount of units you want, eg 19 units.

    [3] Then replace the unit_type with the specific units you want, eg Cohors or Scout Equites

    [4] Then change the width. If its an Infantry unit, I generally give them 21.70 width; Archers get 27.00; Cavalry get 35.00
    Keep the position the same, we’ll sort that out in Step 3.

    [5] Then give the enemy faction a single unit. This is a unit that can’t move, and therefore can’t attack you while you set up Step 3.
    Code:
    			<unit>
    				<unit_type type="tutorial_placeholder_unit"/>
    				<position x="50" y="250"/>
    				<orientation radians="3.14"/>
    				<width metres="27.91"/>
    			</unit>

    Step 3 (Positioning Friendly Units)
    Spoiler Alert, click show to read: 
    Lets do the battle co-ordinates now.
    [1] Go to [prefix]_start.lua, and make sure that the code at the top says
    Code:
    local logging_enabled = true;
    [2] Now launch the mod, and your historical battle. All of your units will spawn on top of each other.

    [3] Move all of your units roughly to where you want them to start

    [4] Zoom in all the way to the ground, and double left click on a unit from the bar at the bottom. This will teleport you to that unit’s position.

    [5] Now click the flag button at the bottom middle of your screen. This will log the camera’s co-ordinates at the time you pressed the button – effectively that unit’s co-ordinates.


    [6] Double left click on each unit, and do this for each of them.

    [7] Once you are done, alt tab and go to C:\Program Files (x86)\Steam\steamapps\common\Total War Attila\data, open Historical_Battle_Camera_log.txt and make sure there are the same amount of entries as you have units. Eg 19 entries for 19 units.

    [8] Now close Attila, and go to [prefix]_battle.xml of your mod.

    [9] Replace each unit’s position code with the one that has been generated in Historical_Battle_Camera_log.txt. Ignore the line below it about Cutscene_NAME

    [10] For orientation radians, 0 means the unit is facing north; whilst 3.14 should mean the unit is facing south. This website may help you work this out, it might take a bit of trial and error though. 45 degrees should be 0.78 radians for example. https://www.rapidtables.com/convert/...ians.html?x=45

    [11] 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 steps 2 and 3, except make sure that the position and orientation radians are as below:
    Code:
    				<position x="50" y="250"/>
    				<orientation radians="3.14"/>
    [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, and paste these co-ordinates 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 (Difficulty Levels)
    Spoiler Alert, click show to read: 
    [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)

    [2] Whichever option you want, you will need to go to [prefix]_battle.xml and change the <unit> tag from
    Code:
    			<unit>
    				<unit_type type="att_rom_palatina_guards"/>
    			...
    			</unit>
    			
    			<unit>
    				<unit_type type="att_rom_cohors"/>
    			...
    			</unit>
    to
    Code:
    			<unit script_name="tutorial1">
    				<unit_type type="att_rom_palatina_guards"/>
    			...
    			</unit>
    			
    			<unit script_name="tutorial2">
    				<unit_type type="att_rom_cohors"/>
    			...
    			</unit>
    [3] Do this for each unit in order. Eg tutorial1; tutorial2; tutorial3. Or rohan1; rohan2; rohan3; rohanreinforcements1; rohanreinforcements2; rohanreinforcements3.

    [4] Then go to [prefix]_script_units.lua and complete this file for your battle. You will be able to see how it works from looking at the template I’ve provided.

    [5] Whichever option you choose, you may wish for certain units to be hidden from the loading screen at the start (eg the units that will get killed off if you go with option 1, or the reinforcements if you go with option 2). To do this, add hide_prebattle=”true” to the unit script_name of a unit, eg below:
    Code:
    			<unit script_name="tutorial1" hide_prebattle="true">
    Step 5a (Difficulty Selector Script)
    [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 go to [prefix]_start.lua and remove “--” from
    Code:
    -- require (battle_shortform .. "_script_units");
    -- require (battle_shortform .. "_script_difficulty_selector");
    [2] 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
    [3] I have provided an example of what the above would look like in the template pack.

    [4] 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.

    [5] 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.
    Step 5b (Reinforcement Script)
    [1] If you instead want extra units to arrive as reinforcements after a certain amount of time, then go to [prefix]_start.lua and remove “--” from
    Code:
    -- require (battle_shortform .. "_script_units");
    -- require (battle_shortform .. "_script_reinforcements");
    -- require (battle_shortform .. "_script_reinforcements_difficulty_selector");
    [2] [prefix]_script_reinforcements.lua is a bit more complicated.
    At the top, 45000 is in milliseconds, meaning 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.

    [3] If you want you can make a cutscene trigger when the reinforcements spawn.

    [4] You’ll note that the Rohan reinforcements will be controlled by the player, as the AI lines are commented out/disabled with the use of --.

    [5] If you look at [prefix]_script_reinforcements_difficulty_selector 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).

    Step 6 (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. I’m sure there are fancier ways of doing this, eg chaining together a path of co-ordinates for them to walk to eg walking through city streets but that would require quite a bit of effort.

    [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_franks_01:get_unitcontroller(), -- unitcontroller over player's army
    	50000 				 -- duration of cutscene in ms
    );
    [3] 50,000 is the length of the cutscene in milliseconds (so 50 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.
    Code:
    	Cutscene_Intro:action(function() ga_franks_01:teleport_to_start_location_offset(0, -50); end, 0);
    	Cutscene_Intro:action(function() ga_soissons_01:teleport_to_start_location_offset(0, -50); end, 0);
    [4] 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.
    Code:
    	Cutscene_Intro:action(function() cam:fade(false, 0.5) end, 0);
    [5] Delete this line as well. This will remove the camera fading in for half a second.

    [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 bottom middle of the screen to trigger the camera log script; 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.
    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:
    Code:
    	Cutscene_Intro:action(function() cam:move_to(v(-118, 272, -250), v(-81, 257, -277), 0, true, 35) end, [START TIME #]);
    	Cutscene_Intro:action(function() cam:move_to(v(-69, 261, -260), v(-101, 246, -292), 10, true, 33) end, [START TIME #]);
    From 0-10 seconds
    
    	Cutscene_Intro:action(function() cam:move_to(v(-54, 260, -250), v(-12, 251, -272), 0, true, 35) end, [START TIME #]);
    	Cutscene_Intro:action(function() cam:move_to(v(-36, 261, -242), v(5, 253, -265), 5, true, 33) end, [START TIME #]);
    From 10-15 seconds
    
    	Cutscene_Intro:action(function() cam:move_to(v(152, 274, -217), v(194, 258, -198), 0, true, 35) end, [START TIME #]);
    	Cutscene_Intro:action(function() cam:move_to(v(176, 275, -202), v(218, 260, -184), 5, true, 33) end, [START TIME #]);
    From 15-20 seconds
    [8] Rinse and repeat for the cutscene co-ordinates you want.

    [9] 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.
    Code:
    	POS_Cam_Cutscene_Intro_Final = v(8, 285, -398);
    	Targ_Cam_Cutscene_Intro_Final = v(4, 275, -350);
    Code:
    	Cutscene_Intro:action(function() cam:move_to(v(4, 280, -362), v(3, 255, -320), 0, true, 35) end, 20000);
    	Cutscene_Intro:action(function() cam:move_to(POS_Cam_Cutscene_Intro_Final, Targ_Cam_Cutscene_Intro_Final, 2, false, 0) end, 20000);
    10. Finally, change the duration of the cutscene at the start to however long your cutscene is. I made mine 22 seconds.
    Code:
    local Cutscene_Intro = cutscene:new(
    	"Cutscene_Intro", 		 -- unique string name for cutscene
    	ga_franks_01:get_unitcontroller(), -- unitcontroller over player's army
    	22000 				 -- duration of cutscene in ms
    );

    Step 7 (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.

    [3] If you want to change the audio that plays, check the tutorial below.

    [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.



    Other Tutorials
    Last edited by Commissar Caligula_; April 24, 2022 at 04:04 AM.



  2. #2
    Commissar Caligula_'s Avatar The Ecstasy of Potatoes
    Join Date
    Dec 2013
    Location
    The alcoves in the Koningin Astridpark
    Posts
    5,866

    Default Re: Scripted 'Historical' Battles

    Further Information On The Tables Involved
    campaign_maps
    main_attila_map
    main_attila_map.png
    If you want a custom map as the background of the historical battle selection screen, then you need to follow the following steps.



    db
    battles_tables
    In this table you put the name of the battle, where the battle.xml is found and where the preview screenshot is found.

    The only entries in this table you will need to change are:
    • key; (the name of the battle)
    • specification; (the path of the battle.xml)
    • screenshot_path (the path of the preview screenshot).
    historical_battles_ui_locations_tables
    So now that you have done that, you want to get your historical battles located in the right area on the map.

    scripted_subtitles_tables
    If you want subtitles during cutscenes at the start, middle or end of your historical battle then you have to make entries for them here. For example: "HB.BoB.Intro_01" (Historical Battle, Battle of Bastards, Intro 1). There will be no audio for these cutscenes.
    These subtitles are also referenced in [prefix]_scripted_subtitles.loc and [prefix]_cutscenes.lua
    If you want to make custom audio for these scripted subtitles, change character_for_vo key to “Narrator”. This tutorial will teach you how to import custom audio, and this spreadsheet will help you identify the audio files to override.


    lua_scripts
    dev.lua and logging_callbacks.lua and util.lua
    These three lua scripts contain a camera co-ordinates logger script that is a combination of work by the Rome 2 Total Realism team; DETrooper; Case; DrunkFlamingo; Farfadet; and myself. This script helps in creating historical battles as every time you click the button in the middle bottom of the screen a text file will write down the co-ordinates that the camera was in at that time. So you arrange a unit in the location you want it, scroll down to ground level, double left click on the unit to teleport to it, then press the button to log the co-ordinates. The log is created in "Historical_Battle_Camera_log.txt" which is saved in C:\Program Files (x86)\Steam\steamapps\common\Total War Attila\data

    If you want to change what button activates the script, change "mon_crest" in logging_callbacks.lua

    If you are creating historical battles for a mod that has disabled historical battles (such as The Dawnless Days), you may need to edit that mod’s “frontend_scripted.lua” to remove:
    Code:
    	UIComponent(scripting.m_root:Find("button_historical_battle")):SetState("inactive");
    frontend_hbs.lua
    This lua script contains a list of the historical battles that are available in your mod. Below is the relevant section.
    Code:
    Historic_Battle_List = {
        "Catalaunian_Plains",
        "Utus",
        "Ravenna",
        "Frigidus_River",
        "Cap_Bon",
        "Samarra",
        "Cartagena",
        "Soissons",
        "Ad_Decimum",
        "Adrianople",
        "Dara"
    };
    Code:
    Historic_Battle_List = {
        "battleofthebastards",
        "fistoffirstmen"
    };


    script
    (prefix)_battle

    I would highly advise that you check out this documentation created by CA. It tells you all kinds of cool things you can put in your historical battles such as:
    • You can make gates of a settlement be forced open;
    • You can spawn siege equipment for the attackers;
    • You can make it so immediately after a historical battle, the game launches either a campaign as a specific faction, or another historical battle. The 2nd option would let you make a "campaign" out of historical battles;
    • You can set it up so the game hides reinforcing armies from appearing in the opening loading screen. So reinforcements will just appear without warning.

    screenshot_small.png
    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

    In Game 1
    In Game 2
    (prefix)_battle.xml
    This file determines quite a lot of what happens in the historical battle. Such as what factions are involved in the historical battle, how many (and what) units each faction has, the co-ordinates/orientation/width of the units, whether they have chevrons in experience, what the victory condition is, where the deployment area is, the type of weather and lighting, the battle map and how big the playable area is.
    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. The Vale is controlled by the AI. You can also see that the two factions have different deployment areas.
    Code:
    	<alliance id="0">
    	
    		<army>
    		
    			<faction>historical_house_stark</faction>
    			
    			<deployment_area>
    				<centre x="160.00" y="-170.00"/>
    				<width metres="250.00"/>
    				<height metres="125.00"/>
    				<orientation radians="0.00"/>
    			</deployment_area>
    			
    			<unit script_name="stark1" hide_prebattle="true">
    				<unit_type type="Historical_Jon"/>
    				<position x="160.70" y="-130.35"/>
    				<orientation radians="0.00"/>
    				<width metres="21.70"/>
    			</unit>
    		</army>
    
    		<army>
    			<faction>historical_vale</faction>
    			
    			<deployment_area>
    				<centre x="-330.00" y="-131.00"/>
    				<width metres="250.00"/>
    				<height metres="125.00"/>
    				<orientation radians="1.00"/>
    			</deployment_area>
    			
    			<unit script_name="valereinforcements1" hide_prebattle="true">
    				<unit_type type="Vale_Arryn_2_Heavy_Cavalry"/>
    				<position x="-250" y="-206"/>
    				<orientation radians="1"/>
    				<width metres="30"/>
    				<unit_experience level="4"/>
    			</unit>
    		</army>		
    		<victory_condition>
    			<kill_or_rout_enemy></kill_or_rout_enemy>
    		</victory_condition>
    		<rout_position x="0.00" y="0.00"/>
    		
    	</alliance>
    The Units
    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.
    Code:
    			<unit>
    				<unit_type type="Wall_Watch_1_Jeor_Mormont"/>
    				<position x="-21.70" y="400.35"/>
    				<orientation radians="3.14"/>
    				<width metres="21.70"/>
    				<unit_experience level="0"/>
    				<general>
    					<name>2147363108</name>
    					<star_rating level="0"/>
    				</general>
    			</unit>
    As you can see here, the unit is a custom general unit for Jeor Mormont. It's x and y co-ordinates are detailed, it's orientation radians is listed (I honestly don't have a clue how you work this out, and it is incredibly annoying), it's width is listed, its experience level and because it is the general I have given it a custom name (which requires files like names_tables and names.loc).

    Width determines how stretched out the unit is/how many rows. For infantry units I generally give them 21.70 width, archers get 27.00 and cavalry get 35.00

    In the Battle of the Bastards historical battle script, you may have noticed that the units look like this.
    Code:
    			<unit script_name="stark1" hide_prebattle="true">
    
    			<unit script_name="stark3">
    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.
    Weather
    With historical battles, you can choose the precise weather that you want to occur in the battle, for example
    Code:
    	<weather>
    		<environment_key>weather\default\default\land\day\snow\snow_3_heavy.environment</environment_key>
    		<prevailing_wind x="1.00" y="0.00"/>
    	</weather>
    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.
    Battle Map
    First off, you have to choose what exact battle map you want to use as your historical battle. You can choose to use a custom battle map, such as
    Code:
    	<battle_map_definition>
      <name>terrain/tiles/battle/assembly_kit/battle_of_the_bastards/battlefield</name>
    	</battle_map_definition>
    Or you can use a vanilla battle map, such as
    Code:
    	<battle_map_definition>
    		<name>Terrain/battles/main_attila_map/</name>
    		<tile_map_position x="0.089" y="0.094">/</tile_map_position>
    		<tile_upgrade>level1</tile_upgrade>
    		<tile_upgrade>escalation3</tile_upgrade>
    	</battle_map_definition>
    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.

    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
    Code:
    	<battle_description>
    		<type>land_normal</type>
    	</battle_description>
    The options available to you are:
    Code:
    coastal_battle
    land_ambush
    land_bridge
    land_normal
    naval_blockade
    naval_breakout
    naval_normal
    port_assault
    settlement_relief
    settlement_sally
    settlement_standard
    settlement_unfortified
    unfortified_port
    You also need to consider how big you want the playable area dimensions to be.
    Code:
    	<playable_area dimension="1924"></playable_area>
    (prefix)_cutscenes.lua
    This controls how long the cutscenes are, how long each individual shot is, the co-ordinates each individual shot starts at and tracks to, the subtitles that appear, when the subtitles appear and for how long, whether the two factions march into position during the cutscene and whether there are additional cutscenes such as reinforcements or at the end of the battle.

    Code:
    POS_Cam_Cutscene_Intro_Final = v(162, 100, -350);
    Targ_Cam_Cutscene_Intro_Final = v(162, 50, -210);
    I believe this is telling the camera what co-ordinates it should be at (more below on co-ordinates), and what it should be looking at when the cutscene ends, or if you skip the cutscene. You will need to play around with this to work it out.


    Code:
    	Cutscene_Intro:action(function() ga_franks_01:teleport_to_start_location_offset(0, -50); end, 0);	
    	Cutscene_Intro:action(function() ga_soissons_01:teleport_to_start_location_offset(0, -50); end, 0);	
    	Cutscene_Intro:action(function() ga_soissons_01:set_visible_to_all(true); end, 0);	
    	
    	Cutscene_Intro:action(function() ga_franks_01:goto_start_location(false); end, 0);
    	Cutscene_Intro:action(function() ga_soissons_01:goto_start_location(false); end, 0);
    This is telling all ga_franks_01 and ga_soissons_01 units to start 50m away from their start position that is set in battles.xml, and to walk to the start position as the cutscene plays. I believe there are modifiers where you can make them walk or run. Presumably you could even make them walk to numerous different co-ordinates during the cutscenes.


    When you use the camera position log script in-game you will notice that the following text is created:
    Code:
    	Cutscene_NAME:action(function() cam:move_to(v(-591, 586, 408), v(-248, 388, 52), [CUTSCENE LENGTH #], true, [FOV #]) end, [START TIME #]);
    You have to:
    1. Give the cutscene a name (eg Cutscene_Intro);
    2. Input the length of the cutscene (this is essentially the speed at which the camera transitions from point A to point B in seconds. Eg it’ll go very slowly if you write 22 seconds, or very fast if you write 2 seconds. You can make it go slowly by writing 22 seconds, but then START the next cutscene after just 8 seconds)
    3. Input the field of view (eg 45 degrees)
    4. Input a start time (this is in milliseconds. Eg if you want the 1st shot to last 20 seconds and the 2nd shot to last 8 seconds then the start time for the first shot is 0, start time for the 2nd shot is 20000, and start time for the 3rd shot is 8000.

    All cutscenes come in pairs. Eg 1 and 2; 3 and 4; 5 and 6.
    The cutscene length for the first of the pair is ALWAYS 0.
    The start time is ALWAYS the same for each pair.

    An example of a finished cutscene is below.
    • Pair 1 and 2 start at 0 seconds, and last for 22 seconds.
    • Pair 3 and 4 start at 22 seconds and last for 20 seconds
    • Pair 5 and 6 start at 42 seconds and last for 10 seconds
    Code:
    	Cutscene_Intro:action(function() cam:move_to(v(382, 76, 217), v(486, 96, 582), 0, true, 45) end, 0);
    	Cutscene_Intro:action(function() cam:move_to(v(382, 72, 185), v(486, 96, 582), 22, true, 43) end, 0);
    
    	
    	Cutscene_Intro:action(function() cam:move_to(v(150, 52, 39), v(167, 7, 416), 0, true, 45) end, 22000);
    	Cutscene_Intro:action(function() cam:move_to(v(150, 52, 23), v(167, 7, 416), 20, true, 43) end, 22000);
    
    
    	Cutscene_Intro:action(function() cam:move_to(v(217, 48, 210), v(-539, -163, 1082), 0, true, 45) end, 42000);
    	Cutscene_Intro:action(function() cam:move_to(v(217, 48, 209), v(-525, -163, 1082), 10, true, 43) end, 42000);
    You can also add subtitles to the cutscene as seen below (also referenced in “scripted_subtitles_tables” and “[prefix]_scripted_subtitles.loc”).
    You can see each subtitle has a start time, and then a function to end that subtitle at a certain time (eg AT 20 seconds, NOT AFTER 20 seconds).
    Code:
    	Cutscene_Intro:action(function() subtitles:set_text("Att.HB.BoB.Intro_01") end, 3000);
    	Cutscene_Intro:action(function() subtitles:clear() end, 12000);
    
    
    	Cutscene_Intro:action(function() subtitles:set_text("Att.HB.BoB.Intro_02") end, 14000);
    	Cutscene_Intro:action(function() subtitles:clear() end, 20000);
    
    
    	Cutscene_Intro:action(function() subtitles:set_text("Att.HB.BoB.Intro_03") end, 23000);
    	Cutscene_Intro:action(function() subtitles:clear() end, 31000);
    
    
    	Cutscene_Intro:action(function() subtitles:set_text("Att.HB.BoB.Intro_04") end, 33000);
    	Cutscene_Intro:action(function() subtitles:clear() end, 40000);
    
    
    	Cutscene_Intro:action(function() subtitles:set_text("Att.HB.BoB.Intro_05") end, 43000);
    	Cutscene_Intro:action(function() subtitles:clear() end, 51000);

    You can also trigger cutscenes to occur after reinforcements arrive.
    (prefix)_script_difficulty_selector.lua
    This script reads what difficulty level you selected when you started the battle (eg Easy, Normal, Hard, Very Hard, Legendary) and can be used to apply a handicap eg certain reinforcement scripts are only called on a particular difficulty.
    The handicap values are as follows:
    Easy1
    Normal0
    Hard-1
    Very Hard-2
    Legendary-3

    I have also included the old version of this script. In this one, you have all your units spawn at the start and then certain units are automatically assigned to a dummy AI group; set to invisible; and killed. Eg:
    Easy10 units spawn on each side, 4 enemy units are killed
    Normal10 units spawn on each side, 0 units are killed
    Hard10 units spawn on each side, 4 of YOUR units are killed

    This may be easier to use than the reinforcement scripts, and I didn’t want all the hard work putting this script together to go to waste. If you use the old version, you don’t need [prefix]_script_reinforcements.lua
    (prefix)_script_misc.lua
    This file contains the camera position log script, and the unit scripts. The unit scripts section is only necessary if you are planning on having reinforcements, or use the difficulty handicap script.

    There must be two alliances, and then you list each army in those alliances.

    You then type in the script names for those units, and what army they’re part of.

    SUnits_ binds certain units together, eg so you can issue them all commands in a script.
    (prefix)_script_reinforcements.lua
    This file contains the reinforcement scripts, if you want to use them. These are tied to the handicap levels in this version of the script. If you look at other historical battles you may be able to work out how to remove the reliance on the handicap script.

    The countdown at the top is in milliseconds. Therefore, 5000 = 5 seconds and 360000 = 6 minutes.

    You can also tie the reinforcement scripts to trigger certain cutscenes when the reinforcements arrive. Check vanilla historical battles, or “Battle of the Bastards” to see how this may be done.

    “SAI_Isengard_Reinforcement_1:defend_position(v(103, -171), 200)” directs this reinforcement army to go to, and defend the position x103 y-171 in a radius of 200m. This should force it to attack any enemy units in that radius. This function does not appear to kick in until 25 seconds have elapsed.
    (prefix)_start.lua
    [prefix]_start.lua requires very little editing.
    • You may turn the logging on/off by typing true or false;
    • You can decide whether the battle starts with the AI immediately attacking you;
    • You can remove the reinforcement script, or the difficulty select script by removing the line below or adding -- to the start, eg:
    Code:
    require (battle_shortform .. "_Cutscenes");
    require (battle_shortform .. "_Script_Misc");
    require (battle_shortform .. "_Script_Difficulty_Selector");
    -- require (battle_shortform .. "_Script_Reinforcements");

    • If you want to add allied armies, you add them as seen below.
    Code:
    ga_franks_01 = gb:get_army(1, 1);
    
    ga_soissons_01 = gb:get_army(2, 1);
    ga_isengard_reinforcements_1 = gb:get_army(2, 2);

    • You can make the Advisor say stuff when the battle starts, when your general/the enemy general dies, and when you win/lose. See vanilla examples or Battle of the Bastards:
    Code:
    -- advice on battle start
    gb:advice_on_message("cutscene_ended", "SK.HB.BA.001", 5000);
    
    
    -- advice on player's general dying
    ga_franks_01:message_on_commander_death("player_general_dies");
    gb:advice_on_message("player_general_dies", "SK.HB.BA.002", 2500);
    
    
    -- advice on enemy general dying
    ga_soissons_01:message_on_commander_death("enemy_general_dies");
    gb:advice_on_message("enemy_general_dies", "SK.HB.BA.003", 2500);
    
    
    -- advice on player winning
    ga_franks_01:message_on_victory("player_wins");
    gb:advice_on_message("player_wins", "SK.HB.BA.004");
    
    
    -- advice on player losing
    ga_franks_01:message_on_defeat("player_loses");
    gb:advice_on_message("player_loses", "SK.HB.BA.005");


    text
    db
    random_localisation_strings.loc
    This is a text file where you can change what each difficulty level is called (if you want to). The default difficulties are Easy, Normal, Hard, Very Hard, Legendary. In “The War of the Ring” I changed the difficulties as follows:
    random_localisation_strings_string_difficulty_level_1Normal
    random_localisation_strings_string_difficulty_level_2Normal
    random_localisation_strings_string_difficulty_level_3Hard
    random_localisation_strings_string_difficulty_level_4Legendary
    random_localisation_strings_string_difficulty_level_5Legendary Plus
    battles.loc
    This is a text file where you put in the name, and the description of your historical battles. For example...

    battles_localised_name_battleofthebastardsBattle of the Bastards
    battles_description_battleofthebastardsThe 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.
    scripted_subtitles.loc
    This is a text file where you put in the subtitles for your cutscenes. For example...

    scripted_subtitles_localised_text_Att.HB.BoB.Intro_01Jon Snow: If he was smart, he’d stay inside the walls of Winterfell and wait us out.
    The subtitles, eg “Att.HB.BoB.Intro_01”, are referenced in scripted_subtitles_tables and [prefix]_cutscenes.lua
    As noted earlier, if you want to make custom audio for these scripted subtitles, this tutorial will teach you how to import custom audio, and this spreadsheet will help you identify the audio files to override.
    uied_component_texts.loc
    This is a text file where you can change the text displaying "Historical Battles" to whatever custom text you want, for example "Battles of the North" or "The War of the Ring". These are the three strings that you would change:

    uied_component_texts_localised_string_string_NewState_Text_3a000cm
    uied_component_texts_localised_string_button_txt_NewState_Text_49000c
    uied_component_texts_localised_string_string_NewState_Text_3a000c


    misc
    db
    videos_tables
    This table is completely optional. It removes the cutscenes when you launch Total War Attila. Since as a modder I am often launching and relaunching the game to test new things, this is very helpful and helps cut down on lag. You can remove the table if you want.
    battle_entities_tables; land_units_tables; main_units_tables; unit_variants_tables
    These tables are just here to implement the placeholder unit which can't move, so you can set up your units without being attacked.
    ui
    layout and fe_backdrop.png
    In a similar fashion to videos_tables, these ui files (“frontend ui – layout”; and “skins – default – fe_backdrop.png”) are completely optional. It simply replaces the animated main menu background with a still image. My laptop is quite bad, so I use whatever tricks I can to improve performance. You can remove these files if you want.
    Last edited by Commissar Caligula_; April 24, 2022 at 04:05 AM.



Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •