DEFCON AI Luabot Reference Document

Luabot 0.3 by Peter Cawley, February 21st, 2010.
Lua 5.1 is copyright © 2006-2008, PUC-Rio. Freely available under the terms of the Lua license.





Placement functions


Script provided functions


User interface functions


World query functions


Method calls







1 - Introduction

DEFCON is a real-time strategy game developed by Introversion Software. It is inspired by the global thermonuclear war of the movie WarGames (1983). Luabot is an extension of the official bot API for DEFCON, which provides a framework for coding DEFCON "AI" bots using Lua.

1.1 - Why Lua?

Lua is a powerful, fast, light-weight, embeddable scripting language. A manual for the language itself is available online, and also in paper form. Lua is a good choice for writing DEFCON bots for several reasons:

1.2 - Installation and setup

A copy of the Windows version of DEFCON (either the full game, or the free demo) is required, along with a method of running it (typically Windows itself, or an emulation layer like WINE). Next, the game executable and additional data files from DEFCON AI API need to be downloaded, and should replace their counterparts in the DEFCON installation - the sample bot and C++ API itself in this package are not required in order to use Luabot, but you may want them anyway - the documentation in this package may be useful as well. Finally, the luabot folder of the Luabot download archive should be extracted so that it becomes Defcon\AI\luabot.

2 - The Luabot binding

Luabot contains a Lua 5.1 interpreter with all standard libraries loaded (base, coroutine, string, table, math, io, os and debug). In addition, the DEFCON AI API is bound in as well. This binding of the API to Lua is the subject of the remainder of this document.

2.1 - Expectations of the script

The Luabot DLL expects the file AI\luabot\main.lua to be present, and this file is loaded and executed when Luabot is selected as the external AI to use in the DEFCON game lobby. This file is free to include more files using standard Lua facilities like dofile and require, although it should be noted that filenames will be interpreted as relative to the DEFCON game executable, so "AI\luabot\" should be prefixed to file names, or "AI\luabot\?.lua;" should be prefixed to package.path. The name of the file which the Luabot DLL loads can be changed from the default to something else by specifing a luabot command line parameter when launching DEFCON, for example, the command line luabot="AI\my bot\bot.lua" will cause AI\my bot\bot.lua to be loaded instead of AI\luabot\main.lua. Note that if the DLL is renamed to something other than luabot.dll, then the default filename and command line option will change accordingly. For example, if renamed to mybot.dll, then the expected file will be AI\mybot\main.lua, and the command line mybot="AI\other\bot.lua" will cause AI\other\bot.lua to be loaded instead.

The script must provide, at a minimum, four global functions: OnInit, OnTick, OnEvent and OnShutdown. These functions will be called by DEFCON during the course of the game. There is only ever one AI bot per Lua state, so your script doesn't have to be reentrant or handle simulating multiple bots at once.

2.2 - IDs

The DEFCON AI API defines a number of ID types: allianceID, eventID, fleetID, teamID, unitID, et al. In Luabot, all ID types are light userdata (and all light userdatum are IDs, hence if type(x) == "userdata" then x is an ID). Furthermore, a metatable is set for all light userdata with a __index and __tostring metamethods. The __index metamethod just does a lookup in the globals table, which allows for method-like calls to be made on ID values. The __tostring metamethod causes tostring(someID) to generate "<n>", where n is some decimal number. Note that as all IDs are all light userdata, no type checking is performed between different classes of IDs. Also, all globals functions are accessible via the method call notation on an ID.

2.3 - Enumerations

The DEFCON AI API defines a number of enumerations (some of which are confusing refered to in the documentation as IDs, like territoryID). Most of these have become string constants, with the exception of unit stats, which are numeric constants.

2.3.1 - Unit type

The following string constants are expected by all type name parameters, and are returned by all functions which return a type name:

2.3.2 - Unit state

Unit states are numeric constants between 0 and 2, the meaning of which is dependant upon the type of the unit:

Unit type State 0 State 1 State 2
AirBase Fighter launch Bomer launch n/a
BattleShip Attack n/a n/a
Bomber Attack Nuke In queue
Carrier Fighter launch Bomer launch Anti-sub
City Default n/a n/a
Explosion Default n/a n/a
Fighter Attack n/a In queue
Fleet Default n/a n/a
Gunshot Default n/a n/a
Invalid Default n/a n/a
Nuke On target Disarm n/a
RadarStation Active n/a n/a
Saucer Default n/a n/a
Silo Nuke Air Defence n/a
Sub Passive sonar Active sonar Nuke
Tornado Default n/a n/a
QueueItem Default n/a n/a

2.3.3 - Territories

Functions like GetTeamTerritories and GetTerritoryName will return strings from the following list (or in the former case, an array containing one or more of these):

2.3.4 - Event type

When OnEvent is called, it will receive one of the following as the event type:

2.4 - Differences to the DEFCON C++ API

Apart from the obvious differences like IDs being light userdata rather than integers, and some enumerations being string constants rather than integer constants, there are some more subtle differences, some of which are outlined below.

Some functions have been renamed for clarity, for example, GetDefcon has become GetDefconLevel and GetTerritoryId has become GetTerritoryName. Some other functions have been renamed for consistency, for example, GetFleets has become GetTeamFleets to be in line with functions like GetTeamUnits. In some cases, the multiple behaviours of a function have been exported as multiple functions, for example, IsValidTerritory has become, in addition to itself, IsLand and IsSea. Note that no functions have been removed, although the official function list document does reference some functions (e.g. DebugMoveCamera and GetShots) which do not actually exist in the bot API, and hence cannot be exposed to Lua.

In some cases the argument order of a function has changed for consistency, for example PlaceStructure now has the same argument order as PlaceFleet and IsValidPlacementLocation. In other cases, extra optional arguments have been added, like with GetAllUnitData. For functions which returned a longitude and latitude value in a std::vector<float>, they now return two values rather than array with two elements, for example GetVelocity.

3 - Comments on the C++ part of Luabot

The actual implementation of Luabot is comprised of several C++ source and header files. A few of these are copied verbatim from the C++ sample bot, and such will not be explained here. Worth mentioning is that Luabot.dll statically links with Lua rather than dynamically linking through a DLL. This is done to reduce the number of DLLs required and improve speed slightly.

3.1 - DLLinterface.cpp

This file provides implementations of the 4 functions which a bot DLL must provide: AI_init, AI_run, AI_addevent and AI_shutdown. It is worth noting that AI_init leaves on the stack alot of strings for AI_run and AI_addevent to use. This means that neither of these functions have to push any strings onto the Lua stack, which saves a bit of time by causing Lua to not do string hashing and string table lookup when these functions are called.

3.2 - LuaBot.cpp

This file contains all the C functions to be called from Lua (apart from those implemented using common templates), along with helper functions to check argument types, push arguments, report errors from lua_pcall, etc. Similarly to DLLinterface.cpp, it is worth noting that all C functions registered by this file share a common environment table which is used to quickly convert between C++ integer constants and Lua string constants. Storing them in upvalues would cause excessive memory bloat, and they cannot be stored on the stack, hence the use of a table. The one exception is lbot_get_all_unit_data, which has the names of the fields it sets in upvalues, again to prevent string rehashing every time it is called.

3.3 - BindingTemplates.h

This file does require some explanation. Given that alot of the DEFCON AI API functions follow a similar format in terms of arguments and return values, it would be wasteful to write a C function by hand for binding each of these to Lua. Instead, a number of template functions are exposed, each of which takes a C++ API function as a template argument, and then wraps that function to become a Lua C function. They are: thunk_GetSimpleValue, thunk_GetValueFromID, thunk_RequestUsingID, thunk_GetValueDualID, thunk_GetValueLngLat and thunk_GetMapDistance. These all take their arguments off the Lua stack, call their API function (note that while g_pDefconInterface isn't defined when these functions are, it is defined when they are instaniated, so things are fine) and use thunk<T>::push to push their result(s) onto the stack. thunk<T>::push(L, val) takes a Lua state and the value to push, and then returns the number of values pushed (which is normally 1, except in the case of thunk<Vector2<T>>::push, which pushes a longitude and a latitude). This function is defined as a static member of a template class to allow for partial template specialisations like the aforementioned Vector2 (this comes at the cost of automatic template parameter deduction, but this isn't required). The template traits classes RValTraits<T> and PValTraits<T> are used to map types to their API return types and thunk<T>::push argument types. For example, when std::vector<void*> is specified as the type, it means that the function from the API actually returns std::vector<int>, and that thunk<T>::push takes const std::vector<int>&. Finally, the classes EnvEnum<I> and Vector2<T> are used as names to identify particular behaviours for pushing results onto the stack.

4 - Function listing

A Lua script has access to all of the global functions detailed below, in addition to the standard Lua libraries:

DebugIsReplayingGame ()

Returns true iff the game is in debug mode and the game is currently being replayed (due to the timeline being clicked), false otherwise.

DebugLog (message [, unitID [, tags [, red [, green [, blue [, alpha]]]]]])

Prints message to the debug log (requires the game be in debug mode) in the colour specified by red, green, blue and alpha - all of which default to 255 if not given, which results in white. unitID is an optional unit to associate with the message, and tags is a string of tags to assist in clarifying the log message.

GetActionQueue (unitID)
unitID:GetActionQueue ()

Returns an array of unitIDs of currently queued actions for the given unitID, for example nukes in a silo or planes on a carrier.

GetAllianceID (teamID)
teamID:GetAllianceID ()

Returns the allianceID of the given teamID.

GetAllOwnUnits ()

Returns an array containing the unitIDs of all units belonging to the current player.

GetAllTeamIDs ()

Returns an array containing all of the teamIDs in the current game.

GetAllUnitData ([table])

Returns a table containing data about all visible units. If table is passed, then that table is reused rather than creating a new table. In the returned table, the keys are unitIDs and the values are tables with the following fields:

The time field is useful when in limited information mode; GetAllUnitData does not erase the contents of the given table (if it is given), meaning that if the same table is passed repeatadly, time becomes the game time at which that unit was last seen.

Note that cities are not counted as units for the purposes of this function, even though they have a ID, team, longitude, latitude. Use GetCityIDs to get the IDs of cities.

GetAllUnits ()

Returns an array containing the unitIDs of all visible units.

GetBomberNukeTarget (unitID)
unitID:GetBomberNukeTarget ()

Returns the target longitude and latitude of the nuke being carried by the given unitID (which should be a bomber).

GetCityIDs ()

Returns an array containing the cityID of every city in the game. The number of cities does not change during the game, nor do their IDs.

GetCommandLineArguments ([field])

If called without arguments, returns a table containing all of the command line arguments passed when the DEFCON executable was launched. For example, if launched with the command line host nowlan limitedinformation luabot="AI\luabot\bot\main.lua" numplayers=1 numplayers=2, would return a table similar to the following:

  [1] = {"C:\\Program Files\\Defcon\\defcon.exe", ""},
  [2] = {"host", ""},
  [3] = {"nowlan", ""},
  [4] = {"limitedinformation", ""},
  [5] = {"luabot", "AI\\luabot\\bot\\main.lua"},
  [6] = {"numplayers", "1"},
  [7] = {"numplayers", "2"},

  host = "",
  limitedinformation = "",
  ["C:\\Program Files\\Defcon\\defcon.exe"] = "",
  nowlan = "",
  numplayers = "2",
  luabot = "AI\\luabot\\bot\\main.lua",

If called with an argument, then it returns the of field in the table. Note that while some game options can have their default values set on the command line, GetOptionValue should be used to get the actual value of a game option.

GetCityPopulation (cityID)
cityID:GetCityPopulation ()

Returns the population of the given cityID, which will typically start as a several million and will never go below zero.

GetCollateralDamage (teamID)
teamID:GetCollateralDamage ()

Returns the sum of all collateral damage deaths (deaths to own population) of the given teamID.

GetCurrentState (unitID)
unitID:GetCurrentState ()

Returns the current state of the given unitID (see §2.3.2).

GetCurrentTargetID (unitID)
unitID:GetCurrentTargetID ()

Returns the unitID of the current target of the given unitID, or nil if there is no such target.

GetDefconLevel ()

Returns the current Defcon level; the game starts at 5 and gradually works down to 1.

GetDesiredGameSpeed (teamID)
teamID:GetDesiredGameSpeed ()

Returns the speed at which the given teamID has requested the game be run at (0, 1, 5, 10 or 20).

GetDistance (longitude1, latitude1, longitude2, latitude2)

Returns the ingame distance between the two given points (longitude1, latitude1) and (longitude2, latitude2).

GetEnemyKills (teamID)
teamID:GetEnemyKills ()

Returns the sum of the enemy kills of the given teamID (as used for scoring purposes).

GetFleetID (unitID)
unitID:GetFleetID ()

Returns the fleetID of the given unitID, or nil if it isn't part of a fleet (?).

GetFleetUnits (fleetID)
fleetID:GetFleetUnits ()

Returns an array containing the unitIDs of every ship in the given fleetID.

GetFleetMemberOffset (fleetSize, memberIndex)

For a hypothetical fleet with fleetSize ships in it, returns the offset of the memberIndexth member of that fleet from the centre of the fleet (as longitude and latitude values). memberIndex should be in range [1, fleetSize].

GetFriendlyDeaths (teamID)
teamID:GetFriendlyDeaths ()

Returns the sum of the friendly deaths (deaths in the population of allies) caused by the given teamID (use GetCollateralDamage to get the deaths in own population).

GetGameSpeed ()

Returns the current speed-up factor of the game over real time (0, 1, 5, 10 or 20).

GetGameTick ()

Returns the number of update cycles (ticks) passed since the game started. There are nominally 10 ticks per real second (regardless of the current game speed).

GetGameTime ()

Returns the current game time, measured in game seconds. Each tick, the game progress by 0.1 * GetGameSpeed() seconds.

GetLatitude (someID)
cityID:GetLatitude ()
eventID:GetLatitude ()
unitID:GetLatitude ()

Returns the current latitude of the given cityID, eventID or unitID. The return value is undefined (?) if it is not visible.

GetLongitude (someID)
cityID:GetLongitude ()
eventID:GetLongitude ()
unitID:GetLongitude ()

Returns the current longitude of the given code>cityID, eventID or unitID. The return value is undefined (?) if it is not visible.

GetMovementTargetLocation (unitID)
unitID:GetMovementTargetLocation ()

Returns as longitude and latitude, the current target location of the given unitID, or 0, 0 if there is no target location set.

GetNukeCount (unitID)
unitID:GetNukeCount ()

Returns the current number of nukes still available to the given unitID.

GetOptionValue (optionname)

Returns the integer value of the given game option. Suitable values for optionname are:

GetOwnFleets ()

Returns an array containing the fleetIDs of all fleets belonging to the current player.

GetOwnTeamID ()

Returns the teamID of the current player. This value doesn't change during the course of a game (?) so you can call this once and cache the result.

GetOwnUnits ()

Returns an array containing the unitIDs of all units belonging to the current player.

GetRange (unitID)
unitID:GetRange ()

Returns the remaining range of the given unitID, or -1 if it has unlimited range.

GetRemainingPopulation (teamID)
teamID:GetRemainingPopulation ()

Returns the remaining population of the given teamID.

GetRemainingUnits (typename)

Returns the number of units of the given type (see §2.3.1) which are still remaining to be placed.

GetSailDistance (longitude1, latitude1, longitude2, latitude2)

Returns the ingame distance between the two given points (longitude1, latitude1) and (longitude2, latitude2) via a sea route (this performs a pathfinding operation).

GetStateCount (unitID, state)
unitID:GetStateCount (state)

Returns the remaining (?) number of activations in the state (see §2.3.2) for the given unitID.

GetStateTimer (unitID)
unitID:GetStateTimer ()

Returns the time (in seconds?) until the current sate of the given unitID is active.

GetSuccessfulCommands ()

Returns an array of commandIDs which were executed in the previous tick (however, nothing can be done with a commandID?).

GetTeamFleets (teamID)
teamID:GetTeamFleets ()

Returns an array of fleetIDs owned by the given teamID. Only fleetIDs with at least one member visible are returned.

GetTeamID (someID)
cityID:GetTeamID ()
unitID:GetTeamID ()

Returns the teamID of the owner of the given cityID or unitID.

GetTeamName (teamID)
teamID:GetTeamName ()

Returns the name of the given teamID.

GetTeamTerritories (teamID)
teamID:GetTeamTerritories ()

Returns an array containing the names of the territories assigned to the given teamID (see §2.3.3).

GetTeamUnits (teamID)
teamID:GetTeamUnits ()

Returns an array of unitIDs owned by the given teamID.

GetTerritoryName (longitude, latitude)

Returns the name of the territory (see §2.3.3) at the given location.

GetTypeCreditCost (typename)

Returns the cost, in credits, of the give unit type (see §2.3.1). This is only useful in variable unit mode.

GetUnitCreditsRemaining ()

Returns the number of credits remaining for placing units. This is only useful in variable unit mode.

GetUnitType (someID)
cityID:GetUnitType ()
unitID:GetUnitType ()

Returns the type (see §2.3.1) of the given cityID or unitID (in the former case, the return value is always "City").

GetVelocity (unitID)
unitID:GetVelocity ()

Returns the current velocity (as longitude and latitude) of the given unitID. Note that this vector has length proportional to the game speed (?).

GetVictoryTimer ()

If the victory timer has been started (test with IsVictoryTimerActive), then returns the time remaining in the game, in seconds (?). If the timer has not yet been started, then the return value is undefined.

IsBorder (longitude, latitude)

Returns true iff the given location is on a border (see data\earth\coastlines.bmp), false otherwise.

IsCeaseFire (teamID, teamIDother)
teamID:IsCeaseFire (teamIDother)

Returns true iff teamID is in a cease-fire with teamIDother, false otherwise.

IsLand (longitude, latitude)

Returns true iff the given location is land, false otherwise. Note that some locations may be neither land nor sea.

IsRetaliating (unitID)
unitID:IsRetaliating ()

Returns true iff the given unitID is automatically retaliating to an earlier attack, false otherwise.

IsSea (longitude, latitude)

Returns true iff the given location is sea, false otherwise. Note that some locations may be neither land nor sea.

IsSharingRadar (teamID, teamIDother)
teamID:IsSharingRadar (teamIDother)

Returns true iff teamID is sharing their radar with teamIDother, false otherwise.

IsValidPlacementLocation (longitude, latitude, typename)

Returns true iff the given location is a valid placement location for a typename unit (see §2.3.1). Note that this only tests the terrain at the location, it does not check that the player still has at least one typename unit to place, nor does it check that units can be placed in the current Defcon level (?).

IsValidTerritory (teamID, longitude, latitude, isSeaArea)

Returns true iff the given location belongs to the given teamID and either isSeaArea is true and the location is sea, or isSeaArea is false and the location is land. Otherwise, if either of these tests fails, false is returned. Note that some locations may be neither land nor sea.

IsVictoryTimerActive ()

Returns true iff the victory timer countdown is active, false otherwise.

IsVisible (unitID, teamID)
unitID:IsVisible (teamID)

Returns true iff the given unitID is visible by the given teamID, false otherwise. In limited information mode, the return value is undefined when teamID ~= GetOwnTeamID().

OnEvent (eventtype, sourceID, targetID, unittype, longitude, latitude)

This function must be provided by the bot script. It is called whenever an event happens in the game. See §2.3.4 for possible event types, and §2.3.1 for unit type values.

OnInit ()

This function must be provided by the bot script. It is called when the bot is selected from the dropdown list in the game lobby. Technically, all code placed in the global scope is executed when this happens, but placing initialisation code inside a function results in neater code.

OnShutdown ()

This function must be provided by the bot script. It is called just before the Lua state is destroyed and the Lua bot unloaded.

OnTick ()

This function must be provided by the bot script. It is called once per update cycle of the game server (which is nominally every 100ms).

PlaceFleet (longitude, latitude, type1 [, type2 [, type3 [, type4 [, type5 [, type6]]]]])

Tries to place a fleet of upto six units at the given location. type1 through type6 should be (if provided) "Sub", "BattleShip" or "Carrier".

PlaceStructure (longitude, latitude, typename)

Tries to place a structure of type typename (see §2.3.1) at the given location. IsValidPlacementLocation can be used to test if the location is valid.

RequestAlliance (allianceID)
allianceID:RequestAlliance ()

Sends out requests to all members of the given allianceID to join the alliance. Use OnEvent to listen for the reply.

RequestCeaseFire (teamID)
teamID:RequestCeaseFire ()

Sends a request for a cease-fire to the given teamID. Use OnEvent to listen for the reply.

RequestGameSpeed (speed)

Notifies the game that speed should be the speed that the game happens at. Depending on the game mode, the actual speed may be the lowest speed of all requested speeds, it may be locked to a certain speed, or it may be a combination of both. speed should be 0, 1, 5, 10 or 20.

RequestShareRadar (teamID)
teamID:RequestShareRadar ()

Sends a request for radar sharing to the given teamID. Use OnEvent to listen for the reply.

SendChat (message [, channel [, raw]])

Sends the given message as chat to all players in the given channel, which should be "public", "alliance", or "spectators" (defaults to "public" if not given). If raw is true, then message is sent as-is. Otherwise, tabs are converted to single spaces (as tabs cannot appear in chat messages) and if there are any line breaks in message, then each line is sent as a seperate message.

SendVote (eventID , infavour)
eventID:SendVote (infavour)

Votes either infavour or not to the given eventID. As votes happen, the eventID can be listened for using OnEvent.

SetActionTarget (unitID [, targetID [, longitude, latitude]])
unitID:SetActionTarget ([targetID [, longitude, latitude]])

Sets the target of the action for the given unitID. Either targetID should be given, or it should be nil and a location given (?).

SetLandingTarget (unitID, targetID)
unitID:SetLandingTarget (targetID)

Instructs the given unitID to land at the given targetID (?).

SetMovementTarget (unitID, longitude, latitude)
unitID:SetMovementTarget (longitude, latitude)

Instructs the given unitID to move to the given location (?). This is usually called for ships, as setting an action target would cause them to use a weapon rather than move (?).

SetState (unitID, state)
unitID:SetState (state)

Instructs the given unitID to change to the given state (see §2.3.2).

WhiteboardDraw (longitude1, latitude1, longitude2, latitude2)

Draws a line segment on the whiteboard between (longitude1, latitude1) and (longitude2, latitude2).

WhiteboardClear ()

Clears all lines from the whiteboard.