DCS Simulation Control User Scripts
====================================

The behaviour of the DCS can be altered using the *GameGUI.lua scripts.
You define the hooks to the DCS events, and then do what you want using the provided API.
===================================================================================================

When loading, DCS searches for Saved Games\DCS\Scripts\*GameGUI.lua files,
sorts them by name and then loads into the GUI Lua-state.
Each user script is loaded into an isolated environment, so the only
thing they share is the state of the simulator.

Each script defines a set of callbacks to the DCS events and sets them with the call
    DCS.setUserCallbacks(cb_table)
For each callback type the hooks of all user scripts will be called in order of loading.

For callbacks which are supposed to returning a value, currently there are 3 of them:
    onPlayerTryConnect
    onPlayerTrySendChat
    onPlayerTryChangeSlot
returning a value means breaking the hook call chain.
Returning nothing (or nil) means continuing the hook chain, which ends with the default allow-all handlers.
 
The example user script 'testGameGUI.lua':
----------------------------------------------------------------------------------------------
local test = {}

function test.onPlayerTryConnect(ipaddr, name, ucid, playerID)
    print('onPlayerTryConnect(%s, %s, %s, %d)', ipaddr, name, ucid, playerID)
    -- if you want to gently intercept the call, allowing other user scripts to get it,
    -- you better return nothing here
    return true -- allow the player to connect
end

function test.onSimulationStart()
    print('Current mission is '..DCS.getMissionName())
end

DCS.setUserCallbacks(test)  -- here we set our callbacks
----------------------------------------------------------------------------------------------


The available API are documented below.
The full list of the callbacks is at the end of this document.

In addition, all standard lua 5.1 libraries are available as well, namely:
base api, like print, etc,
math.*
table.*
string.*
io.*
os.*
debug.*

===================================================================================================



Lua File System (lfs) API
-------------------------------
lfs.currentdir() -> string
    Returns the path of the DCS install folder

lfs.writedir() -> string
    Returns the path of the current 'Saved Games\DCS' folder.

lfs.tempdir() -> string
    Returns the pat of the DCS Temp folder (AppData\Local\Temp\DCS).

lfs.mkdir()
lfs.rmdir()
lfs.attributes()
lfs.dir()
lfs.normpath()
lfs.realpath()



DCS Control API, table 'DCS.*'
-------------------------------

DCS.setPause(bool)
    Pauses/resumes the simulation. Server-side only.

DCS.getPause() -> bool
    true if simulation is paused

DCS.stopMission()
    stops current mission

DCS.exitProcess()
    Exits the DCS process.

DCS.isMultiplayer() -> bool
    True when running in the multiplayer mode.

DCS.isServer() -> bool
    True when running as a server or in the single-player mode.

DCS.getModelTime() -> number
    returns current DCS simulation time in seconds.

DCS.getRealTime() -> number
    returns current DCS real time in seconds relative to the DCS start time.

DCS.getMissionOptions() -> table
   Returns the value of 'mission.options'

DCS.getMissionDescription() -> string
   translated mission.descriptionText string

DCS.getAvailableCoalitions() -> table {
 [coalition_id] = { name = "coalition name", }
 ...
}
   Returns a list of coalitions which have available slots.

DCS.getAvailableSlots(coalitionID) -> array of {unitId, type, role, callsign, groupName, country}
   Returns the list of available slots.
   NOTE: the returned unitID is actually a slotID, which for multi-seat units is 'unitID_seatID'

DCS.getCurrentMission() -> table with the currently loaded mission
   NOTE: to get valid mission.options use DCS.getMissionOptions()

DCS.getMissionName() -> string
    Returns the name of the current mission
	
DCS.getMissionFilename() -> string
    Returns the file name of the current mission (returns nil when acting as a multiplayer client).
	
DCS.getMissionResult(string side) -> integer [0, 100]
    Gets missin result for either 'red' or 'blue'

DCS.getUnitProperty(missionId, propertyId) -> string
   propertyId:
    DCS.UNIT_RUNTIME_ID, // unique within runtime mission. int
    DCS.UNIT_MISSION_ID, // unique within mission file. int>0
    DCS.UNIT_NAME, // unit name, as assigned by mission designer.
    DCS.UNIT_TYPE, // unit type (Ural, ZU-23, etc)
    DCS.UNIT_CATEGORY,
    DCS.UNIT_GROUP_MISSION_ID, // group ID, unique within mission file. int>0
    DCS.UNIT_GROUPNAME, // group name, as assigned by mission designer.
    DCS.UNIT_GROUPCATEGORY,
    DCS.UNIT_CALLSIGN,
    DCS.UNIT_HIDDEN,// ME hiding  
    DCS.UNIT_COALITION,// "blue", "red" or "unknown" 
    DCS.UNIT_COUNTRY_ID,
    DCS.UNIT_TASK, //"unit.group.task"
    DCS.UNIT_PLAYER_NAME, // valid for network "humanable" units
    DCS.UNIT_ROLE,//"artillery_commander", "instructor", etc
    DCS.UNIT_INVISIBLE_MAP_ICON,//ME invisible map icon

DCS.getUnitType(missionId) -> typeId
    a shortcut for DCS.getUnitProperty(missionId, DCS.UNIT_TYPE)

DCS.getUnitTypeAttribute(typeId, attr) -> string
    Returns a value from Database: Objects[typeId][attr],
    for example DCS.getUnitTypeAttribute("Ural", "DisplayName")
	
DCS.writeDebriefing(str)
    Writes a custom string to the debriefing file

DCS.setUserCallbacks(cb_table)
    Hooks the callbacks using the handlers from the provided table.
    See: "GameGUI scripts" section.
   

	
Logging API 'log.*'
------------------------
Logging works as follows:
a) each log message is accompanied with 2 attributes: a subsystem, and level.
b) after each messages gets into a logger it passes (asynchronously) through
   a series of output filters which decide where the message will be written to.
   
Writing to log is done by:

log.write(SUBSYSTEM_NAME, LOG_LEVEL, message, ...)
  if there are any arguments after 'message',
  the actual string is formed as string.format(message, ...)

  SUBSYSTEM_NAME is a string
  LOG_LEVEL is one of the values, listed below
  see log.set_output()

log.set_output(log_file_name_wo_ext, rule_subsystem_name, rule_level_mask, rule_output_mode)

   the args:
    log_file_name_wo_ext: resulting log will be written to $WRITE_DIR/Logs/<log_file_name_wo_ext>.log
	
    rule_subsytem_name: the name of the subsystem whose messages to write or empty string to match all subsystems
	
    rule_level_mask: a sum of log-level bit flags to match messages
        valid flags are:
            log.ALERT
            log.ERROR
            log.WARNING
            log.INFO
            log.DEBUG
            log.ALL - includes all of the above
            log.TRACE - a special level which is excluded from dcs.log file

    rule_output_mode: a sum of output flags:
        log.MESSAGE
        log.TIME
        log.MODULE - this is a 'subsystem', not a dlc
        log.LEVEL
        log.FULL - all of the above

So, in order to save net.trace(msg) messages to a file, you should issue a call:
   log.set_output('lua-net', 'LuaNET', log.TRACE, log.MESSAGE + log.TIME)
   
   This will write to a Logs/lua-net.log file

Or, to save everything lua-network-related:
   log.set_output('lua-net', 'LuaNET', log.TRACE + log.ALL, log.MESSAGE + log.TIME + log.LEVEL)

To close the log file, you must use
   log.set_output('lua-net', '', 0, 0)

log.* API is available in the 'Saved Games\DCS\Config\autoexec.cfg' file as well so you can control log output in you local machine.



Network specific API, available through the table 'net.'
----------------------------------------------------------------

net.log(msg) -- equivalent to log.write('LuaNET', log.INFO, msg)
net.trace(msg) -- equivalent to log.write('LuaNET', log.TRACE, msg)

What is the difference: log() always writes to dcs.log, but may lose messages if the output rate is too high.
trace() output never appears in the dcs.log file, it must be explicitly directed to a log file.
It never loses messages when there's an active output, but it may block if output rate is higher than writing to the log file.
To control logger output you can use $WRITE_DIR/Config/autoexec.cfg file, or call this from your network script
(log.* API, see above)


net.dostring_in(state, string) -> string
   Executes a lua-string in a given internal lua-state and returns a string result
   Valid state names are:
       'config': the state in which $INSTALL_DIR/Config/main.cfg is executed, as well as $WRITE_DIR/Config/autoexec.cfg
                 used for configuration settings
       'mission': holds current mission
       'export': runs $WRITE_DIR/Scripts/Export.lua and the relevant export API

net.send_chat(string message, bool all)
    Send chat message. If not all, then send to my coalition (side) only.
	
net.send_chat_to(string message, playerID to)
    Send direct chat message to a player
	Server-side only:
	net.send_chat_to(string message, playerID to[, playerID from])

net.recv_chat(message[, int from=0])
    Receive chat message locally[, pretending it was sent by another player].
	from = 0 means from the system

net.load_mission(miz_filename)
    Loads a specified mission, temporarily overriding the server mission list.
	SERVER ONLY

net.load_next_mission() -> bool
    Load the next mission from the server mission list. Returns false if list end is reached
	SERVER ONLY

net.get_player_list() -> array of playerID
    Returns the list of currently connected players

net.get_my_player_id() -> playerID
    Returns the playerID of the local player. Currently always 1 for the server.
	
net.get_server_id() -> playerID
    Returns playerID of the server. Currently, always 1.

net.get_player_info(playerID) -> table
    Returns a table of all player attributes or nil if playerID is invalid

net.get_player_info(playerID, attrName) -> value
    Returns a value of a given attribute for the playerID.

    Currently defined attributes are:
        'id': playerID
        'name': player name
        'side': 0 - spectators, 1 - red, 2 - blue
        'slot': slotID of the player or ''
        'ping': ping of the player in ms
        'ipaddr': IP address of the player, SERVER ONLY
        'ucid': Unique Client Identifier, SERVER ONLY

net.kick(id, message)
    Kick a player.

net.get_stat(playerID, statID) -> integer
    Get statistics for player. statIDs are:
        net.PS_PING  (0) - ping (in ms)
        net.PS_CRASH (1) - number of crashes
        net.PS_CAR   (2) - number of destroyed vehicles
        net.PS_PLANE (3) - ... planes/helicopters
        net.PS_SHIP  (4) - ... ships
        net.PS_SCORE (5) - total score
        net.PS_LAND  (6) - number of landings
        net.PS_EJECT (7) - of ejects


net.get_name(playerID) -> string
    The same as net.get_player_info(playerID, 'name')
    FIXME: implement in ServMan_compat.lua ?

net.get_slot(playerID) -> sideID, slotID
    The same as:
        net.get_player_info(playerID, 'side'), net.get_player_info(playerID, 'slot')
    FIXME: implement in ServMan_compat.lua ?



net.set_slot(sideID, slotID)
    Try to set the local player's slot. Empty slotID ('') puts the player into spectators.
	
net.force_player_slot(playerID, sideID, slotID) -> boolean
    Forces a player to occupy a set slot. Slot '' means no slot (moves player to spectators)
	SideID: 0 - spectators, 1 - red, 2 - blue

net.set_name(playerID, name) -- OBSOLETE, works only locally


net.lua2json(value) -> string
    Convert a Lua value to JSON string

net.json2lua(json_string) -> value
    Convert JSON string to a Lua value


LuaExport API 'Export.Lo*'
----------------------------------------------------------------
See Scripts/Export.lua for the documentation. Note that all export
API functions are available here in the Export. namespace, not the global one.
In multiplayer the availability of the API on clients depends on the server setting.

The calls to check export capabilities:
    Export.LoIsObjectExportAllowed()  -- returns the value of server.advanced.allow_object_export
    Export.LoIsSensorExportAllowed()  -- returns the value of server.advanced.allow_sensor_export
    Export.LoIsOwnshipExportAllowed() -- returns the value of  server.advanced.allow_ownship_export

These calls are only available on clients when LoIsObjectExportAllowed() is true:
    Export.LoGetObjectById
    Export.LoGetWorldObjects

These calls are only available on clients when LoIsSensorExportAllowed() is true:
    Export.LoGetTWSInfo
    Export.LoGetTargetInformation
    Export.LoGetLockedTargetInformation
    Export.LoGetF15_TWS_Contacts
    Export.LoGetSightingSystemInfo
    Export.LoGetWingTargets

These calls are only available on clients when LoIsOwnshipExportAllowed() is true:
    Export.LoGetPlayerPlaneId
    Export.LoGetIndicatedAirSpeed
    Export.LoGetAngleOfAttack
    Export.LoGetAngleOfSideSlip
    Export.LoGetAccelerationUnits
    Export.LoGetVerticalVelocity
    Export.LoGetADIPitchBankYaw
    Export.LoGetTrueAirSpeed
    Export.LoGetAltitudeAboveSeaLevel
    Export.LoGetAltitudeAboveGroundLevel
    Export.LoGetMachNumber
    Export.LoGetRadarAltimeter
    Export.LoGetMagneticYaw
    Export.LoGetGlideDeviation
    Export.LoGetSideDeviation
    Export.LoGetSlipBallPosition
    Export.LoGetBasicAtmospherePressure
    Export.LoGetControlPanel_HSI
    Export.LoGetEngineInfo
    Export.LoGetSelfData
    Export.LoGetCameraPosition
    Export.LoSetCameraPosition
    Export.LoSetCommand
    Export.LoGetMCPState
    Export.LoGetRoute
    Export.LoGetNavigationInfo
    Export.LoGetPayloadInfo
    Export.LoGetWingInfo
    Export.LoGetMechInfo
    Export.LoGetRadioBeaconsStatus
    Export.LoGetVectorVelocity
    Export.LoGetVectorWindVelocity
    Export.LoGetSnares
    Export.LoGetAngularVelocity
    Export.LoGetHeightWithObjects
    Export.LoGetFMData

These functions are always available:
    Export.LoGetPilotName
    Export.LoGetAltitude
    Export.LoGetNameByType
    Export.LoGeoCoordinatesToLoCoordinates
    Export.LoCoordinatesToGeoCoordinates
    Export.LoGetVersionInfo
    Export.LoGetWindAtPoint
    Export.LoGetModelTime
    Export.LoGetMissionStartTime

These are not available in the *GameGUI state:
--    Export.LoSetSharedTexture
--    Export.LoRemoveSharedTexture
--    Export.LoUpdateSharedTexture


-------------------------------------------------------------------------------------------
--- The Callbacks.
-------------------------------------------------------------------------------------------

function onMissionLoadBegin()
end

function onMissionLoadProgress(progress, message)
end

function onMissionLoadEnd()
end


function onSimulationStart()
end

function onSimulationStop()
end

function onSimulationFrame()
end 

function onSimulationPause()
end

function onSimulationResume()
end


function onGameEvent(eventName,arg1,arg2,arg3,arg4) 
--"friendly_fire", playerID, weaponName, victimPlayerID
--"mission_end", winner, msg
--"kill", killerPlayerID, killerUnitType, killerSide, victimPlayerID, victimUnitType, victimSide, weaponName
--"self_kill", playerID
--"change_slot", playerID, slotID, prevSide
--"connect", playerID, name
--"disconnect", playerID, name, playerSide, reason_code
--"crash", playerID, unit_missionID
--"eject", playerID, unit_missionID
--"takeoff", playerID, unit_missionID, airdromeName
--"landing", playerID, unit_missionID, airdromeName
--"pilot_death", playerID, unit_missionID
end

function onNetConnect(localPlayerID)
end

function onNetMissionChanged(newMissionName)
end

function onNetDisconnect(reason_msg, err_code)
end

-- disconnect reason codes:
  net.ERR_INVALID_ADDRESS
  net.ERR_CONNECT_FAILED
  net.ERR_WRONG_VERSION
  net.ERR_PROTOCOL_ERROR
  net.ERR_TAINTED_CLIENT
  net.ERR_INVALID_PASSWORD
  net.ERR_BANNED
  net.ERR_BAD_CALLSIGN

  net.ERR_TIMEOUT
  net.ERR_KICKED


function onPlayerConnect(id)
end

function onPlayerDisconnect(id, err_code)
  -- this is never called for local playerID
end

function onPlayerStart(id)
 -- a player entered the simulation
  -- this is never called for local playerID
end

function onPlayerStop(id)
  -- a player left the simulation (happens right before a disconnect, if player exited by desire)
  -- this is never called for local playerID
end

function onPlayerChangeSlot(id)
 -- a player successfully changed the slot
 -- this will also come as onGameEvent('change_slot', playerID, slotID),
 -- if allowed by server.advanced.event_Connect setting
end


--- These 3 functions are different from the rest:
--- 1. they are called directly from the network code, so try to make them as fast as possible
--- 2. they return a result
-- The code shows the default implementations.

function onPlayerTryConnect(addr, name, ucid, playerID) --> true | false, "disconnect reason"
    return true
end

function onPlayerTrySendChat(playerID, msg, all) -- -> filteredMessage | "" - empty string drops the message
    return msg
end

function onPlayerTryChangeSlot(playerID, side, slotID) -- -> true | false
    return true
end



-- GUI callbacks
function onChatMessage(message, from)
  -- this one may be useful for chat archiving
end

function onShowRadioMenu(a_h)
end

function onShowPool()
end

function onShowGameMenu()
end

function onShowBriefing()
end

function onShowChatAll()
end

function onShowChatTeam()
end

function onShowChatRead()
end

function onShowMessage(a_text, a_duration)
end

function onTriggerMessage(message, duration, clearView)
end

function onRadioMessage(message, duration)
end

function onRadioCommand(command_message)
end

===================================================================================================

Happy hacking!

Sincerely,
dsb at eagle dot ru
