Battle Index
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 Campaign | |
Relevant in Battle | |
Relevant in Frontend |
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_unit
and 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.
Notifications from the game interface to script generally happen through either Battle Phases
or Battle Timers
. These are documented further down this page.
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_battles
and 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
cm:add_custom_battlefield
. - 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_script
field of the relevant record in thebattle_set_pieces
table. - 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 script/battle/default_battle/battle_start.lua
.
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 empire_battle:new()
.
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 is automatically created when the script libraries are loaded by calling load_script_libraries()
in battle.
--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 battle
object.
Other script objects that represent alliances, armies and units may be retrieved from the battle
/battle_manager
once it's created. See the battle_hierarchy
page for more information.
Using the 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_unit
and 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_unit
and 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 battle_unit
and battle_unitcontroller
objects.
For more information on scriptunits see the script_unit
documentation or the section on Using Scriptunits
on the battle_hierarchy
page.
-- create scriptunit
sunit_1 = script_unit:new_by_reference(scriptunit:new(army, 1))
unit_1 = sunit_1.unit
uc_1 = sunit_1.uc
The generated_battle
system, if used, automatically sets up handles for armies and units in a battle. No handles to individual battle_unit
or 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.
See the generated_battle
page or the section on Setting Up a Generated Battle
on the battle_hierarchy
page for more information.
Battles progress through phases as they play out. The most notable phases are:
Phase | Comments |
Deployment | 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. |
Deployed | Triggered at the start of the combat phase. |
VictoryCountdown | 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. |
Complete | 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:callback
and 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 battle_manager:remove_process
.
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.