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:

EmpireBattleScript.rtf

Relevant in Battle loaded in battle
Back to top

Battle Script Structure

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.

battle hierarchy

Notifications from the game interface to script generally happen through either Battle Phases or Battle Timers. These are documented further down this page.

Back to top

About Battle Setups

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 the battle_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.

Back to top

Battle and Battle Manager Objects

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 may be created with the following script:

--load the script libraries
load_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.

Back to top

Battle Script Hierarchy

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.

battle hierarchy

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.

Back to top

Unitcontrollers

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.

Back to top

Scriptunits

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(scriptunit:new(army, 1))

unit_1 = sunit_1.unit
uc_1 = sunit_1.uc

Back to top

The Generated Battle System

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.

Back to top

Battle Phases

Battles progress through phases as they play out. The most notable phases are:

PhaseComments
DeploymentTriggered 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.
DeployedTriggered at the start of the combat phase.
VictoryCountdownTriggered 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.
CompleteTriggered 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.

Back to top

Battle Timers

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.

Back to top

Monitoring Battle Conditions

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.

Back to top

Battle Co-ordinates

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.

Back to top

Camera Modes and Manipulation

TBD

Last updated 12/10/19 15:46:48