Welcome to the homepage for battle scripting in Total War. Here you can find resources and guidance for the creation of scripts that will run in battles.
The original battle scripting documentation can be viewed at the following link. It describes the raw interface the game provides to battle scripts, and will remain useful until the documentation for that raw interface is properly added to these pages:
|Relevant in Battle|
The interface supplied by the battle model to script provides an over-arching
battle object, which is the interface through which most script functionality is provided. From it, a hierarchy of other script objects may be derived that represent objects on the battlefield such as units and armies. See the
battle_hierarchy page for more information.
The battle script libraries build upon this raw interface, providing significant quality of life improvements. The battle script libraries provide a number of objects that wrap underlying objects from the game interface, such as the
battle_manager which wraps the
battle object and
script_unit objects which provide easy access to
battle_unitcontroller interfaces. Where provided, it is strongly preferred to use script wrapper objects in place of the underlying game interface as more functionality is provided and that functionality is often easier to use.
All battles are defined by a battle setup, which is a data construct the battle engine needs in order to load a battle. Battle setups are created from a variety of sources, such as the following:
- The custom battle screen in the case of a custom battle.
- The campaign model in the case of a campaign battle.
- Database records in the case of a set piece battle (quest battle) - see
set_piece_battlesand related tables.
- A battle setup xml.
The battle setup is independent of and is loaded before any script that is run - indeed it is only by adding a script file path to the battle setup that a script file is loaded in the first place. It is important to understand at an early stage that many changes involved in creating a scripted battle actually involve changing the battle setup instead. The act of actually writing battle scripts is often just a subset of the sum process of creating a scripted battle.
Information defined in the battle setup includes the alliance, army and unit compositions of the forces involved, deployment zones, victory conditions, the terrain on which the battle should be fought, the environment and lighting conditions, a time limit, who is attacking, and a path to any lua script file that is to be run with the battle.
An instruction to load a script alongside the battle may be added to a battle setup in one of several different ways, depending on the source of the battle setup.
- Battles loaded from a campaign can be set to load a script with the campaign script command
- Set piece battles defined in the database may be set to load a battle script by setting a path to a script file in the
battle_scriptfield of the relevant record in the
- Battles defined in a battle xml file may be set to load a battle script by creating a
<battle_script>tag. See battle xml documentation elsewhere for more details.
If no battle script is defined in the battle setup then a default battle script is loaded. This is currently hard-coded to be
Most battle scripts calls are made through a central
empire_battle object, commonly referred to in script as the
battle object. A
battle object may be declared in battle scripts by calling
However, the battle script libraries provide an interface to create a
battle_manager object which wraps a supplied
battle object. Any call that would normally be made to a
battle object may be made to a
battle_manager object, which also provides further functionality beyond that offered by a
battle object. A
battle_manager object may be created with the following script:
--load the script librariesload_script_libraries()bm = battle_manager:new(empire_battle:new())
The call to
load_script_libraries must be made before declaring a
battle_manager, as shown above. It is highly recommended to use this function to create a
battle_manager object rather than creating and using a raw
generated_battle system or creating
script_unit objects instead of handles to individual
battle_units is recommended. Nevertheless, it is beneficial to understand the object hierarchy as it underpins the behaviour of all battle scripts.
While handles to
unit objects may be used to test their state, units may not be modified or given orders through this interface. Instead, orders are issued through a
battle_unitcontroller object which must be created seperately. Rather than manually creating handles to
battle_unitcontroller objects, however, it is recommended that
script_unit objects be created instead as these automatically package a
battle_unitcontroller interface together, as well as providing additional functionality.
In practice, the full battle hierarchy and the traditional method of creating a unitcontroller are seldom used in the forms given above. A better method of creating
battle_unitcontroller objects is provided by the
script_unit library. When a
script_unit is created the library script creates a handle to the
battle_unit object for a given unit and a
battle_unitcontroller object with control over it, and packages the two together as a single
script_unit object. Unless creating a
generated_battle, it is highly recommended to set up handles to
script_unit objects instead of manually creating
-- create scriptunit
sunit_1 = script_unit:new(scriptunit:new(army, 1))
unit_1 = sunit_1.unit
uc_1 = sunit_1.uc
generated_battle system, if used, automatically sets up handles for armies and units in a battle. No handles to individual
script_unit objects are explicitly created in this case as battles are co-ordinated and orders are given at the army level. This makes generated battle scripts easier to create and work with, at the expense of the fine unit-level control offered when creating a fully-scripted battle.
Battles progress through phases as they play out. The most notable phases are:
|Triggered at the start of the deployment phase where both alliances get to deploy their armies. This phase change is still triggered even for battles where deployment is being skipped.|
|Triggered at the start of the combat phase.|
|Triggered once an alliance has won the battle and the victory timer starts counting down. This usually takes ten seconds but can be modified by script.|
|Triggered once the victory countdown has completed.|
Scripts can listen for phase changes using the
battle_manager:register_phase_change_callback command. Phase changes are one of the main mechanisms for triggering script at particular events during the battle - particularly on the start of both the deployment and combat phases.
Battle timers can be used to execute functions after a certain period. See the documentation on
battle_manager:repeat_callback for more information. If a callback is given a name then
battle_manager:remove_process can be used to cancel it before it triggers.
-- call after 5 seconds
bm:callback(function() func_to_call() end, 5000, "name_of_callback")
Note that in battle it's common to express time periods in milliseconds, as opposed to campaign where time periods are always expressed in seconds.
Watches are commonly used to poll arbitrary conditions in battle. A watch repeatedly checks a boolean condition until it returns true, and then calls a supplied callback. As with
battle_manager:callback, if a name is supplied for the watch then it can be cancelled with
It is often necessary to specify battle co-ordinates in order to move units or the camera etc. The
battle_vector interface allows objects to be created that represent 2D or 3D positions on the battlefield. The raw game interface supplies the
battle_vector:new() function to create a new vector, but the battle script library supplies the shorthand method
v which is preferable to use.
Vectors may be declared in two dimensions or three dimensions. In the case of a three-dimensional vector the middle number is the height.
--declare a position at [-100, 400]
pos = v(-100, 400)
--declare a position at [-100, 400] and at a height of 50m
pos = v(-100, 50, 400)
Destinations for units to move to can be specified in two or three dimensions - in the latter case the height co-ordinate is discarded.