Merge branch 'master' into LDT-Debug

# Conflicts:
#	Moose Development/Moose/Moose.lua
#	Moose Mission Setup/Moose.lua
This commit is contained in:
FlightControl_Master 2017-11-05 06:09:41 +01:00
commit c1910646e2
370 changed files with 85751 additions and 9019 deletions

View File

@ -0,0 +1,565 @@
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

View File

@ -0,0 +1,190 @@
Game;Date;Time;Source Player Name;Target Player Name;Event;Source Coalition;Source Category;Source Type;Source Unit;Target Coalition;Target Category;Target Type;Target Name;Nr;Score
TAW_CSARz_PALACE_3.6136;7/05/2017;12:19:37;PL8;;DESTROY_SCORE;Red;Plane;Su-25T;Pilot #058;Blue;Vehicle;Hawk cwar;Unit #114;1;5
TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:25;PL12;;HIT_SCORE;Red;Plane;AJS37;Pilot #110;Blue;Vehicle;Soldier M249;Unit #453;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:14:30;PL1;PL2;DESTROY_PENALTY;Blue;Plane;F-15C;Pilot #069;Blue;Plane;F-15C;Pilot #071;1;-15
TAW_CSARz_PALACE_3.6136;7/05/2017;12:40:14;PL3;PL4;HIT_SCORE;Blue;Plane;F-15C;Pilot #018;Red;Plane;AJS37;Pilot #112;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:25:22;PL5;;HIT_PENALTY;Red;Helicopter;Ka-50;MEDEVAC RED #13;Red;Vehicle;Infantry AK;Wounded Pilot #1143;1;-10
TAW_CSARz_PALACE_3.6136;7/05/2017;1:18:34;PL6;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-2 Bradley;Unit #007;1;3
TAW_CSARz_PALACE_3.6136;7/05/2017;1:21:35;PL7;PL2;HIT_SCORE;Red;Plane;M-2000C;Pilot #037;Blue;Plane;F-15C;Pilot #071;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:46:42;PL8;;HIT_SCORE;Red;Plane;Su-25T;Pilot #058;Blue;Vehicle;Strela-1 9P31;Unit #429;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:30:09;PL9;PL10;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;Su-27;Pilot #039;1;5
TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:46;PL11;;HIT_SCORE;Red;;;;;Scenery;Infantry AK;Wounded Pilot #1129;1;0
TAW_CSARz_PALACE_3.6136;7/05/2017;1:11:46;PL6;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;MLRS;Unit #011;1;2
TAW_CSARz_PALACE_3.6136;7/05/2017;12:53:10;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;MLRS;Unit #510;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:10:54;PL12;;HIT_SCORE;Red;;;;;Scenery;UKRYTIE;280468;1;0
TAW_CSARz_PALACE_3.6136;7/05/2017;1:09:28;PL13;PL9;HIT_SCORE;Red;Plane;Su-27;Pilot #086;Blue;Plane;F-15C;Pilot #017;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:52:59;PL13;PL14;HIT_SCORE;Red;Plane;Su-27;Pilot #086;Blue;Plane;M-2000C;Pilot #030;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:27;PL12;;DESTROY_SCORE;Red;Plane;AJS37;Pilot #110;Blue;Vehicle;Soldier stinger;Unit #454;1;4
TAW_CSARz_PALACE_3.6136;7/05/2017;12:53:21;PL11;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;MLRS;Unit #510;1;2
TAW_CSARz_PALACE_3.6136;7/05/2017;12:51:37;PL15;PL9;HIT_SCORE;Red;Plane;Su-27;Pilot #041;Blue;Plane;F-15C;Pilot #017;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:22:05;PL16;PL2;DESTROY_SCORE;Red;Plane;Su-27;Pilot #044;Blue;Plane;F-15C;Pilot #071;1;3
TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:28;PL6;PL3;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Plane;F-15C;Pilot #018;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:38:21;PL17;PL18;HIT_SCORE;Blue;Plane;M-2000C;Pilot #029;Red;Plane;Su-25T;Pilot #059;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:14:34;PL7;PL17;DESTROY_SCORE;Red;Plane;M-2000C;Pilot #037;Blue;Plane;M-2000C;Pilot #029;1;2,5
TAW_CSARz_PALACE_3.6136;7/05/2017;12:21:22;PL19;PL20;HIT_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;A-10C;Pilot #057;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:24:21;PL5;PL21;HIT_PENALTY;Red;Helicopter;Mi-8MT;MEDEVAC RED #10;Red;Helicopter;SA342Mistral;helicargo4;1;-10
TAW_CSARz_PALACE_3.6136;7/05/2017;12:21:36;PL6;PL19;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Plane;F-15C;Pilot #024;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:21:51;PL6;PL19;DESTROY_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Plane;F-15C;Pilot #024;1;6
TAW_CSARz_PALACE_3.6136;7/05/2017;12:21:14;PL19;PL20;HIT_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;A-10C;Pilot #057;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:13:59;PL2;;HIT_SCORE;Blue;;;;;Scenery;F-15C;Pilot #069;1;0
TAW_CSARz_PALACE_3.6136;7/05/2017;12:48:55;PL11;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;M-1 Abrams;Unit #514;1;3
TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:48;PL6;PL3;DESTROY_SCORE;Red;;;;Blue;Plane;F-15C;Pilot #018;1;6
TAW_CSARz_PALACE_3.6136;7/05/2017;1:18:19;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-2 Bradley;Unit #007;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:25:19;PL5;;HIT_PENALTY;Red;Helicopter;Ka-50;MEDEVAC RED #13;Red;Vehicle;Infantry AK;Wounded Pilot #1143;1;-10
TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:12;PL9;PL20;HIT_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;A-10C;Pilot #057;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:51:35;PL15;PL9;HIT_SCORE;Red;Plane;Su-27;Pilot #041;Blue;Plane;F-15C;Pilot #017;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:19:18;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-113;Unit #006;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:54:22;PL1;PL13;HIT_SCORE;Blue;Plane;F-15C;Pilot #069;Red;Plane;Su-27;Pilot #086;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:37:18;PL11;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;Osa 9A33 ln;Unit #218;1;5
TAW_CSARz_PALACE_3.6136;7/05/2017;1:21:14;PL16;PL1;DESTROY_SCORE;Red;Plane;Su-27;Pilot #044;Blue;Plane;F-15C;Pilot #069;1;5
TAW_CSARz_PALACE_3.6136;7/05/2017;12:55:00;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;M1128 Stryker MGS;Unit #010;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:13;PL9;PL20;HIT_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;A-10C;Pilot #057;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:19:55;PL2;PL18;HIT_SCORE;Blue;Plane;F-15C;Pilot #071;Red;Plane;Su-25T;Pilot #059;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:14:34;PL16;PL17;DESTROY_SCORE;Red;Plane;Su-27;Pilot #044;Blue;Plane;M-2000C;Pilot #029;1;2,5
TAW_CSARz_PALACE_3.6136;7/05/2017;12:46:42;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;Soldier M4;Unit #470;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:11:46;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;MLRS;Unit #011;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:21:20;PL19;PL20;HIT_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;A-10C;Pilot #057;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:10:01;PL6;PL3;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Plane;F-15C;Pilot #018;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:38:22;PL18;PL17;DESTROY_SCORE;Red;Plane;Su-25T;Pilot #059;Blue;Plane;M-2000C;Pilot #029;1;5
TAW_CSARz_PALACE_3.6136;7/05/2017;12:46:25;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-2 Bradley;Unit #007;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:19:53;PL24;PL18;HIT_SCORE;Blue;Plane;F-15C;Pilot #033;Red;Plane;Su-25T;Pilot #059;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:51:55;PL15;PL9;DESTROY_SCORE;Red;Plane;Su-27;Pilot #041;Blue;Plane;F-15C;Pilot #017;1;5
TAW_CSARz_PALACE_3.6136;7/05/2017;12:48:11;PL2;PL10;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #071;Red;Plane;Su-27;Pilot #039;1;5
TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:25;PL12;;DESTROY_SCORE;Red;Plane;AJS37;Pilot #110;Blue;Vehicle;Soldier M249;X33;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:13:13;PL6;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-1 Abrams;Unit #009;1;3
TAW_CSARz_PALACE_3.6136;7/05/2017;1:17:07;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-2 Bradley;Unit #007;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:18:29;PL19;PL18;HIT_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;Su-25T;Pilot #059;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:22:05;PL16;PL2;HIT_SCORE;Red;Plane;Su-27;Pilot #044;Blue;Plane;F-15C;Pilot #071;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:26;PL19;PL20;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;A-10C;Pilot #057;1;2
TAW_CSARz_PALACE_3.6136;7/05/2017;12:55:13;PL6;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M1128 Stryker MGS;Unit #010;1;1,5
TAW_CSARz_PALACE_3.6136;7/05/2017;12:55:54;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;AAV7;Unit #005;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:21:26;PL6;PL19;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Plane;F-15C;Pilot #024;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:09:27;PL3;PL13;HIT_SCORE;Blue;Plane;F-15C;Pilot #018;Red;Plane;Su-27;Pilot #086;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:57:05;PL15;PL1;DESTROY_SCORE;Red;Plane;Su-27;Pilot #041;Blue;Plane;F-15C;Pilot #069;1;5
TAW_CSARz_PALACE_3.6136;7/05/2017;12:53:23;PL11;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;MLRS;Unit #511;1;2
TAW_CSARz_PALACE_3.6136;7/05/2017;12:47:44;PL16;PL26;HIT_SCORE;Red;Plane;Su-27;Pilot #044;Blue;Plane;F-15C;Pilot #068;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:40:04;PL19;PL12;HIT_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;AJS37;Pilot #110;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:41:24;PL20;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;Soldier M4;X36;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:07:56;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-109;Unit #512;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:41:24;PL20;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;Soldier M249;Unit #310;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:25;PL12;;HIT_SCORE;Red;Plane;AJS37;Pilot #110;Blue;Vehicle;Soldier M4;Unit #450;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:46:45;PL11;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;Soldier M4;Unit #469;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:54:26;PL1;PL13;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #069;Red;Plane;Su-27;Pilot #086;1;5
TAW_CSARz_PALACE_3.6136;7/05/2017;12:13:29;PL4;;DESTROY_SCORE;Red;Plane;AJS37;Pilot #112;Blue;Structure;Warehouse;AMMO SENAKI;1;50
TAW_CSARz_PALACE_3.6136;7/05/2017;12:56:32;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;M-109;Unit #014;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:55:49;PL16;PL2;HIT_SCORE;Red;Plane;Su-27;Pilot #044;Blue;Plane;F-15C;Pilot #071;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:17:00;PL6;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;Vulcan;Unit #004;1;4
TAW_CSARz_PALACE_3.6136;7/05/2017;12:47:55;PL10;PL19;HIT_SCORE;Red;Plane;Su-27;Pilot #039;Blue;Plane;F-15C;Pilot #024;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:29:20;PL9;PL10;HIT_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;Su-27;Pilot #039;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:46:45;PL11;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;Soldier M4;Unit #470;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:54:58;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;AAV7;Unit #005;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:48:15;PL11;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;Vulcan;Unit #503;1;4
TAW_CSARz_PALACE_3.6136;7/05/2017;1:22:05;PL7;PL2;DESTROY_SCORE;Red;Plane;M-2000C;Pilot #037;Blue;Plane;F-15C;Pilot #071;1;3
TAW_CSARz_PALACE_3.6136;7/05/2017;1:19:55;PL24;PL18;HIT_SCORE;Blue;Plane;F-15C;Pilot #033;Red;Plane;Su-25T;Pilot #059;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:45:32;PL7;;DESTROY_SCORE;Red;Plane;M-2000C;Pilot #037;Blue;Structure;Windsock;logistic10;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:41:22;PL20;;HIT_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;Soldier M4;Unit #308;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:47:30;;;CAPTURE KOBULETI;Red;;;;NA;NA;NA;NA;1;50
TAW_CSARz_PALACE_3.6136;7/05/2017;12:55:13;PL11;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;M1128 Stryker MGS;Unit #010;1;1,5
TAW_CSARz_PALACE_3.6136;7/05/2017;1:20:05;PL17;PL18;DESTROY_SCORE;Blue;;;;Red;Plane;Su-25T;Pilot #059;1;1,67
TAW_CSARz_PALACE_3.6136;7/05/2017;1:22:42;PL23;PL22;HIT_SCORE;Red;Plane;M-2000C;Pilot #038;Blue;Plane;F-15C;Pilot #070;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:46:42;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;Soldier M4;Unit #469;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:37:08;PL18;;DESTROY_SCORE;Red;Plane;Su-25T;Pilot #059;Blue;Vehicle;Hawk sr;Unit #118;1;5
TAW_CSARz_PALACE_3.6136;7/05/2017;12:40:12;PL19;PL12;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;AJS37;Pilot #110;1;4
TAW_CSARz_PALACE_3.6136;7/05/2017;12:50:48;PL20;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;Vulcan;Unit #095;1;4
TAW_CSARz_PALACE_3.6136;7/05/2017;1:45:31;PL7;;HIT_SCORE;Red;Plane;M-2000C;Pilot #037;Blue;Vehicle;outpost;outpost1;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:25:15;PL5;;HIT_PENALTY;Red;Helicopter;Ka-50;MEDEVAC RED #13;Red;Vehicle;Infantry AK;Wounded Pilot #1143;1;-10
TAW_CSARz_PALACE_3.6136;7/05/2017;12:13:29;PL4;;HIT_SCORE;Red;;;;;Scenery;Warehouse;AMMO SENAKI;1;0
TAW_CSARz_PALACE_3.6136;7/05/2017;1:20:05;PL2;PL18;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #071;Red;Plane;Su-25T;Pilot #059;1;1,67
TAW_CSARz_PALACE_3.6136;7/05/2017;1:45:32;PL7;;HIT_SCORE;Red;;;;;Scenery;Windsock;logistic10;1;0
TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:25;PL12;;HIT_SCORE;Red;Plane;AJS37;Pilot #110;Blue;Vehicle;Soldier M249;X33;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:52:37;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;Vulcan;Unit #508;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:47:58;PL2;PL10;HIT_SCORE;Blue;Plane;F-15C;Pilot #071;Red;Plane;Su-27;Pilot #039;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:56:35;PL15;PL1;HIT_SCORE;Red;Plane;Su-27;Pilot #041;Blue;Plane;F-15C;Pilot #069;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:41:25;PL20;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;Soldier M4;Unit #308;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:32:57;PL9;PL25;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;Su-27;Pilot #043;1;5
TAW_CSARz_PALACE_3.6136;7/05/2017;1:18:23;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-2 Bradley;Unit #007;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:21:14;PL16;PL1;HIT_SCORE;Red;Plane;Su-27;Pilot #044;Blue;Plane;F-15C;Pilot #069;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:47;PL9;PL6;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;A-10C;Pilot #056;1;2
TAW_CSARz_PALACE_3.6136;7/05/2017;12:23:16;PL5;PL5;HIT_PENALTY;Red;Helicopter;Mi-8MT;MEDEVAC RED #10;Red;Helicopter;Mi-8MT;MEDEVAC RED #10;1;-10
TAW_CSARz_PALACE_3.6136;7/05/2017;12:21:03;PL19;PL6;HIT_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;A-10C;Pilot #056;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:46:24;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-2 Bradley;Unit #007;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:21:17;PL11;PL27;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Plane;F-15C;Pilot #025;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:18:22;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-2 Bradley;Unit #007;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:52:37;PL11;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;Vulcan;Unit #508;1;4
TAW_CSARz_PALACE_3.6136;7/05/2017;1:19:30;PL6;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-113;Unit #006;1;2
TAW_CSARz_PALACE_3.6136;7/05/2017;1:41:22;PL20;;HIT_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;Soldier M249;Unit #310;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:46;PL9;PL11;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;A-10C;Pilot #054;1;4
TAW_CSARz_PALACE_3.6136;7/05/2017;12:36:57;PL18;;HIT_SCORE;Red;Plane;Su-25T;Pilot #059;Blue;Vehicle;Hawk sr;Unit #118;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:41:22;PL20;;HIT_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;Soldier M249;Unit #311;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:26;PL11;;HIT_SCORE;Red;;;;;Scenery;KAZARMA2;270090725;1;0
TAW_CSARz_PALACE_3.6136;7/05/2017;1:37:07;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;Osa 9A33 ln;Unit #218;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:18:46;;;CAPTURE KUTAISI;Red;;;;NA;NA;NA;NA;1;50
TAW_CSARz_PALACE_3.6136;7/05/2017;12:33:20;PL9;PL15;HIT_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;Su-27;Pilot #041;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:10:10;PL6;PL3;DESTROY_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Plane;F-15C;Pilot #018;1;6
TAW_CSARz_PALACE_3.6136;7/05/2017;1:13:23;PL6;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-2 Bradley;Unit #008;1;3
TAW_CSARz_PALACE_3.6136;7/05/2017;12:56:08;PL11;PL26;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Plane;F-15C;Pilot #025;1;6
TAW_CSARz_PALACE_3.6136;7/05/2017;1:14:23;PL7;PL17;HIT_SCORE;Red;Plane;M-2000C;Pilot #037;Blue;Plane;M-2000C;Pilot #029;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:47;PL19;PL6;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;A-10C;Pilot #056;1;2
TAW_CSARz_PALACE_3.6136;7/05/2017;12:50:37;PL20;;HIT_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;Vulcan;Unit #015;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:26;PL9;PL20;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;A-10C;Pilot #057;1;2
TAW_CSARz_PALACE_3.6136;7/05/2017;12:57:05;PL2;PL1;DESTROY_PENALTY;Blue;Plane;F-15C;Pilot #071;Blue;Plane;F-15C;Pilot #069;1;-15
TAW_CSARz_PALACE_3.6136;7/05/2017;12:56:44;PL11;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;M-109;Unit #014;1;2
TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:36;PL9;PL6;HIT_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;A-10C;Pilot #056;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:53:12;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;MLRS;Unit #511;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:47:56;PL20;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;M-109;Unit #013;1;2
TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:37;PL9;PL6;HIT_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;A-10C;Pilot #056;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:48:04;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;M-113;Unit #505;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:13:23;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-2 Bradley;Unit #008;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:46:42;PL11;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;Vulcan;Unit #515;1;4
TAW_CSARz_PALACE_3.6136;7/05/2017;12:38:22;PL18;;HIT_SCORE;Red;;;;;Scenery;M-2000C;Pilot #029;1;0
TAW_CSARz_PALACE_3.6136;7/05/2017;1:09:28;PL13;PL9;DESTROY_SCORE;Red;Plane;Su-27;Pilot #086;Blue;Plane;F-15C;Pilot #017;1;5
TAW_CSARz_PALACE_3.6136;7/05/2017;12:13:29;PL4;;DESTROY_SCORE;Red;Plane;AJS37;Pilot #112;Blue;Structure;Warehouse;AMMO SENAKI;1;50
TAW_CSARz_PALACE_3.6136;7/05/2017;12:48:49;PL28;;HIT_SCORE;;;;;;Scenery;Ural-4320T;Unit #150;1;0
TAW_CSARz_PALACE_3.6136;7/05/2017;12:40:22;PL3;PL4;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #018;Red;Plane;AJS37;Pilot #112;1;4
TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:25;PL12;;DESTROY_SCORE;Red;Plane;AJS37;Pilot #110;Blue;Vehicle;Soldier M4;Unit #450;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:14:24;PL16;PL17;HIT_SCORE;Red;Plane;Su-27;Pilot #044;Blue;Plane;M-2000C;Pilot #029;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:48:04;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;Vulcan;Unit #503;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:00:00;;;CAPTURE GUDAUTA;Blue;;;;NA;NA;NA;NA;1;50
TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:25;PL12;;HIT_SCORE;Red;Plane;AJS37;Pilot #110;Blue;Vehicle;Soldier stinger;Unit #454;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:13:13;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-1 Abrams;Unit #009;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:46:42;PL8;;DESTROY_SCORE;Red;Plane;Su-25T;Pilot #058;Blue;Vehicle;Strela-1 9P31;Unit #429;1;4
TAW_CSARz_PALACE_3.6136;7/05/2017;12:56:06;PL11;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;AAV7;Unit #005;1;2
TAW_CSARz_PALACE_3.6136;7/05/2017;12:48:55;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;M-1 Abrams;Unit #514;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:53:06;PL20;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;MLRS;Unit #012;1;2
TAW_CSARz_PALACE_3.6136;7/05/2017;12:13:59;PL1;;HIT_SCORE;Blue;;;;;Scenery;F-15C;Pilot #071;1;0
TAW_CSARz_PALACE_3.6136;7/05/2017;1:25:17;PL5;;HIT_PENALTY;Red;Helicopter;Ka-50;MEDEVAC RED #13;Red;Vehicle;Infantry AK;Wounded Pilot #1143;1;-10
TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:25;PL12;;HIT_SCORE;Red;Plane;AJS37;Pilot #110;Blue;Vehicle;Soldier M4;Unit #451;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:25:20;PL5;;HIT_PENALTY;Red;Helicopter;Ka-50;MEDEVAC RED #13;Red;Vehicle;Infantry AK;Wounded Pilot #1143;1;-10
TAW_CSARz_PALACE_3.6136;7/05/2017;12:53:14;PL13;PL14;DESTROY_SCORE;Red;Plane;Su-27;Pilot #086;Blue;Plane;M-2000C;Pilot #030;1;5
TAW_CSARz_PALACE_3.6136;7/05/2017;12:50:48;PL20;;HIT_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;Vulcan;Unit #095;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:45:31;PL7;;DESTROY_SCORE;Red;Plane;M-2000C;Pilot #037;Blue;Vehicle;outpost;outpost1;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:25;PL12;;HIT_SCORE;Red;Plane;AJS37;Pilot #110;Blue;Vehicle;outpost;outpost2;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:19:37;PL8;;HIT_SCORE;Red;Plane;Su-25T;Pilot #058;Blue;Vehicle;Hawk cwar;Unit #114;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:18:36;PL19;PL18;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;Su-25T;Pilot #059;1;4
TAW_CSARz_PALACE_3.6136;7/05/2017;1:07:56;PL6;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-109;Unit #512;1;2
TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:25;;;SEALS 4;Blue;;;;NA;NA;NA;NA;1;50
TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:19;PL12;;HIT_SCORE;Red;;;;;Scenery;BLK_LIGHT_POLE;269633408;1;0
TAW_CSARz_PALACE_3.6136;7/05/2017;1:41:24;PL20;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;Soldier M249;Unit #311;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:20:05;PL24;PL18;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #033;Red;Plane;Su-25T;Pilot #059;1;1,67
TAW_CSARz_PALACE_3.6136;7/05/2017;12:46:42;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;Vulcan;Unit #515;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:09:37;PL3;PL13;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #018;Red;Plane;Su-27;Pilot #086;1;5
TAW_CSARz_PALACE_3.6136;7/05/2017;12:48:04;PL11;;HIT_SCORE;Red;;;;;Scenery;BLK_LIGHT_POLE;269963637;1;0
TAW_CSARz_PALACE_3.6136;7/05/2017;1:41:24;;;SEALS 1;Blue;;;;NA;NA;NA;NA;1;50
TAW_CSARz_PALACE_3.6136;7/05/2017;12:11:14;PL4;;HIT_SCORE;Red;;;;;Scenery;UKRYTIE;280476;1;0
TAW_CSARz_PALACE_3.6136;7/05/2017;12:53:06;PL20;;HIT_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;MLRS;Unit #012;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:32:54;PL9;PL25;HIT_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;Su-27;Pilot #043;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:55:01;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;M1128 Stryker MGS;Unit #010;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:25;PL12;;DESTROY_SCORE;Red;Plane;AJS37;Pilot #110;Blue;Vehicle;Soldier M4;Unit #451;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:47:56;PL20;;HIT_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;M-109;Unit #013;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:18:25;PL19;PL18;HIT_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;Su-25T;Pilot #059;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:41:22;PL20;;HIT_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;Soldier M4;X36;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:18:24;PL19;PL18;HIT_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;Su-25T;Pilot #059;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:21:17;PL19;PL20;HIT_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;A-10C;Pilot #057;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:23:03;PL23;PL22;DESTROY_SCORE;Red;Plane;M-2000C;Pilot #038;Blue;Plane;F-15C;Pilot #070;1;6
TAW_CSARz_PALACE_3.6136;7/05/2017;1:17:00;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;Vulcan;Unit #004;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:45:40;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M1128 Stryker MGS;Unit #010;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;1:09:18;PL13;;HIT_SCORE;Red;;;;;Scenery;KORPUS_B1;269962308;1;0
TAW_CSARz_PALACE_3.6136;7/05/2017;12:33:24;PL9;PL15;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;Su-27;Pilot #041;1;5
TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:27;PL12;;DESTROY_SCORE;Red;Plane;AJS37;Pilot #110;Blue;Vehicle;Soldier M249;Unit #453;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:21:53;PL9;PL11;HIT_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;A-10C;Pilot #054;1;1
TAW_CSARz_PALACE_3.6136;7/05/2017;12:50:37;PL20;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;Vulcan;Unit #015;1;4
1 Game Date Time Source Player Name Target Player Name Event Source Coalition Source Category Source Type Source Unit Target Coalition Target Category Target Type Target Name Nr Score
2 TAW_CSARz_PALACE_3.6136 7/05/2017 12:19:37 PL8 DESTROY_SCORE Red Plane Su-25T Pilot #058 Blue Vehicle Hawk cwar Unit #114 1 5
3 TAW_CSARz_PALACE_3.6136 7/05/2017 1:39:25 PL12 HIT_SCORE Red Plane AJS37 Pilot #110 Blue Vehicle Soldier M249 Unit #453 1 1
4 TAW_CSARz_PALACE_3.6136 7/05/2017 12:14:30 PL1 PL2 DESTROY_PENALTY Blue Plane F-15C Pilot #069 Blue Plane F-15C Pilot #071 1 -15
5 TAW_CSARz_PALACE_3.6136 7/05/2017 12:40:14 PL3 PL4 HIT_SCORE Blue Plane F-15C Pilot #018 Red Plane AJS37 Pilot #112 1 1
6 TAW_CSARz_PALACE_3.6136 7/05/2017 1:25:22 PL5 HIT_PENALTY Red Helicopter Ka-50 MEDEVAC RED #13 Red Vehicle Infantry AK Wounded Pilot #1143 1 -10
7 TAW_CSARz_PALACE_3.6136 7/05/2017 1:18:34 PL6 DESTROY_SCORE Red Plane A-10C Pilot #056 Blue Vehicle M-2 Bradley Unit #007 1 3
8 TAW_CSARz_PALACE_3.6136 7/05/2017 1:21:35 PL7 PL2 HIT_SCORE Red Plane M-2000C Pilot #037 Blue Plane F-15C Pilot #071 1 1
9 TAW_CSARz_PALACE_3.6136 7/05/2017 12:46:42 PL8 HIT_SCORE Red Plane Su-25T Pilot #058 Blue Vehicle Strela-1 9P31 Unit #429 1 1
10 TAW_CSARz_PALACE_3.6136 7/05/2017 12:30:09 PL9 PL10 DESTROY_SCORE Blue Plane F-15C Pilot #017 Red Plane Su-27 Pilot #039 1 5
11 TAW_CSARz_PALACE_3.6136 7/05/2017 12:22:46 PL11 HIT_SCORE Red Scenery Infantry AK Wounded Pilot #1129 1 0
12 TAW_CSARz_PALACE_3.6136 7/05/2017 1:11:46 PL6 DESTROY_SCORE Red Plane A-10C Pilot #056 Blue Vehicle MLRS Unit #011 1 2
13 TAW_CSARz_PALACE_3.6136 7/05/2017 12:53:10 PL11 HIT_SCORE Red Plane A-10C Pilot #054 Blue Vehicle MLRS Unit #510 1 1
14 TAW_CSARz_PALACE_3.6136 7/05/2017 12:10:54 PL12 HIT_SCORE Red Scenery UKRYTIE 280468 1 0
15 TAW_CSARz_PALACE_3.6136 7/05/2017 1:09:28 PL13 PL9 HIT_SCORE Red Plane Su-27 Pilot #086 Blue Plane F-15C Pilot #017 1 1
16 TAW_CSARz_PALACE_3.6136 7/05/2017 12:52:59 PL13 PL14 HIT_SCORE Red Plane Su-27 Pilot #086 Blue Plane M-2000C Pilot #030 1 1
17 TAW_CSARz_PALACE_3.6136 7/05/2017 1:39:27 PL12 DESTROY_SCORE Red Plane AJS37 Pilot #110 Blue Vehicle Soldier stinger Unit #454 1 4
18 TAW_CSARz_PALACE_3.6136 7/05/2017 12:53:21 PL11 DESTROY_SCORE Red Plane A-10C Pilot #054 Blue Vehicle MLRS Unit #510 1 2
19 TAW_CSARz_PALACE_3.6136 7/05/2017 12:51:37 PL15 PL9 HIT_SCORE Red Plane Su-27 Pilot #041 Blue Plane F-15C Pilot #017 1 1
20 TAW_CSARz_PALACE_3.6136 7/05/2017 1:22:05 PL16 PL2 DESTROY_SCORE Red Plane Su-27 Pilot #044 Blue Plane F-15C Pilot #071 1 3
21 TAW_CSARz_PALACE_3.6136 7/05/2017 12:22:28 PL6 PL3 HIT_SCORE Red Plane A-10C Pilot #056 Blue Plane F-15C Pilot #018 1 1
22 TAW_CSARz_PALACE_3.6136 7/05/2017 12:38:21 PL17 PL18 HIT_SCORE Blue Plane M-2000C Pilot #029 Red Plane Su-25T Pilot #059 1 1
23 TAW_CSARz_PALACE_3.6136 7/05/2017 1:14:34 PL7 PL17 DESTROY_SCORE Red Plane M-2000C Pilot #037 Blue Plane M-2000C Pilot #029 1 2,5
24 TAW_CSARz_PALACE_3.6136 7/05/2017 12:21:22 PL19 PL20 HIT_SCORE Blue Plane F-15C Pilot #024 Red Plane A-10C Pilot #057 1 1
25 TAW_CSARz_PALACE_3.6136 7/05/2017 12:24:21 PL5 PL21 HIT_PENALTY Red Helicopter Mi-8MT MEDEVAC RED #10 Red Helicopter SA342Mistral helicargo4 1 -10
26 TAW_CSARz_PALACE_3.6136 7/05/2017 12:21:36 PL6 PL19 HIT_SCORE Red Plane A-10C Pilot #056 Blue Plane F-15C Pilot #024 1 1
27 TAW_CSARz_PALACE_3.6136 7/05/2017 12:21:51 PL6 PL19 DESTROY_SCORE Red Plane A-10C Pilot #056 Blue Plane F-15C Pilot #024 1 6
28 TAW_CSARz_PALACE_3.6136 7/05/2017 12:21:14 PL19 PL20 HIT_SCORE Blue Plane F-15C Pilot #024 Red Plane A-10C Pilot #057 1 1
29 TAW_CSARz_PALACE_3.6136 7/05/2017 12:13:59 PL2 HIT_SCORE Blue Scenery F-15C Pilot #069 1 0
30 TAW_CSARz_PALACE_3.6136 7/05/2017 12:48:55 PL11 DESTROY_SCORE Red Plane A-10C Pilot #054 Blue Vehicle M-1 Abrams Unit #514 1 3
31 TAW_CSARz_PALACE_3.6136 7/05/2017 12:22:48 PL6 PL3 DESTROY_SCORE Red Blue Plane F-15C Pilot #018 1 6
32 TAW_CSARz_PALACE_3.6136 7/05/2017 1:18:19 PL6 HIT_SCORE Red Plane A-10C Pilot #056 Blue Vehicle M-2 Bradley Unit #007 1 1
33 TAW_CSARz_PALACE_3.6136 7/05/2017 1:25:19 PL5 HIT_PENALTY Red Helicopter Ka-50 MEDEVAC RED #13 Red Vehicle Infantry AK Wounded Pilot #1143 1 -10
34 TAW_CSARz_PALACE_3.6136 7/05/2017 12:22:12 PL9 PL20 HIT_SCORE Blue Plane F-15C Pilot #017 Red Plane A-10C Pilot #057 1 1
35 TAW_CSARz_PALACE_3.6136 7/05/2017 12:51:35 PL15 PL9 HIT_SCORE Red Plane Su-27 Pilot #041 Blue Plane F-15C Pilot #017 1 1
36 TAW_CSARz_PALACE_3.6136 7/05/2017 1:19:18 PL6 HIT_SCORE Red Plane A-10C Pilot #056 Blue Vehicle M-113 Unit #006 1 1
37 TAW_CSARz_PALACE_3.6136 7/05/2017 12:54:22 PL1 PL13 HIT_SCORE Blue Plane F-15C Pilot #069 Red Plane Su-27 Pilot #086 1 1
38 TAW_CSARz_PALACE_3.6136 7/05/2017 1:37:18 PL11 DESTROY_SCORE Red Plane A-10C Pilot #054 Blue Vehicle Osa 9A33 ln Unit #218 1 5
39 TAW_CSARz_PALACE_3.6136 7/05/2017 1:21:14 PL16 PL1 DESTROY_SCORE Red Plane Su-27 Pilot #044 Blue Plane F-15C Pilot #069 1 5
40 TAW_CSARz_PALACE_3.6136 7/05/2017 12:55:00 PL11 HIT_SCORE Red Plane A-10C Pilot #054 Blue Vehicle M1128 Stryker MGS Unit #010 1 1
41 TAW_CSARz_PALACE_3.6136 7/05/2017 12:22:13 PL9 PL20 HIT_SCORE Blue Plane F-15C Pilot #017 Red Plane A-10C Pilot #057 1 1
42 TAW_CSARz_PALACE_3.6136 7/05/2017 1:19:55 PL2 PL18 HIT_SCORE Blue Plane F-15C Pilot #071 Red Plane Su-25T Pilot #059 1 1
43 TAW_CSARz_PALACE_3.6136 7/05/2017 1:14:34 PL16 PL17 DESTROY_SCORE Red Plane Su-27 Pilot #044 Blue Plane M-2000C Pilot #029 1 2,5
44 TAW_CSARz_PALACE_3.6136 7/05/2017 12:46:42 PL11 HIT_SCORE Red Plane A-10C Pilot #054 Blue Vehicle Soldier M4 Unit #470 1 1
45 TAW_CSARz_PALACE_3.6136 7/05/2017 1:11:46 PL6 HIT_SCORE Red Plane A-10C Pilot #056 Blue Vehicle MLRS Unit #011 1 1
46 TAW_CSARz_PALACE_3.6136 7/05/2017 12:21:20 PL19 PL20 HIT_SCORE Blue Plane F-15C Pilot #024 Red Plane A-10C Pilot #057 1 1
47 TAW_CSARz_PALACE_3.6136 7/05/2017 1:10:01 PL6 PL3 HIT_SCORE Red Plane A-10C Pilot #056 Blue Plane F-15C Pilot #018 1 1
48 TAW_CSARz_PALACE_3.6136 7/05/2017 12:38:22 PL18 PL17 DESTROY_SCORE Red Plane Su-25T Pilot #059 Blue Plane M-2000C Pilot #029 1 5
49 TAW_CSARz_PALACE_3.6136 7/05/2017 12:46:25 PL6 HIT_SCORE Red Plane A-10C Pilot #056 Blue Vehicle M-2 Bradley Unit #007 1 1
50 TAW_CSARz_PALACE_3.6136 7/05/2017 1:19:53 PL24 PL18 HIT_SCORE Blue Plane F-15C Pilot #033 Red Plane Su-25T Pilot #059 1 1
51 TAW_CSARz_PALACE_3.6136 7/05/2017 12:51:55 PL15 PL9 DESTROY_SCORE Red Plane Su-27 Pilot #041 Blue Plane F-15C Pilot #017 1 5
52 TAW_CSARz_PALACE_3.6136 7/05/2017 12:48:11 PL2 PL10 DESTROY_SCORE Blue Plane F-15C Pilot #071 Red Plane Su-27 Pilot #039 1 5
53 TAW_CSARz_PALACE_3.6136 7/05/2017 1:39:25 PL12 DESTROY_SCORE Red Plane AJS37 Pilot #110 Blue Vehicle Soldier M249 X33 1 1
54 TAW_CSARz_PALACE_3.6136 7/05/2017 1:13:13 PL6 DESTROY_SCORE Red Plane A-10C Pilot #056 Blue Vehicle M-1 Abrams Unit #009 1 3
55 TAW_CSARz_PALACE_3.6136 7/05/2017 1:17:07 PL6 HIT_SCORE Red Plane A-10C Pilot #056 Blue Vehicle M-2 Bradley Unit #007 1 1
56 TAW_CSARz_PALACE_3.6136 7/05/2017 12:18:29 PL19 PL18 HIT_SCORE Blue Plane F-15C Pilot #024 Red Plane Su-25T Pilot #059 1 1
57 TAW_CSARz_PALACE_3.6136 7/05/2017 1:22:05 PL16 PL2 HIT_SCORE Red Plane Su-27 Pilot #044 Blue Plane F-15C Pilot #071 1 1
58 TAW_CSARz_PALACE_3.6136 7/05/2017 12:22:26 PL19 PL20 DESTROY_SCORE Blue Plane F-15C Pilot #024 Red Plane A-10C Pilot #057 1 2
59 TAW_CSARz_PALACE_3.6136 7/05/2017 12:55:13 PL6 DESTROY_SCORE Red Plane A-10C Pilot #056 Blue Vehicle M1128 Stryker MGS Unit #010 1 1,5
60 TAW_CSARz_PALACE_3.6136 7/05/2017 12:55:54 PL11 HIT_SCORE Red Plane A-10C Pilot #054 Blue Vehicle AAV7 Unit #005 1 1
61 TAW_CSARz_PALACE_3.6136 7/05/2017 12:21:26 PL6 PL19 HIT_SCORE Red Plane A-10C Pilot #056 Blue Plane F-15C Pilot #024 1 1
62 TAW_CSARz_PALACE_3.6136 7/05/2017 1:09:27 PL3 PL13 HIT_SCORE Blue Plane F-15C Pilot #018 Red Plane Su-27 Pilot #086 1 1
63 TAW_CSARz_PALACE_3.6136 7/05/2017 12:57:05 PL15 PL1 DESTROY_SCORE Red Plane Su-27 Pilot #041 Blue Plane F-15C Pilot #069 1 5
64 TAW_CSARz_PALACE_3.6136 7/05/2017 12:53:23 PL11 DESTROY_SCORE Red Plane A-10C Pilot #054 Blue Vehicle MLRS Unit #511 1 2
65 TAW_CSARz_PALACE_3.6136 7/05/2017 12:47:44 PL16 PL26 HIT_SCORE Red Plane Su-27 Pilot #044 Blue Plane F-15C Pilot #068 1 1
66 TAW_CSARz_PALACE_3.6136 7/05/2017 12:40:04 PL19 PL12 HIT_SCORE Blue Plane F-15C Pilot #024 Red Plane AJS37 Pilot #110 1 1
67 TAW_CSARz_PALACE_3.6136 7/05/2017 1:41:24 PL20 DESTROY_SCORE Red Plane A-10C Pilot #057 Blue Vehicle Soldier M4 X36 1 1
68 TAW_CSARz_PALACE_3.6136 7/05/2017 1:07:56 PL6 HIT_SCORE Red Plane A-10C Pilot #056 Blue Vehicle M-109 Unit #512 1 1
69 TAW_CSARz_PALACE_3.6136 7/05/2017 1:41:24 PL20 DESTROY_SCORE Red Plane A-10C Pilot #057 Blue Vehicle Soldier M249 Unit #310 1 1
70 TAW_CSARz_PALACE_3.6136 7/05/2017 1:39:25 PL12 HIT_SCORE Red Plane AJS37 Pilot #110 Blue Vehicle Soldier M4 Unit #450 1 1
71 TAW_CSARz_PALACE_3.6136 7/05/2017 12:46:45 PL11 DESTROY_SCORE Red Plane A-10C Pilot #054 Blue Vehicle Soldier M4 Unit #469 1 1
72 TAW_CSARz_PALACE_3.6136 7/05/2017 12:54:26 PL1 PL13 DESTROY_SCORE Blue Plane F-15C Pilot #069 Red Plane Su-27 Pilot #086 1 5
73 TAW_CSARz_PALACE_3.6136 7/05/2017 12:13:29 PL4 DESTROY_SCORE Red Plane AJS37 Pilot #112 Blue Structure Warehouse AMMO SENAKI 1 50
74 TAW_CSARz_PALACE_3.6136 7/05/2017 12:56:32 PL11 HIT_SCORE Red Plane A-10C Pilot #054 Blue Vehicle M-109 Unit #014 1 1
75 TAW_CSARz_PALACE_3.6136 7/05/2017 12:55:49 PL16 PL2 HIT_SCORE Red Plane Su-27 Pilot #044 Blue Plane F-15C Pilot #071 1 1
76 TAW_CSARz_PALACE_3.6136 7/05/2017 1:17:00 PL6 DESTROY_SCORE Red Plane A-10C Pilot #056 Blue Vehicle Vulcan Unit #004 1 4
77 TAW_CSARz_PALACE_3.6136 7/05/2017 12:47:55 PL10 PL19 HIT_SCORE Red Plane Su-27 Pilot #039 Blue Plane F-15C Pilot #024 1 1
78 TAW_CSARz_PALACE_3.6136 7/05/2017 12:29:20 PL9 PL10 HIT_SCORE Blue Plane F-15C Pilot #017 Red Plane Su-27 Pilot #039 1 1
79 TAW_CSARz_PALACE_3.6136 7/05/2017 12:46:45 PL11 DESTROY_SCORE Red Plane A-10C Pilot #054 Blue Vehicle Soldier M4 Unit #470 1 1
80 TAW_CSARz_PALACE_3.6136 7/05/2017 12:54:58 PL11 HIT_SCORE Red Plane A-10C Pilot #054 Blue Vehicle AAV7 Unit #005 1 1
81 TAW_CSARz_PALACE_3.6136 7/05/2017 12:48:15 PL11 DESTROY_SCORE Red Plane A-10C Pilot #054 Blue Vehicle Vulcan Unit #503 1 4
82 TAW_CSARz_PALACE_3.6136 7/05/2017 1:22:05 PL7 PL2 DESTROY_SCORE Red Plane M-2000C Pilot #037 Blue Plane F-15C Pilot #071 1 3
83 TAW_CSARz_PALACE_3.6136 7/05/2017 1:19:55 PL24 PL18 HIT_SCORE Blue Plane F-15C Pilot #033 Red Plane Su-25T Pilot #059 1 1
84 TAW_CSARz_PALACE_3.6136 7/05/2017 1:45:32 PL7 DESTROY_SCORE Red Plane M-2000C Pilot #037 Blue Structure Windsock logistic10 1 1
85 TAW_CSARz_PALACE_3.6136 7/05/2017 1:41:22 PL20 HIT_SCORE Red Plane A-10C Pilot #057 Blue Vehicle Soldier M4 Unit #308 1 1
86 TAW_CSARz_PALACE_3.6136 7/05/2017 12:47:30 CAPTURE KOBULETI Red NA NA NA NA 1 50
87 TAW_CSARz_PALACE_3.6136 7/05/2017 12:55:13 PL11 DESTROY_SCORE Red Plane A-10C Pilot #054 Blue Vehicle M1128 Stryker MGS Unit #010 1 1,5
88 TAW_CSARz_PALACE_3.6136 7/05/2017 1:20:05 PL17 PL18 DESTROY_SCORE Blue Red Plane Su-25T Pilot #059 1 1,67
89 TAW_CSARz_PALACE_3.6136 7/05/2017 1:22:42 PL23 PL22 HIT_SCORE Red Plane M-2000C Pilot #038 Blue Plane F-15C Pilot #070 1 1
90 TAW_CSARz_PALACE_3.6136 7/05/2017 12:46:42 PL11 HIT_SCORE Red Plane A-10C Pilot #054 Blue Vehicle Soldier M4 Unit #469 1 1
91 TAW_CSARz_PALACE_3.6136 7/05/2017 12:37:08 PL18 DESTROY_SCORE Red Plane Su-25T Pilot #059 Blue Vehicle Hawk sr Unit #118 1 5
92 TAW_CSARz_PALACE_3.6136 7/05/2017 12:40:12 PL19 PL12 DESTROY_SCORE Blue Plane F-15C Pilot #024 Red Plane AJS37 Pilot #110 1 4
93 TAW_CSARz_PALACE_3.6136 7/05/2017 12:50:48 PL20 DESTROY_SCORE Red Plane A-10C Pilot #057 Blue Vehicle Vulcan Unit #095 1 4
94 TAW_CSARz_PALACE_3.6136 7/05/2017 1:45:31 PL7 HIT_SCORE Red Plane M-2000C Pilot #037 Blue Vehicle outpost outpost1 1 1
95 TAW_CSARz_PALACE_3.6136 7/05/2017 1:25:15 PL5 HIT_PENALTY Red Helicopter Ka-50 MEDEVAC RED #13 Red Vehicle Infantry AK Wounded Pilot #1143 1 -10
96 TAW_CSARz_PALACE_3.6136 7/05/2017 12:13:29 PL4 HIT_SCORE Red Scenery Warehouse AMMO SENAKI 1 0
97 TAW_CSARz_PALACE_3.6136 7/05/2017 1:20:05 PL2 PL18 DESTROY_SCORE Blue Plane F-15C Pilot #071 Red Plane Su-25T Pilot #059 1 1,67
98 TAW_CSARz_PALACE_3.6136 7/05/2017 1:45:32 PL7 HIT_SCORE Red Scenery Windsock logistic10 1 0
99 TAW_CSARz_PALACE_3.6136 7/05/2017 1:39:25 PL12 HIT_SCORE Red Plane AJS37 Pilot #110 Blue Vehicle Soldier M249 X33 1 1
100 TAW_CSARz_PALACE_3.6136 7/05/2017 12:52:37 PL11 HIT_SCORE Red Plane A-10C Pilot #054 Blue Vehicle Vulcan Unit #508 1 1
101 TAW_CSARz_PALACE_3.6136 7/05/2017 12:47:58 PL2 PL10 HIT_SCORE Blue Plane F-15C Pilot #071 Red Plane Su-27 Pilot #039 1 1
102 TAW_CSARz_PALACE_3.6136 7/05/2017 12:56:35 PL15 PL1 HIT_SCORE Red Plane Su-27 Pilot #041 Blue Plane F-15C Pilot #069 1 1
103 TAW_CSARz_PALACE_3.6136 7/05/2017 1:41:25 PL20 DESTROY_SCORE Red Plane A-10C Pilot #057 Blue Vehicle Soldier M4 Unit #308 1 1
104 TAW_CSARz_PALACE_3.6136 7/05/2017 12:32:57 PL9 PL25 DESTROY_SCORE Blue Plane F-15C Pilot #017 Red Plane Su-27 Pilot #043 1 5
105 TAW_CSARz_PALACE_3.6136 7/05/2017 1:18:23 PL6 HIT_SCORE Red Plane A-10C Pilot #056 Blue Vehicle M-2 Bradley Unit #007 1 1
106 TAW_CSARz_PALACE_3.6136 7/05/2017 1:21:14 PL16 PL1 HIT_SCORE Red Plane Su-27 Pilot #044 Blue Plane F-15C Pilot #069 1 1
107 TAW_CSARz_PALACE_3.6136 7/05/2017 12:22:47 PL9 PL6 DESTROY_SCORE Blue Plane F-15C Pilot #017 Red Plane A-10C Pilot #056 1 2
108 TAW_CSARz_PALACE_3.6136 7/05/2017 12:23:16 PL5 PL5 HIT_PENALTY Red Helicopter Mi-8MT MEDEVAC RED #10 Red Helicopter Mi-8MT MEDEVAC RED #10 1 -10
109 TAW_CSARz_PALACE_3.6136 7/05/2017 12:21:03 PL19 PL6 HIT_SCORE Blue Plane F-15C Pilot #024 Red Plane A-10C Pilot #056 1 1
110 TAW_CSARz_PALACE_3.6136 7/05/2017 12:46:24 PL6 HIT_SCORE Red Plane A-10C Pilot #056 Blue Vehicle M-2 Bradley Unit #007 1 1
111 TAW_CSARz_PALACE_3.6136 7/05/2017 12:21:17 PL11 PL27 HIT_SCORE Red Plane A-10C Pilot #054 Blue Plane F-15C Pilot #025 1 1
112 TAW_CSARz_PALACE_3.6136 7/05/2017 1:18:22 PL6 HIT_SCORE Red Plane A-10C Pilot #056 Blue Vehicle M-2 Bradley Unit #007 1 1
113 TAW_CSARz_PALACE_3.6136 7/05/2017 12:52:37 PL11 DESTROY_SCORE Red Plane A-10C Pilot #054 Blue Vehicle Vulcan Unit #508 1 4
114 TAW_CSARz_PALACE_3.6136 7/05/2017 1:19:30 PL6 DESTROY_SCORE Red Plane A-10C Pilot #056 Blue Vehicle M-113 Unit #006 1 2
115 TAW_CSARz_PALACE_3.6136 7/05/2017 1:41:22 PL20 HIT_SCORE Red Plane A-10C Pilot #057 Blue Vehicle Soldier M249 Unit #310 1 1
116 TAW_CSARz_PALACE_3.6136 7/05/2017 12:22:46 PL9 PL11 DESTROY_SCORE Blue Plane F-15C Pilot #017 Red Plane A-10C Pilot #054 1 4
117 TAW_CSARz_PALACE_3.6136 7/05/2017 12:36:57 PL18 HIT_SCORE Red Plane Su-25T Pilot #059 Blue Vehicle Hawk sr Unit #118 1 1
118 TAW_CSARz_PALACE_3.6136 7/05/2017 1:41:22 PL20 HIT_SCORE Red Plane A-10C Pilot #057 Blue Vehicle Soldier M249 Unit #311 1 1
119 TAW_CSARz_PALACE_3.6136 7/05/2017 12:22:26 PL11 HIT_SCORE Red Scenery KAZARMA2 270090725 1 0
120 TAW_CSARz_PALACE_3.6136 7/05/2017 1:37:07 PL11 HIT_SCORE Red Plane A-10C Pilot #054 Blue Vehicle Osa 9A33 ln Unit #218 1 1
121 TAW_CSARz_PALACE_3.6136 7/05/2017 12:18:46 CAPTURE KUTAISI Red NA NA NA NA 1 50
122 TAW_CSARz_PALACE_3.6136 7/05/2017 12:33:20 PL9 PL15 HIT_SCORE Blue Plane F-15C Pilot #017 Red Plane Su-27 Pilot #041 1 1
123 TAW_CSARz_PALACE_3.6136 7/05/2017 1:10:10 PL6 PL3 DESTROY_SCORE Red Plane A-10C Pilot #056 Blue Plane F-15C Pilot #018 1 6
124 TAW_CSARz_PALACE_3.6136 7/05/2017 1:13:23 PL6 DESTROY_SCORE Red Plane A-10C Pilot #056 Blue Vehicle M-2 Bradley Unit #008 1 3
125 TAW_CSARz_PALACE_3.6136 7/05/2017 12:56:08 PL11 PL26 DESTROY_SCORE Red Plane A-10C Pilot #054 Blue Plane F-15C Pilot #025 1 6
126 TAW_CSARz_PALACE_3.6136 7/05/2017 1:14:23 PL7 PL17 HIT_SCORE Red Plane M-2000C Pilot #037 Blue Plane M-2000C Pilot #029 1 1
127 TAW_CSARz_PALACE_3.6136 7/05/2017 12:22:47 PL19 PL6 DESTROY_SCORE Blue Plane F-15C Pilot #024 Red Plane A-10C Pilot #056 1 2
128 TAW_CSARz_PALACE_3.6136 7/05/2017 12:50:37 PL20 HIT_SCORE Red Plane A-10C Pilot #057 Blue Vehicle Vulcan Unit #015 1 1
129 TAW_CSARz_PALACE_3.6136 7/05/2017 12:22:26 PL9 PL20 DESTROY_SCORE Blue Plane F-15C Pilot #017 Red Plane A-10C Pilot #057 1 2
130 TAW_CSARz_PALACE_3.6136 7/05/2017 12:57:05 PL2 PL1 DESTROY_PENALTY Blue Plane F-15C Pilot #071 Blue Plane F-15C Pilot #069 1 -15
131 TAW_CSARz_PALACE_3.6136 7/05/2017 12:56:44 PL11 DESTROY_SCORE Red Plane A-10C Pilot #054 Blue Vehicle M-109 Unit #014 1 2
132 TAW_CSARz_PALACE_3.6136 7/05/2017 12:22:36 PL9 PL6 HIT_SCORE Blue Plane F-15C Pilot #017 Red Plane A-10C Pilot #056 1 1
133 TAW_CSARz_PALACE_3.6136 7/05/2017 12:53:12 PL11 HIT_SCORE Red Plane A-10C Pilot #054 Blue Vehicle MLRS Unit #511 1 1
134 TAW_CSARz_PALACE_3.6136 7/05/2017 12:47:56 PL20 DESTROY_SCORE Red Plane A-10C Pilot #057 Blue Vehicle M-109 Unit #013 1 2
135 TAW_CSARz_PALACE_3.6136 7/05/2017 12:22:37 PL9 PL6 HIT_SCORE Blue Plane F-15C Pilot #017 Red Plane A-10C Pilot #056 1 1
136 TAW_CSARz_PALACE_3.6136 7/05/2017 12:48:04 PL11 HIT_SCORE Red Plane A-10C Pilot #054 Blue Vehicle M-113 Unit #505 1 1
137 TAW_CSARz_PALACE_3.6136 7/05/2017 1:13:23 PL6 HIT_SCORE Red Plane A-10C Pilot #056 Blue Vehicle M-2 Bradley Unit #008 1 1
138 TAW_CSARz_PALACE_3.6136 7/05/2017 12:46:42 PL11 DESTROY_SCORE Red Plane A-10C Pilot #054 Blue Vehicle Vulcan Unit #515 1 4
139 TAW_CSARz_PALACE_3.6136 7/05/2017 12:38:22 PL18 HIT_SCORE Red Scenery M-2000C Pilot #029 1 0
140 TAW_CSARz_PALACE_3.6136 7/05/2017 1:09:28 PL13 PL9 DESTROY_SCORE Red Plane Su-27 Pilot #086 Blue Plane F-15C Pilot #017 1 5
141 TAW_CSARz_PALACE_3.6136 7/05/2017 12:13:29 PL4 DESTROY_SCORE Red Plane AJS37 Pilot #112 Blue Structure Warehouse AMMO SENAKI 1 50
142 TAW_CSARz_PALACE_3.6136 7/05/2017 12:48:49 PL28 HIT_SCORE Scenery Ural-4320T Unit #150 1 0
143 TAW_CSARz_PALACE_3.6136 7/05/2017 12:40:22 PL3 PL4 DESTROY_SCORE Blue Plane F-15C Pilot #018 Red Plane AJS37 Pilot #112 1 4
144 TAW_CSARz_PALACE_3.6136 7/05/2017 1:39:25 PL12 DESTROY_SCORE Red Plane AJS37 Pilot #110 Blue Vehicle Soldier M4 Unit #450 1 1
145 TAW_CSARz_PALACE_3.6136 7/05/2017 1:14:24 PL16 PL17 HIT_SCORE Red Plane Su-27 Pilot #044 Blue Plane M-2000C Pilot #029 1 1
146 TAW_CSARz_PALACE_3.6136 7/05/2017 12:48:04 PL11 HIT_SCORE Red Plane A-10C Pilot #054 Blue Vehicle Vulcan Unit #503 1 1
147 TAW_CSARz_PALACE_3.6136 7/05/2017 12:00:00 CAPTURE GUDAUTA Blue NA NA NA NA 1 50
148 TAW_CSARz_PALACE_3.6136 7/05/2017 1:39:25 PL12 HIT_SCORE Red Plane AJS37 Pilot #110 Blue Vehicle Soldier stinger Unit #454 1 1
149 TAW_CSARz_PALACE_3.6136 7/05/2017 1:13:13 PL6 HIT_SCORE Red Plane A-10C Pilot #056 Blue Vehicle M-1 Abrams Unit #009 1 1
150 TAW_CSARz_PALACE_3.6136 7/05/2017 12:46:42 PL8 DESTROY_SCORE Red Plane Su-25T Pilot #058 Blue Vehicle Strela-1 9P31 Unit #429 1 4
151 TAW_CSARz_PALACE_3.6136 7/05/2017 12:56:06 PL11 DESTROY_SCORE Red Plane A-10C Pilot #054 Blue Vehicle AAV7 Unit #005 1 2
152 TAW_CSARz_PALACE_3.6136 7/05/2017 12:48:55 PL11 HIT_SCORE Red Plane A-10C Pilot #054 Blue Vehicle M-1 Abrams Unit #514 1 1
153 TAW_CSARz_PALACE_3.6136 7/05/2017 12:53:06 PL20 DESTROY_SCORE Red Plane A-10C Pilot #057 Blue Vehicle MLRS Unit #012 1 2
154 TAW_CSARz_PALACE_3.6136 7/05/2017 12:13:59 PL1 HIT_SCORE Blue Scenery F-15C Pilot #071 1 0
155 TAW_CSARz_PALACE_3.6136 7/05/2017 1:25:17 PL5 HIT_PENALTY Red Helicopter Ka-50 MEDEVAC RED #13 Red Vehicle Infantry AK Wounded Pilot #1143 1 -10
156 TAW_CSARz_PALACE_3.6136 7/05/2017 1:39:25 PL12 HIT_SCORE Red Plane AJS37 Pilot #110 Blue Vehicle Soldier M4 Unit #451 1 1
157 TAW_CSARz_PALACE_3.6136 7/05/2017 1:25:20 PL5 HIT_PENALTY Red Helicopter Ka-50 MEDEVAC RED #13 Red Vehicle Infantry AK Wounded Pilot #1143 1 -10
158 TAW_CSARz_PALACE_3.6136 7/05/2017 12:53:14 PL13 PL14 DESTROY_SCORE Red Plane Su-27 Pilot #086 Blue Plane M-2000C Pilot #030 1 5
159 TAW_CSARz_PALACE_3.6136 7/05/2017 12:50:48 PL20 HIT_SCORE Red Plane A-10C Pilot #057 Blue Vehicle Vulcan Unit #095 1 1
160 TAW_CSARz_PALACE_3.6136 7/05/2017 1:45:31 PL7 DESTROY_SCORE Red Plane M-2000C Pilot #037 Blue Vehicle outpost outpost1 1 1
161 TAW_CSARz_PALACE_3.6136 7/05/2017 1:39:25 PL12 HIT_SCORE Red Plane AJS37 Pilot #110 Blue Vehicle outpost outpost2 1 1
162 TAW_CSARz_PALACE_3.6136 7/05/2017 12:19:37 PL8 HIT_SCORE Red Plane Su-25T Pilot #058 Blue Vehicle Hawk cwar Unit #114 1 1
163 TAW_CSARz_PALACE_3.6136 7/05/2017 12:18:36 PL19 PL18 DESTROY_SCORE Blue Plane F-15C Pilot #024 Red Plane Su-25T Pilot #059 1 4
164 TAW_CSARz_PALACE_3.6136 7/05/2017 1:07:56 PL6 DESTROY_SCORE Red Plane A-10C Pilot #056 Blue Vehicle M-109 Unit #512 1 2
165 TAW_CSARz_PALACE_3.6136 7/05/2017 1:39:25 SEALS 4 Blue NA NA NA NA 1 50
166 TAW_CSARz_PALACE_3.6136 7/05/2017 1:39:19 PL12 HIT_SCORE Red Scenery BLK_LIGHT_POLE 269633408 1 0
167 TAW_CSARz_PALACE_3.6136 7/05/2017 1:41:24 PL20 DESTROY_SCORE Red Plane A-10C Pilot #057 Blue Vehicle Soldier M249 Unit #311 1 1
168 TAW_CSARz_PALACE_3.6136 7/05/2017 1:20:05 PL24 PL18 DESTROY_SCORE Blue Plane F-15C Pilot #033 Red Plane Su-25T Pilot #059 1 1,67
169 TAW_CSARz_PALACE_3.6136 7/05/2017 12:46:42 PL11 HIT_SCORE Red Plane A-10C Pilot #054 Blue Vehicle Vulcan Unit #515 1 1
170 TAW_CSARz_PALACE_3.6136 7/05/2017 1:09:37 PL3 PL13 DESTROY_SCORE Blue Plane F-15C Pilot #018 Red Plane Su-27 Pilot #086 1 5
171 TAW_CSARz_PALACE_3.6136 7/05/2017 12:48:04 PL11 HIT_SCORE Red Scenery BLK_LIGHT_POLE 269963637 1 0
172 TAW_CSARz_PALACE_3.6136 7/05/2017 1:41:24 SEALS 1 Blue NA NA NA NA 1 50
173 TAW_CSARz_PALACE_3.6136 7/05/2017 12:11:14 PL4 HIT_SCORE Red Scenery UKRYTIE 280476 1 0
174 TAW_CSARz_PALACE_3.6136 7/05/2017 12:53:06 PL20 HIT_SCORE Red Plane A-10C Pilot #057 Blue Vehicle MLRS Unit #012 1 1
175 TAW_CSARz_PALACE_3.6136 7/05/2017 12:32:54 PL9 PL25 HIT_SCORE Blue Plane F-15C Pilot #017 Red Plane Su-27 Pilot #043 1 1
176 TAW_CSARz_PALACE_3.6136 7/05/2017 12:55:01 PL11 HIT_SCORE Red Plane A-10C Pilot #054 Blue Vehicle M1128 Stryker MGS Unit #010 1 1
177 TAW_CSARz_PALACE_3.6136 7/05/2017 1:39:25 PL12 DESTROY_SCORE Red Plane AJS37 Pilot #110 Blue Vehicle Soldier M4 Unit #451 1 1
178 TAW_CSARz_PALACE_3.6136 7/05/2017 12:47:56 PL20 HIT_SCORE Red Plane A-10C Pilot #057 Blue Vehicle M-109 Unit #013 1 1
179 TAW_CSARz_PALACE_3.6136 7/05/2017 12:18:25 PL19 PL18 HIT_SCORE Blue Plane F-15C Pilot #024 Red Plane Su-25T Pilot #059 1 1
180 TAW_CSARz_PALACE_3.6136 7/05/2017 1:41:22 PL20 HIT_SCORE Red Plane A-10C Pilot #057 Blue Vehicle Soldier M4 X36 1 1
181 TAW_CSARz_PALACE_3.6136 7/05/2017 12:18:24 PL19 PL18 HIT_SCORE Blue Plane F-15C Pilot #024 Red Plane Su-25T Pilot #059 1 1
182 TAW_CSARz_PALACE_3.6136 7/05/2017 12:21:17 PL19 PL20 HIT_SCORE Blue Plane F-15C Pilot #024 Red Plane A-10C Pilot #057 1 1
183 TAW_CSARz_PALACE_3.6136 7/05/2017 1:23:03 PL23 PL22 DESTROY_SCORE Red Plane M-2000C Pilot #038 Blue Plane F-15C Pilot #070 1 6
184 TAW_CSARz_PALACE_3.6136 7/05/2017 1:17:00 PL6 HIT_SCORE Red Plane A-10C Pilot #056 Blue Vehicle Vulcan Unit #004 1 1
185 TAW_CSARz_PALACE_3.6136 7/05/2017 12:45:40 PL6 HIT_SCORE Red Plane A-10C Pilot #056 Blue Vehicle M1128 Stryker MGS Unit #010 1 1
186 TAW_CSARz_PALACE_3.6136 7/05/2017 1:09:18 PL13 HIT_SCORE Red Scenery KORPUS_B1 269962308 1 0
187 TAW_CSARz_PALACE_3.6136 7/05/2017 12:33:24 PL9 PL15 DESTROY_SCORE Blue Plane F-15C Pilot #017 Red Plane Su-27 Pilot #041 1 5
188 TAW_CSARz_PALACE_3.6136 7/05/2017 1:39:27 PL12 DESTROY_SCORE Red Plane AJS37 Pilot #110 Blue Vehicle Soldier M249 Unit #453 1 1
189 TAW_CSARz_PALACE_3.6136 7/05/2017 12:21:53 PL9 PL11 HIT_SCORE Blue Plane F-15C Pilot #017 Red Plane A-10C Pilot #054 1 1
190 TAW_CSARz_PALACE_3.6136 7/05/2017 12:50:37 PL20 DESTROY_SCORE Red Plane A-10C Pilot #057 Blue Vehicle Vulcan Unit #015 1 4

View File

@ -3,7 +3,7 @@
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.ui.externaltools.launchGroup"/>
</listAttribute>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Utils/luarocks/lua5.1.exe}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Utils/Generate_Moose.bat}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="&quot;Moose_Create.lua&quot; &#13;&#10;&quot;D&quot;&#13;&#10;&quot;${current_date}&quot; &#13;&#10;&quot;${workspace_loc:/Moose_Framework//Moose Development/Moose}&quot; &#13;&#10;&quot;${workspace_loc:/Moose_Framework/Moose Mission Setup}&quot;"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Moose_Framework/Moose Mission Setup}"/>
</launchConfiguration>

View File

@ -3,7 +3,7 @@
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.ui.externaltools.launchGroup"/>
</listAttribute>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Utils/luarocks/lua5.1.exe}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Utils/Generate_Moose.bat}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="&quot;Moose_Create.lua&quot; &#13;&#10;&quot;S&quot;&#13;&#10;&quot;${current_date}&quot; &#13;&#10;&quot;${workspace_loc:/Moose_Framework//Moose Development/Moose}&quot; &#13;&#10;&quot;${workspace_loc:/Moose_Framework/Moose Mission Setup}&quot;"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Moose_Framework/Moose Mission Setup}"/>
</launchConfiguration>

View File

@ -70,8 +70,9 @@ function AI_A2A:New( AIGroup )
self:SetControllable( AIGroup )
self:ManageFuel( .2, 60 )
self:ManageDamage( 0.4 )
self:SetFuelThreshold( .2, 60 )
self:SetDamageThreshold( 0.4 )
self:SetDisengageRadius( 70000 )
self:SetStartState( "Stopped" )
@ -219,8 +220,37 @@ function AI_A2A:New( AIGroup )
-- @param #string Event The Event string.
-- @param #string To The To State string.
self:AddTransition( "Patrolling", "Refuel", "Refuelling" )
--- Refuel Handler OnBefore for AI_A2A
-- @function [parent=#AI_A2A] OnBeforeRefuel
-- @param #AI_A2A self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Refuel Handler OnAfter for AI_A2A
-- @function [parent=#AI_A2A] OnAfterRefuel
-- @param #AI_A2A self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From
-- @param #string Event
-- @param #string To
--- Refuel Trigger for AI_A2A
-- @function [parent=#AI_A2A] Refuel
-- @param #AI_A2A self
--- Refuel Asynchronous Trigger for AI_A2A
-- @function [parent=#AI_A2A] __Refuel
-- @param #AI_A2A self
-- @param #number Delay
self:AddTransition( "*", "Takeoff", "Airborne" )
self:AddTransition( "*", "Return", "Returning" )
self:AddTransition( "*", "Hold", "Holding" )
self:AddTransition( "*", "Home", "Home" )
self:AddTransition( "*", "LostControl", "LostControl" )
self:AddTransition( "*", "Fuel", "Fuel" )
@ -234,6 +264,13 @@ function AI_A2A:New( AIGroup )
return self
end
--- @param Wrapper.Group#GROUP self
-- @param Core.Event#EVENTDATA EventData
function GROUP:OnEventTakeoff( EventData, Fsm )
Fsm:Takeoff()
self:UnHandleEvent( EVENTS.Takeoff )
end
function AI_A2A:SetDispatcher( Dispatcher )
self.Dispatcher = Dispatcher
end
@ -294,8 +331,27 @@ function AI_A2A:SetHomeAirbase( HomeAirbase )
self.HomeAirbase = HomeAirbase
end
--- Sets to refuel at the given tanker.
-- @param #AI_A2A self
-- @param Wrapper.Group#GROUP TankerName The group name of the tanker as defined within the Mission Editor or spawned.
-- @return #AI_A2A self
function AI_A2A:SetTanker( TankerName )
self:F2( { TankerName } )
self.TankerName = TankerName
end
--- Sets the disengage range, that when engaging a target beyond the specified range, the engagement will be cancelled and the plane will RTB.
-- @param #AI_A2A self
-- @param #number DisengageRadius The disengage range.
-- @return #AI_A2A self
function AI_A2A:SetDisengageRadius( DisengageRadius )
self:F2( { DisengageRadius } )
self.DisengageRadius = DisengageRadius
end
--- Set the status checking off.
-- @param #AI_A2A self
-- @return #AI_A2A self
@ -311,13 +367,13 @@ end
-- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_A2A.
-- Once the time is finished, the old AI will return to the base.
-- @param #AI_A2A self
-- @param #number PatrolFuelTresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
-- @param #number PatrolFuelThresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
-- @param #number PatrolOutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base.
-- @return #AI_A2A self
function AI_A2A:ManageFuel( PatrolFuelTresholdPercentage, PatrolOutOfFuelOrbitTime )
function AI_A2A:SetFuelThreshold( PatrolFuelThresholdPercentage, PatrolOutOfFuelOrbitTime )
self.PatrolManageFuel = true
self.PatrolFuelTresholdPercentage = PatrolFuelTresholdPercentage
self.PatrolFuelThresholdPercentage = PatrolFuelThresholdPercentage
self.PatrolOutOfFuelOrbitTime = PatrolOutOfFuelOrbitTime
self.Controllable:OptionRTBBingoFuel( false )
@ -332,12 +388,12 @@ end
-- Note that for groups, the average damage of the complete group will be calculated.
-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage treshold will be 0.25.
-- @param #AI_A2A self
-- @param #number PatrolDamageTreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged.
-- @param #number PatrolDamageThreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged.
-- @return #AI_A2A self
function AI_A2A:ManageDamage( PatrolDamageTreshold )
function AI_A2A:SetDamageThreshold( PatrolDamageThreshold )
self.PatrolManageDamage = true
self.PatrolDamageTreshold = PatrolDamageTreshold
self.PatrolDamageThreshold = PatrolDamageThreshold
return self
end
@ -372,45 +428,79 @@ end
--- @param #AI_A2A self
function AI_A2A:onafterStatus()
self:F()
self:F( " Checking Status" )
if self.Controllable and self.Controllable:IsAlive() then
local RTB = false
local Fuel = self.Controllable:GetUnit(1):GetFuel()
self:F({Fuel=Fuel})
if Fuel < self.PatrolFuelTresholdPercentage then
self:E( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... RTB!" )
local OldAIControllable = self.Controllable
local AIControllableTemplate = self.Controllable:GetTemplate()
local DistanceFromHomeBase = self.HomeAirbase:GetCoordinate():Get2DDistance( self.Controllable:GetCoordinate() )
if not self:Is( "Holding" ) and not self:Is( "Returning" ) then
local DistanceFromHomeBase = self.HomeAirbase:GetCoordinate():Get2DDistance( self.Controllable:GetCoordinate() )
self:F({DistanceFromHomeBase=DistanceFromHomeBase})
local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
local TimedOrbitTask = OldAIControllable:TaskControlled( OrbitTask, OldAIControllable:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil ) )
OldAIControllable:SetTask( TimedOrbitTask, 10 )
if DistanceFromHomeBase > self.DisengageRadius then
self:E( self.Controllable:GetName() .. " is too far from home base, RTB!" )
self:Hold( 300 )
RTB = false
end
end
if self:Is( "Fuel" ) or self:Is( "Damaged" ) or self:Is( "LostControl" ) then
if DistanceFromHomeBase < 5000 then
self:E( self.Controllable:GetName() .. " is too far from home base, RTB!" )
self:Home( "Destroy" )
end
end
self:Fuel()
RTB = true
else
if not self:Is( "Fuel" ) and not self:Is( "Home" ) then
local Fuel = self.Controllable:GetFuel()
self:F({Fuel=Fuel})
if Fuel < self.PatrolFuelThresholdPercentage then
if self.TankerName then
self:E( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... Refuelling at Tanker!" )
self:Refuel()
else
self:E( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... RTB!" )
local OldAIControllable = self.Controllable
local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
local TimedOrbitTask = OldAIControllable:TaskControlled( OrbitTask, OldAIControllable:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil ) )
OldAIControllable:SetTask( TimedOrbitTask, 10 )
self:Fuel()
RTB = true
end
else
end
end
-- TODO: Check GROUP damage function.
local Damage = self.Controllable:GetLife()
local InitialLife = self.Controllable:GetLife0()
self:F( { Damage = Damage, InitialLife = InitialLife, DamageTreshold = self.PatrolDamageTreshold } )
if ( Damage / InitialLife ) < self.PatrolDamageTreshold then
self:F( { Damage = Damage, InitialLife = InitialLife, DamageThreshold = self.PatrolDamageThreshold } )
if ( Damage / InitialLife ) < self.PatrolDamageThreshold then
self:E( self.Controllable:GetName() .. " is damaged: " .. Damage .. " ... RTB!" )
self:Damaged()
RTB = true
self:SetStatusOff()
end
-- Check if planes went RTB and are out of control.
if self.Controllable:HasTask() == false then
if not self:Is( "Started" ) and
not self:Is( "Stopped" ) then
not self:Is( "Stopped" ) and
not self:Is( "Home" ) then
if self.IdleCount >= 2 then
self:E( self.Controllable:GetName() .. " control lost! " )
self:LostControl()
if Damage ~= InitialLife then
self:Damaged()
else
self:E( self.Controllable:GetName() .. " control lost! " )
self:LostControl()
end
else
self.IdleCount = self.IdleCount + 1
end
@ -418,7 +508,7 @@ function AI_A2A:onafterStatus()
else
self.IdleCount = 0
end
if RTB == true then
self:__RTB( 0.5 )
end
@ -429,13 +519,28 @@ end
--- @param Wrapper.Group#GROUP AIGroup
function AI_A2A.RTBRoute( AIGroup )
function AI_A2A.RTBRoute( AIGroup, Fsm )
AIGroup:E( { "RTBRoute:", AIGroup:GetName() } )
local _AI_A2A = AIGroup:GetState( AIGroup, "AI_A2A" ) -- #AI_A2A
_AI_A2A:__RTB( 0.5 )
AIGroup:F( { "AI_A2A.RTBRoute:", AIGroup:GetName() } )
if AIGroup:IsAlive() then
Fsm:__RTB( 0.5 )
end
end
--- @param Wrapper.Group#GROUP AIGroup
function AI_A2A.RTBHold( AIGroup, Fsm )
AIGroup:F( { "AI_A2A.RTBHold:", AIGroup:GetName() } )
if AIGroup:IsAlive() then
Fsm:__RTB( 0.5 )
Fsm:Return()
local Task = AIGroup:TaskOrbitCircle( 4000, 400 )
AIGroup:SetTask( Task )
end
end
--- @param #AI_A2A self
@ -448,8 +553,6 @@ function AI_A2A:onafterRTB( AIGroup, From, Event, To )
self:E( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" )
self.CheckStatus = false
self:ClearTargetDistance()
AIGroup:ClearTasks()
@ -466,11 +569,12 @@ function AI_A2A:onafterRTB( AIGroup, From, Event, To )
local ToAirbaseCoord = CurrentCoord:Translate( 5000, ToAirbaseAngle )
if Distance < 5000 then
self:E( "RTB and near the airbase!" )
self:Home()
return
end
--- Create a route point of type air.
local ToPatrolRoutePoint = ToAirbaseCoord:RoutePointAir(
local ToRTBRoutePoint = ToAirbaseCoord:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
@ -481,7 +585,8 @@ function AI_A2A:onafterRTB( AIGroup, From, Event, To )
self:F( { Angle = ToAirbaseAngle, ToTargetSpeed = ToTargetSpeed } )
self:T2( { self.MinSpeed, self.MaxSpeed, ToTargetSpeed } )
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
EngageRoute[#EngageRoute+1] = ToRTBRoutePoint
EngageRoute[#EngageRoute+1] = ToRTBRoutePoint
AIGroup:OptionROEHoldFire()
AIGroup:OptionROTEvadeFire()
@ -490,13 +595,11 @@ function AI_A2A:onafterRTB( AIGroup, From, Event, To )
AIGroup:WayPointInitialize( EngageRoute )
local Tasks = {}
Tasks[#Tasks+1] = AIGroup:TaskFunction( 1, 1, "AI_A2A.RTBRoute" )
EngageRoute[1].task = AIGroup:TaskCombo( Tasks )
AIGroup:SetState( AIGroup, "AI_A2A", self )
Tasks[#Tasks+1] = AIGroup:TaskFunction( "AI_A2A.RTBRoute", self )
EngageRoute[#EngageRoute].task = AIGroup:TaskCombo( Tasks )
--- NOW ROUTE THE GROUP!
AIGroup:WayPointExecute( 1, 0 )
AIGroup:Route( EngageRoute, 0.5 )
end
@ -513,6 +616,89 @@ function AI_A2A:onafterHome( AIGroup, From, Event, To )
end
end
--- @param #AI_A2A self
-- @param Wrapper.Group#GROUP AIGroup
function AI_A2A:onafterHold( AIGroup, From, Event, To, HoldTime )
self:F( { AIGroup, From, Event, To } )
self:E( "Group " .. self.Controllable:GetName() .. " ... Holding! ( " .. self:GetState() .. " )" )
if AIGroup and AIGroup:IsAlive() then
local OrbitTask = AIGroup:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
local TimedOrbitTask = AIGroup:TaskControlled( OrbitTask, AIGroup:TaskCondition( nil, nil, nil, nil, HoldTime , nil ) )
local RTBTask = AIGroup:TaskFunction( "AI_A2A.RTBHold", self )
local OrbitHoldTask = AIGroup:TaskOrbitCircle( 4000, self.PatrolMinSpeed )
--AIGroup:SetState( AIGroup, "AI_A2A", self )
AIGroup:SetTask( AIGroup:TaskCombo( { TimedOrbitTask, RTBTask, OrbitHoldTask } ), 1 )
end
end
--- @param Wrapper.Group#GROUP AIGroup
function AI_A2A.Resume( AIGroup, Fsm )
AIGroup:F( { "AI_A2A.Resume:", AIGroup:GetName() } )
if AIGroup:IsAlive() then
Fsm:__RTB( 0.5 )
end
end
--- @param #AI_A2A self
-- @param Wrapper.Group#GROUP AIGroup
function AI_A2A:onafterRefuel( AIGroup, From, Event, To )
self:F( { AIGroup, From, Event, To } )
self:E( "Group " .. self.Controllable:GetName() .. " ... Refuelling! ( " .. self:GetState() .. " )" )
if AIGroup and AIGroup:IsAlive() then
local Tanker = GROUP:FindByName( self.TankerName )
if Tanker:IsAlive() and Tanker:IsAirPlane() then
local RefuelRoute = {}
--- Calculate the target route point.
local CurrentCoord = AIGroup:GetCoordinate()
local ToRefuelCoord = Tanker:GetCoordinate()
local ToRefuelSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
--- Create a route point of type air.
local ToRefuelRoutePoint = ToRefuelCoord:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
ToRefuelSpeed,
true
)
self:F( { ToRefuelSpeed = ToRefuelSpeed } )
RefuelRoute[#RefuelRoute+1] = ToRefuelRoutePoint
RefuelRoute[#RefuelRoute+1] = ToRefuelRoutePoint
AIGroup:OptionROEHoldFire()
AIGroup:OptionROTEvadeFire()
local Tasks = {}
Tasks[#Tasks+1] = AIGroup:TaskRefueling()
Tasks[#Tasks+1] = AIGroup:TaskFunction( self:GetClassName() .. ".Resume", self )
RefuelRoute[#RefuelRoute].task = AIGroup:TaskCombo( Tasks )
AIGroup:Route( RefuelRoute, 0.5 )
else
self:RTB()
end
end
end

View File

@ -4,7 +4,7 @@
--
-- ===
--
-- AI CAP classes makes AI Controllables execute a Combat Air Patrol.
-- AI CAP classes makes AI Groups execute a Combat Air Patrol.
--
-- There are the following types of CAP classes defined:
--
@ -34,7 +34,7 @@
--- # AI_A2A_CAP class, extends @{AI_CAP#AI_PATROL_ZONE}
--
-- The AI_A2A_CAP class implements the core functions to patrol a @{Zone} by an AI @{Controllable} or @{Group}
-- The AI_A2A_CAP class implements the core functions to patrol a @{Zone} by an AI @{Group} or @{Group}
-- and automatically engage any airborne enemies that are within a certain range or within a certain zone.
--
-- ![Process](..\Presentations\AI_CAP\Dia3.JPG)
@ -120,20 +120,20 @@ AI_A2A_CAP = {
--- Creates a new AI_A2A_CAP object
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AIGroup
-- @param Wrapper.Group#GROUP AICap
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
-- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h.
-- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h.
-- @param Dcs.DCSTypes#Speed EngageMinSpeed The minimum speed of the @{Controllable} in km/h when engaging a target.
-- @param Dcs.DCSTypes#Speed EngageMaxSpeed The maximum speed of the @{Controllable} in km/h when engaging a target.
-- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h.
-- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Group} in km/h.
-- @param Dcs.DCSTypes#Speed EngageMinSpeed The minimum speed of the @{Group} in km/h when engaging a target.
-- @param Dcs.DCSTypes#Speed EngageMaxSpeed The maximum speed of the @{Group} in km/h when engaging a target.
-- @param Dcs.DCSTypes#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
-- @return #AI_A2A_CAP
function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType )
function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType )
-- Inherits from BASE
local self = BASE:Inherit( self, AI_A2A_PATROL:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_A2A_CAP
local self = BASE:Inherit( self, AI_A2A_PATROL:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_A2A_CAP
self.Accomplished = false
self.Engaging = false
@ -141,12 +141,12 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
self.EngageMinSpeed = EngageMinSpeed
self.EngageMaxSpeed = EngageMaxSpeed
self:AddTransition( { "Patrolling", "Engaging", "Returning" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_CAP.
self:AddTransition( { "Patrolling", "Engaging", "Returning", "Airborne" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_CAP.
--- OnBefore Transition Handler for Event Engage.
-- @function [parent=#AI_A2A_CAP] OnBeforeEngage
-- @param #AI_A2A_CAP self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -155,7 +155,7 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
--- OnAfter Transition Handler for Event Engage.
-- @function [parent=#AI_A2A_CAP] OnAfterEngage
-- @param #AI_A2A_CAP self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -172,7 +172,7 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
--- OnLeave Transition Handler for State Engaging.
-- @function [parent=#AI_A2A_CAP] OnLeaveEngaging
-- @param #AI_A2A_CAP self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -181,7 +181,7 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
--- OnEnter Transition Handler for State Engaging.
-- @function [parent=#AI_A2A_CAP] OnEnterEngaging
-- @param #AI_A2A_CAP self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -191,7 +191,7 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
--- OnBefore Transition Handler for Event Fired.
-- @function [parent=#AI_A2A_CAP] OnBeforeFired
-- @param #AI_A2A_CAP self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -200,7 +200,7 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
--- OnAfter Transition Handler for Event Fired.
-- @function [parent=#AI_A2A_CAP] OnAfterFired
-- @param #AI_A2A_CAP self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -219,7 +219,7 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
--- OnBefore Transition Handler for Event Destroy.
-- @function [parent=#AI_A2A_CAP] OnBeforeDestroy
-- @param #AI_A2A_CAP self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -228,7 +228,7 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
--- OnAfter Transition Handler for Event Destroy.
-- @function [parent=#AI_A2A_CAP] OnAfterDestroy
-- @param #AI_A2A_CAP self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -248,7 +248,7 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
--- OnBefore Transition Handler for Event Abort.
-- @function [parent=#AI_A2A_CAP] OnBeforeAbort
-- @param #AI_A2A_CAP self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -257,7 +257,7 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
--- OnAfter Transition Handler for Event Abort.
-- @function [parent=#AI_A2A_CAP] OnAfterAbort
-- @param #AI_A2A_CAP self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -276,7 +276,7 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
--- OnBefore Transition Handler for Event Accomplish.
-- @function [parent=#AI_A2A_CAP] OnBeforeAccomplish
-- @param #AI_A2A_CAP self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -285,7 +285,7 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
--- OnAfter Transition Handler for Event Accomplish.
-- @function [parent=#AI_A2A_CAP] OnAfterAccomplish
-- @param #AI_A2A_CAP self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -302,6 +302,17 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
return self
end
--- onafter State Transition for Event Patrol.
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AICap The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2A_CAP:onafterStart( AICap, From, Event, To )
AICap:HandleEvent( EVENTS.Takeoff, nil, self )
end
--- Set the Engage Zone which defines where the AI will engage bogies.
-- @param #AI_A2A_CAP self
@ -333,33 +344,36 @@ end
--- onafter State Transition for Event Patrol.
-- @param #AI_A2A_CAP self
-- @param Wrapper.Controllable#CONTROLLABLE AIGroup The AI Group managed by the FSM.
-- @param Wrapper.Group#GROUP AICap The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2A_CAP:onafterPatrol( AIGroup, From, Event, To )
function AI_A2A_CAP:onafterPatrol( AICap, From, Event, To )
-- Call the parent Start event handler
self:GetParent(self).onafterPatrol( self, AIGroup, From, Event, To )
self:GetParent(self).onafterPatrol( self, AICap, From, Event, To )
self:HandleEvent( EVENTS.Dead )
end
-- todo: need to fix this global function
--- @param Wrapper.Group#GROUP AIGroup
function AI_A2A_CAP.AttackRoute( AIGroup )
--- @param Wrapper.Group#GROUP AICap
function AI_A2A_CAP.AttackRoute( AICap, Fsm )
local EngageZone = AIGroup:GetState( AIGroup, "AI_A2A_CAP" ) -- AI.AI_Cap#AI_A2A_CAP
EngageZone:__Engage( 0.5 )
AICap:F( { "AI_A2A_CAP.AttackRoute:", AICap:GetName() } )
if AICap:IsAlive() then
Fsm:__Engage( 0.5 )
end
end
--- @param #AI_A2A_CAP self
-- @param Wrapper.Controllable#CONTROLLABLE AIGroup The Controllable Object managed by the FSM.
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2A_CAP:onbeforeEngage( AIGroup, From, Event, To )
function AI_A2A_CAP:onbeforeEngage( AICap, From, Event, To )
if self.Accomplished == true then
return false
@ -367,43 +381,43 @@ function AI_A2A_CAP:onbeforeEngage( AIGroup, From, Event, To )
end
--- @param #AI_A2A_CAP self
-- @param Wrapper.Controllable#CONTROLLABLE AIGroup The AI Group managed by the FSM.
-- @param Wrapper.Group#GROUP AICap The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2A_CAP:onafterAbort( AIGroup, From, Event, To )
AIGroup:ClearTasks()
function AI_A2A_CAP:onafterAbort( AICap, From, Event, To )
AICap:ClearTasks()
self:__Route( 0.5 )
end
--- @param #AI_A2A_CAP self
-- @param Wrapper.Controllable#CONTROLLABLE AIGroup The Controllable Object managed by the FSM.
-- @param Wrapper.Group#GROUP AICap The AICap Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2A_CAP:onafterEngage( AIGroup, From, Event, To, AttackSetUnit )
function AI_A2A_CAP:onafterEngage( AICap, From, Event, To, AttackSetUnit )
self:F( { AIGroup, From, Event, To, AttackSetUnit} )
self:F( { AICap, From, Event, To, AttackSetUnit} )
self.AttackSetUnit = AttackSetUnit or self.AttackSetUnit -- Core.Set#SET_UNIT
local FirstAttackUnit = self.AttackSetUnit:GetFirst()
local FirstAttackUnit = self.AttackSetUnit:GetFirst() -- Wrapper.Unit#UNIT
if FirstAttackUnit then
if FirstAttackUnit and FirstAttackUnit:IsAlive() then -- If there is no attacker anymore, stop the engagement.
if AIGroup:IsAlive() then
if AICap:IsAlive() then
local EngageRoute = {}
--- Calculate the target route point.
local CurrentCoord = AIGroup:GetCoordinate()
local CurrentCoord = AICap:GetCoordinate()
local ToTargetCoord = self.AttackSetUnit:GetFirst():GetCoordinate()
local ToTargetSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed )
local ToInterceptAngle = CurrentCoord:GetAngleDegrees( CurrentCoord:GetDirectionVec3( ToTargetCoord ) )
--- Create a route point of type air.
local ToPatrolRoutePoint = CurrentCoord:Translate( 5000, ToInterceptAngle ):RoutePointAir(
local ToPatrolRoutePoint = CurrentCoord:Translate( 5000, ToInterceptAngle ):WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
@ -414,40 +428,31 @@ function AI_A2A_CAP:onafterEngage( AIGroup, From, Event, To, AttackSetUnit )
self:F( { Angle = ToInterceptAngle, ToTargetSpeed = ToTargetSpeed } )
self:T2( { self.MinSpeed, self.MaxSpeed, ToTargetSpeed } )
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
AIGroup:OptionROEOpenFire()
AIGroup:OptionROTPassiveDefense()
local AttackTasks = {}
for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do
local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT
self:T( { "Attacking Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } )
if AttackUnit:IsAlive() and AttackUnit:IsAir() then
AttackTasks[#AttackTasks+1] = AIGroup:TaskAttackUnit( AttackUnit )
AttackTasks[#AttackTasks+1] = AICap:TaskAttackUnit( AttackUnit )
end
end
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
self.Controllable:WayPointInitialize( EngageRoute )
if #AttackTasks == 0 then
self:E("No targets found -> Going back to Patrolling")
self:__Abort( 0.5 )
else
AttackTasks[#AttackTasks+1] = AIGroup:TaskFunction( 1, #AttackTasks, "AI_A2A_CAP.AttackRoute" )
AttackTasks[#AttackTasks+1] = AIGroup:TaskOrbitCircle( 4000, self.PatrolMinSpeed )
EngageRoute[1].task = AIGroup:TaskCombo( AttackTasks )
--- Do a trick, link the NewEngageRoute function of the object to the AIControllable in a temporary variable ...
AIGroup:SetState( AIGroup, "AI_A2A_CAP", self )
AICap:OptionROEOpenFire()
AICap:OptionROTEvadeFire()
AttackTasks[#AttackTasks+1] = AICap:TaskFunction( "AI_A2A_CAP.AttackRoute", self )
EngageRoute[#EngageRoute].task = AICap:TaskCombo( AttackTasks )
end
--- NOW ROUTE THE GROUP!
AIGroup:WayPointExecute( 1, 0 )
AICap:Route( EngageRoute, 0.5 )
end
else
self:E("No targets found -> Going back to Patrolling")
@ -456,22 +461,22 @@ function AI_A2A_CAP:onafterEngage( AIGroup, From, Event, To, AttackSetUnit )
end
--- @param #AI_A2A_CAP self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2A_CAP:onafterAccomplish( Controllable, From, Event, To )
function AI_A2A_CAP:onafterAccomplish( AICap, From, Event, To )
self.Accomplished = true
self:SetDetectionOff()
end
--- @param #AI_A2A_CAP self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Core.Event#EVENTDATA EventData
function AI_A2A_CAP:onafterDestroy( Controllable, From, Event, To, EventData )
function AI_A2A_CAP:onafterDestroy( AICap, From, Event, To, EventData )
if EventData.IniUnit then
self.AttackUnits[EventData.IniUnit] = nil
@ -489,3 +494,15 @@ function AI_A2A_CAP:OnEventDead( EventData )
end
end
end
--- @param Wrapper.Group#GROUP AICap
function AI_A2A_CAP.Resume( AICap )
AICap:F( { "AI_A2A_CAP.Resume:", AICap:GetName() } )
if AICap:IsAlive() then
local _AI_A2A = AICap:GetState( AICap, "AI_A2A" ) -- #AI_A2A
_AI_A2A:__Reset( 1 )
_AI_A2A:__Route( 5 )
end
end

File diff suppressed because it is too large Load Diff

View File

@ -117,12 +117,12 @@ AI_A2A_GCI = {
--- Creates a new AI_A2A_GCI object
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIGroup
-- @param Wrapper.Group#GROUP AIIntercept
-- @return #AI_A2A_GCI
function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed )
-- Inherits from BASE
local self = BASE:Inherit( self, AI_A2A:New( AIGroup ) ) -- #AI_A2A_GCI
local self = BASE:Inherit( self, AI_A2A:New( AIIntercept ) ) -- #AI_A2A_GCI
self.Accomplished = false
self.Engaging = false
@ -134,12 +134,12 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
self.PatrolAltType = "RADIO"
self:AddTransition( { "Started", "Engaging", "Returning" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_GCI.
self:AddTransition( { "Started", "Engaging", "Returning", "Airborne" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_GCI.
--- OnBefore Transition Handler for Event Engage.
-- @function [parent=#AI_A2A_GCI] OnBeforeEngage
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -148,7 +148,7 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
--- OnAfter Transition Handler for Event Engage.
-- @function [parent=#AI_A2A_GCI] OnAfterEngage
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -165,7 +165,7 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
--- OnLeave Transition Handler for State Engaging.
-- @function [parent=#AI_A2A_GCI] OnLeaveEngaging
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -174,7 +174,7 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
--- OnEnter Transition Handler for State Engaging.
-- @function [parent=#AI_A2A_GCI] OnEnterEngaging
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -184,7 +184,7 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
--- OnBefore Transition Handler for Event Fired.
-- @function [parent=#AI_A2A_GCI] OnBeforeFired
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -193,7 +193,7 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
--- OnAfter Transition Handler for Event Fired.
-- @function [parent=#AI_A2A_GCI] OnAfterFired
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -212,7 +212,7 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
--- OnBefore Transition Handler for Event Destroy.
-- @function [parent=#AI_A2A_GCI] OnBeforeDestroy
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -221,7 +221,7 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
--- OnAfter Transition Handler for Event Destroy.
-- @function [parent=#AI_A2A_GCI] OnAfterDestroy
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -241,7 +241,7 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
--- OnBefore Transition Handler for Event Abort.
-- @function [parent=#AI_A2A_GCI] OnBeforeAbort
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -250,7 +250,7 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
--- OnAfter Transition Handler for Event Abort.
-- @function [parent=#AI_A2A_GCI] OnAfterAbort
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -269,7 +269,7 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
--- OnBefore Transition Handler for Event Accomplish.
-- @function [parent=#AI_A2A_GCI] OnBeforeAccomplish
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -278,7 +278,7 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
--- OnAfter Transition Handler for Event Accomplish.
-- @function [parent=#AI_A2A_GCI] OnAfterAccomplish
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -295,14 +295,27 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
return self
end
--- onafter State Transition for Event Patrol.
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIGroup The AI Group managed by the FSM.
-- @param Wrapper.Group#GROUP AIIntercept The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2A_GCI:onafterEngage( AIGroup, From, Event, To )
function AI_A2A_GCI:onafterStart( AIIntercept, From, Event, To )
AIIntercept:HandleEvent( EVENTS.Takeoff, nil, self )
end
--- onafter State Transition for Event Patrol.
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2A_GCI:onafterEngage( AIIntercept, From, Event, To )
self:HandleEvent( EVENTS.Dead )
@ -311,19 +324,24 @@ end
-- todo: need to fix this global function
--- @param Wrapper.Group#GROUP AIControllable
function AI_A2A_GCI.InterceptRoute( AIControllable )
function AI_A2A_GCI.InterceptRoute( AIIntercept, Fsm )
AIControllable:T( "NewEngageRoute" )
local EngageZone = AIControllable:GetState( AIControllable, "EngageZone" ) -- AI.AI_Cap#AI_A2A_GCI
EngageZone:__Engage( 0.5 )
AIIntercept:F( { "AI_A2A_GCI.InterceptRoute:", AIIntercept:GetName() } )
if AIIntercept:IsAlive() then
Fsm:__Engage( 0.5 )
--local Task = AIIntercept:TaskOrbitCircle( 4000, 400 )
--AIIntercept:SetTask( Task )
end
end
--- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2A_GCI:onbeforeEngage( AIGroup, From, Event, To )
function AI_A2A_GCI:onbeforeEngage( AIIntercept, From, Event, To )
if self.Accomplished == true then
return false
@ -331,39 +349,41 @@ function AI_A2A_GCI:onbeforeEngage( AIGroup, From, Event, To )
end
--- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIGroup The AI Group managed by the FSM.
-- @param Wrapper.Group#GROUP AIIntercept The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2A_GCI:onafterAbort( AIGroup, From, Event, To )
AIGroup:ClearTasks()
function AI_A2A_GCI:onafterAbort( AIIntercept, From, Event, To )
AIIntercept:ClearTasks()
self:Return()
self:__RTB( 0.5 )
end
--- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIIntercept The GroupGroup managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2A_GCI:onafterEngage( AIGroup, From, Event, To, AttackSetUnit )
function AI_A2A_GCI:onafterEngage( AIIntercept, From, Event, To, AttackSetUnit )
self:F( { AIGroup, From, Event, To, AttackSetUnit} )
self:F( { AIIntercept, From, Event, To, AttackSetUnit} )
self.AttackSetUnit = AttackSetUnit or self.AttackSetUnit -- Core.Set#SET_UNIT
local FirstAttackUnit = self.AttackSetUnit:GetFirst()
if FirstAttackUnit then
if FirstAttackUnit and FirstAttackUnit:IsAlive() then
if AIGroup:IsAlive() then
if AIIntercept:IsAlive() then
local EngageRoute = {}
local CurrentCoord = AIIntercept:GetCoordinate()
--- Calculate the target route point.
local CurrentCoord = AIGroup:GetCoordinate()
local CurrentCoord = AIIntercept:GetCoordinate()
local ToTargetCoord = self.AttackSetUnit:GetFirst():GetCoordinate()
self:SetTargetDistance( ToTargetCoord ) -- For RTB status check
@ -372,7 +392,7 @@ function AI_A2A_GCI:onafterEngage( AIGroup, From, Event, To, AttackSetUnit )
local ToInterceptAngle = CurrentCoord:GetAngleDegrees( CurrentCoord:GetDirectionVec3( ToTargetCoord ) )
--- Create a route point of type air.
local ToPatrolRoutePoint = CurrentCoord:Translate( 5000, ToInterceptAngle ):RoutePointAir(
local ToPatrolRoutePoint = CurrentCoord:Translate( 15000, ToInterceptAngle ):WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
@ -381,42 +401,34 @@ function AI_A2A_GCI:onafterEngage( AIGroup, From, Event, To, AttackSetUnit )
)
self:F( { Angle = ToInterceptAngle, ToTargetSpeed = ToTargetSpeed } )
self:T2( { self.EngageMinSpeed, self.EngageMaxSpeed, ToTargetSpeed } )
self:F( { self.EngageMinSpeed, self.EngageMaxSpeed, ToTargetSpeed } )
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
AIGroup:OptionROEOpenFire()
AIGroup:OptionROTPassiveDefense()
local AttackTasks = {}
for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do
local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT
self:T( { "Intercepting Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } )
if AttackUnit:IsAlive() and AttackUnit:IsAir() then
AttackTasks[#AttackTasks+1] = AIGroup:TaskAttackUnit( AttackUnit )
self:T( { "Intercepting Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } )
AttackTasks[#AttackTasks+1] = AIIntercept:TaskAttackUnit( AttackUnit )
end
end
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
AIGroup:WayPointInitialize( EngageRoute )
if #AttackTasks == 0 then
self:E("No targets found -> Going RTB")
self:Return()
self:__RTB( 0.5 )
else
AttackTasks[#AttackTasks+1] = AIGroup:TaskFunction( 1, #AttackTasks, "AI_A2A_GCI.InterceptRoute" )
AttackTasks[#AttackTasks+1] = AIGroup:TaskOrbitCircle( 4000, self.EngageMinSpeed )
EngageRoute[1].task = AIGroup:TaskCombo( AttackTasks )
--- Do a trick, link the NewEngageRoute function of the object to the AIControllable in a temporary variable ...
AIGroup:SetState( AIGroup, "EngageZone", self )
AIIntercept:OptionROEOpenFire()
AIIntercept:OptionROTEvadeFire()
AttackTasks[#AttackTasks+1] = AIIntercept:TaskFunction( "AI_A2A_GCI.InterceptRoute", self )
EngageRoute[#EngageRoute].task = AIIntercept:TaskCombo( AttackTasks )
end
--- NOW ROUTE THE GROUP!
AIGroup:WayPointExecute( 1, 0 )
AIIntercept:Route( EngageRoute, 0.5 )
end
else
@ -427,22 +439,22 @@ function AI_A2A_GCI:onafterEngage( AIGroup, From, Event, To, AttackSetUnit )
end
--- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2A_GCI:onafterAccomplish( AIGroup, From, Event, To )
function AI_A2A_GCI:onafterAccomplish( AIIntercept, From, Event, To )
self.Accomplished = true
self:SetDetectionOff()
end
--- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Core.Event#EVENTDATA EventData
function AI_A2A_GCI:onafterDestroy( AIGroup, From, Event, To, EventData )
function AI_A2A_GCI:onafterDestroy( AIIntercept, From, Event, To, EventData )
if EventData.IniUnit then
self.AttackUnits[EventData.IniUnit] = nil

View File

@ -4,7 +4,7 @@
--
-- ===
--
-- AI PATROL classes makes AI Controllables execute an Patrol.
-- AI PATROL classes makes AI Groups execute an Patrol.
--
-- There are the following types of PATROL classes defined:
--
@ -44,7 +44,7 @@
--- # AI_A2A_PATROL class, extends @{Fsm#FSM_CONTROLLABLE}
--
-- The AI_A2A_PATROL class implements the core functions to patrol a @{Zone} by an AI @{Controllable} or @{Group}.
-- The AI_A2A_PATROL class implements the core functions to patrol a @{Zone} by an AI @{Group} or @{Group}.
--
-- ![Process](..\Presentations\AI_PATROL\Dia3.JPG)
--
@ -120,7 +120,7 @@
-- * @{#AI_A2A_PATROL.SetDetectionOn}(): Set the detection on. The AI will detect for targets.
-- * @{#AI_A2A_PATROL.SetDetectionOff}(): Set the detection off, the AI will not detect for targets. The existing target list will NOT be erased.
--
-- The detection frequency can be set with @{#AI_A2A_PATROL.SetDetectionInterval}( seconds ), where the amount of seconds specify how much seconds will be waited before the next detection.
-- The detection frequency can be set with @{#AI_A2A_PATROL.SetRefreshTimeInterval}( seconds ), where the amount of seconds specify how much seconds will be waited before the next detection.
-- Use the method @{#AI_A2A_PATROL.GetDetectedUnits}() to obtain a list of the @{Unit}s detected by the AI.
--
-- The detection can be filtered to potential targets in a specific zone.
@ -139,7 +139,7 @@
--
-- ## 7. Manage "damage" behaviour of the AI in the AI_A2A_PATROL
--
-- When the AI is damaged, it is required that a new AIControllable is started. However, damage cannon be foreseen early on.
-- When the AI is damaged, it is required that a new Patrol is started. However, damage cannon be foreseen early on.
-- Therefore, when the damage treshold is reached, the AI will return immediately to the home base (RTB).
-- Use the method @{#AI_A2A_PATROL.ManageDamage}() to have this proces in place.
--
@ -152,23 +152,23 @@ AI_A2A_PATROL = {
--- Creates a new AI_A2A_PATROL object
-- @param #AI_A2A_PATROL self
-- @param Wrapper.Group#GROUP AIGroup
-- @param Wrapper.Group#GROUP AIPatrol
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
-- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h.
-- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h.
-- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h.
-- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Group} in km/h.
-- @param Dcs.DCSTypes#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
-- @return #AI_A2A_PATROL self
-- @usage
-- -- Define a new AI_A2A_PATROL Object. This PatrolArea will patrol an AIControllable within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h.
-- -- Define a new AI_A2A_PATROL Object. This PatrolArea will patrol a Group within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h.
-- PatrolZone = ZONE:New( 'PatrolZone' )
-- PatrolSpawn = SPAWN:New( 'Patrol Group' )
-- PatrolArea = AI_A2A_PATROL:New( PatrolZone, 3000, 6000, 600, 900 )
function AI_A2A_PATROL:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
function AI_A2A_PATROL:New( AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
-- Inherits from BASE
local self = BASE:Inherit( self, AI_A2A:New( AIGroup ) ) -- #AI_A2A_PATROL
local self = BASE:Inherit( self, AI_A2A:New( AIPatrol ) ) -- #AI_A2A_PATROL
self.PatrolZone = PatrolZone
self.PatrolFloorAltitude = PatrolFloorAltitude
@ -179,12 +179,12 @@ function AI_A2A_PATROL:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeil
-- defafult PatrolAltType to "RADIO" if not specified
self.PatrolAltType = PatrolAltType or "RADIO"
self:AddTransition( "Started", "Patrol", "Patrolling" )
self:AddTransition( { "Started", "Airborne", "Refuelling" }, "Patrol", "Patrolling" )
--- OnBefore Transition Handler for Event Patrol.
-- @function [parent=#AI_A2A_PATROL] OnBeforePatrol
-- @param #AI_A2A_PATROL self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -193,7 +193,7 @@ function AI_A2A_PATROL:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeil
--- OnAfter Transition Handler for Event Patrol.
-- @function [parent=#AI_A2A_PATROL] OnAfterPatrol
-- @param #AI_A2A_PATROL self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -210,7 +210,7 @@ function AI_A2A_PATROL:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeil
--- OnLeave Transition Handler for State Patrolling.
-- @function [parent=#AI_A2A_PATROL] OnLeavePatrolling
-- @param #AI_A2A_PATROL self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -219,7 +219,7 @@ function AI_A2A_PATROL:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeil
--- OnEnter Transition Handler for State Patrolling.
-- @function [parent=#AI_A2A_PATROL] OnEnterPatrolling
-- @param #AI_A2A_PATROL self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -229,7 +229,7 @@ function AI_A2A_PATROL:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeil
--- OnBefore Transition Handler for Event Route.
-- @function [parent=#AI_A2A_PATROL] OnBeforeRoute
-- @param #AI_A2A_PATROL self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -238,7 +238,7 @@ function AI_A2A_PATROL:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeil
--- OnAfter Transition Handler for Event Route.
-- @function [parent=#AI_A2A_PATROL] OnAfterRoute
-- @param #AI_A2A_PATROL self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@ -252,6 +252,8 @@ function AI_A2A_PATROL:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeil
-- @param #AI_A2A_PATROL self
-- @param #number Delay The delay in seconds.
self:AddTransition( "*", "Reset", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_PATROL.
return self
@ -262,8 +264,8 @@ end
--- Sets (modifies) the minimum and maximum speed of the patrol.
-- @param #AI_A2A_PATROL self
-- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h.
-- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h.
-- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h.
-- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Group} in km/h.
-- @return #AI_A2A_PATROL self
function AI_A2A_PATROL:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed )
self:F2( { PatrolMinSpeed, PatrolMaxSpeed } )
@ -290,18 +292,18 @@ end
--- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings.
-- @param #AI_A2A_PATROL self
-- @return #AI_A2A_PATROL self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2A_PATROL:onafterPatrol( Controllable, From, Event, To )
function AI_A2A_PATROL:onafterPatrol( AIPatrol, From, Event, To )
self:F2()
self:ClearTargetDistance()
self:__Route( 1 )
self.Controllable:OnReSpawn(
AIPatrol:OnReSpawn(
function( PatrolGroup )
self:E( "ReSpawn" )
self:__Reset( 1 )
@ -312,23 +314,27 @@ end
--- @param Wrapper.Group#GROUP AIGroup
-- This statis method is called from the route path within the last task at the last waaypoint of the Controllable.
-- Note that this method is required, as triggers the next route when patrolling for the Controllable.
function AI_A2A_PATROL.PatrolRoute( AIGroup )
--- @param Wrapper.Group#GROUP AIPatrol
-- This statis method is called from the route path within the last task at the last waaypoint of the AIPatrol.
-- Note that this method is required, as triggers the next route when patrolling for the AIPatrol.
function AI_A2A_PATROL.PatrolRoute( AIPatrol, Fsm )
local _AI_A2A_Patrol = AIGroup:GetState( AIGroup, "AI_A2A_PATROL" ) -- #AI_A2A_PATROL
_AI_A2A_Patrol:Route()
AIPatrol:F( { "AI_A2A_PATROL.PatrolRoute:", AIPatrol:GetName() } )
if AIPatrol:IsAlive() then
Fsm:Route()
end
end
--- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings.
-- @param #AI_A2A_PATROL self
-- @param Wrapper.Group#GROUP AIGroup The AIGroup managed by the FSM.
-- @param Wrapper.Group#GROUP AIPatrol The Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2A_PATROL:onafterRoute( AIGroup, From, Event, To )
function AI_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To )
self:F2()
@ -338,22 +344,22 @@ function AI_A2A_PATROL:onafterRoute( AIGroup, From, Event, To )
end
if AIGroup:IsAlive() then
if AIPatrol:IsAlive() then
local PatrolRoute = {}
--- Calculate the target route point.
local CurrentCoord = AIGroup:GetCoordinate()
local CurrentCoord = AIPatrol:GetCoordinate()
local ToTargetCoord = self.PatrolZone:GetRandomPointVec2()
ToTargetCoord:SetAlt(math.random( self.PatrolFloorAltitude,self.PatrolCeilingAltitude ) )
ToTargetCoord:SetAlt( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) )
self:SetTargetDistance( ToTargetCoord ) -- For RTB status check
local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
--- Create a route point of type air.
local ToPatrolRoutePoint = ToTargetCoord:RoutePointAir(
local ToPatrolRoutePoint = ToTargetCoord:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
@ -361,22 +367,29 @@ function AI_A2A_PATROL:onafterRoute( AIGroup, From, Event, To )
true
)
PatrolRoute[#PatrolRoute+1] = ToPatrolRoutePoint
PatrolRoute[#PatrolRoute+1] = ToPatrolRoutePoint
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
AIGroup:WayPointInitialize( PatrolRoute )
local Tasks = {}
Tasks[#Tasks+1] = AIGroup:TaskFunction( 1, 1, "AI_A2A_PATROL.PatrolRoute" )
Tasks[#Tasks+1] = AIPatrol:TaskFunction( "AI_A2A_PATROL.PatrolRoute", self )
PatrolRoute[#PatrolRoute].task = AIPatrol:TaskCombo( Tasks )
PatrolRoute[1].task = AIGroup:TaskCombo( Tasks )
--- Do a trick, link the NewPatrolRoute function of the PATROLGROUP object to the AIControllable in a temporary variable ...
AIGroup:SetState( AIGroup, "AI_A2A_PATROL", self )
AIPatrol:OptionROEReturnFire()
AIPatrol:OptionROTEvadeFire()
--- NOW ROUTE THE GROUP!
AIGroup:WayPointExecute( 1, 2 )
AIPatrol:Route( PatrolRoute, 0.5 )
end
end
--- @param Wrapper.Group#GROUP AIPatrol
function AI_A2A_PATROL.Resume( AIPatrol )
AIPatrol:F( { "AI_A2A_PATROL.Resume:", AIPatrol:GetName() } )
if AIPatrol:IsAlive() then
local _AI_A2A = AIPatrol:GetState( AIPatrol, "AI_A2A" ) -- #AI_A2A
_AI_A2A:__Reset( 1 )
_AI_A2A:__Route( 5 )
end
end

View File

@ -531,7 +531,7 @@ function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To,
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToEngageZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:RoutePointAir(
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
@ -588,7 +588,7 @@ function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To,
local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y )
--- Create a route point of type air.
local ToTargetRoutePoint = ToTargetPointVec3:RoutePointAir(
local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
@ -612,7 +612,7 @@ function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To,
--- NOW ROUTE THE GROUP!
Controllable:WayPointExecute( 1 )
self:SetDetectionInterval( 2 )
self:SetRefreshTimeInterval( 2 )
self:SetDetectionActivated()
self:__Target( -2 ) -- Start Targetting
end

View File

@ -33,7 +33,7 @@
--- @type AI_BALANCER
-- @field Core.Set#SET_CLIENT SetClient
-- @field Functional.Spawn#SPAWN SpawnAI
-- @field Core.Spawn#SPAWN SpawnAI
-- @field Wrapper.Group#GROUP Test
-- @extends Core.Fsm#FSM_SET
@ -106,7 +106,7 @@ AI_BALANCER = {
--- Creates a new AI_BALANCER object
-- @param #AI_BALANCER self
-- @param Core.Set#SET_CLIENT SetClient A SET\_CLIENT object that will contain the CLIENT objects to be monitored if they are alive or not (joined by a player).
-- @param Functional.Spawn#SPAWN SpawnAI The default Spawn object to spawn new AI Groups when needed.
-- @param Core.Spawn#SPAWN SpawnAI The default Spawn object to spawn new AI Groups when needed.
-- @return #AI_BALANCER
function AI_BALANCER:New( SetClient, SpawnAI )
@ -151,22 +151,22 @@ end
--- Returns the AI to the nearest friendly @{Airbase#AIRBASE}.
-- @param #AI_BALANCER self
-- @param Dcs.DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}.
-- @param Dcs.DCSTypes#Distance ReturnThresholdRange If there is an enemy @{Client#CLIENT} within the ReturnThresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}.
-- @param Core.Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Set#SET_AIRBASE}s to evaluate where to return to.
function AI_BALANCER:ReturnToNearestAirbases( ReturnTresholdRange, ReturnAirbaseSet )
function AI_BALANCER:ReturnToNearestAirbases( ReturnThresholdRange, ReturnAirbaseSet )
self.ToNearestAirbase = true
self.ReturnTresholdRange = ReturnTresholdRange
self.ReturnThresholdRange = ReturnThresholdRange
self.ReturnAirbaseSet = ReturnAirbaseSet
end
--- Returns the AI to the home @{Airbase#AIRBASE}.
-- @param #AI_BALANCER self
-- @param Dcs.DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}.
function AI_BALANCER:ReturnToHomeAirbase( ReturnTresholdRange )
-- @param Dcs.DCSTypes#Distance ReturnThresholdRange If there is an enemy @{Client#CLIENT} within the ReturnThresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}.
function AI_BALANCER:ReturnToHomeAirbase( ReturnThresholdRange )
self.ToHomeAirbase = true
self.ReturnTresholdRange = ReturnTresholdRange
self.ReturnThresholdRange = ReturnThresholdRange
end
--- @param #AI_BALANCER self
@ -246,12 +246,12 @@ function AI_BALANCER:onenterMonitoring( SetGroup )
if self.ToNearestAirbase == false and self.ToHomeAirbase == false then
self:Destroy( Client.UnitName, AIGroup )
else
-- We test if there is no other CLIENT within the self.ReturnTresholdRange of the first unit of the AI group.
-- We test if there is no other CLIENT within the self.ReturnThresholdRange of the first unit of the AI group.
-- If there is a CLIENT, the AI stays engaged and will not return.
-- If there is no CLIENT within the self.ReturnTresholdRange, then the unit will return to the Airbase return method selected.
-- If there is no CLIENT within the self.ReturnThresholdRange, then the unit will return to the Airbase return method selected.
local PlayerInRange = { Value = false }
local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnTresholdRange )
local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnThresholdRange )
self:T2( RangeZone )

View File

@ -358,16 +358,20 @@ function AI_CAP_ZONE:onafterStart( Controllable, From, Event, To )
end
-- todo: need to fix this global function
--- @param Wrapper.Controllable#CONTROLLABLE AIControllable
function _NewEngageCapRoute( AIControllable )
--- @param AI.AI_CAP#AI_CAP_ZONE
-- @param Wrapper.Group#GROUP EngageGroup
function AI_CAP_ZONE.EngageRoute( EngageGroup, Fsm )
AIControllable:T( "NewEngageRoute" )
local EngageZone = AIControllable:GetState( AIControllable, "EngageZone" ) -- AI.AI_Cap#AI_CAP_ZONE
EngageZone:__Engage( 1 )
EngageGroup:F( { "AI_CAP_ZONE.EngageRoute:", EngageGroup:GetName() } )
if EngageGroup:IsAlive() then
Fsm:__Engage( 1 )
end
end
--- @param #AI_CAP_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
@ -402,7 +406,7 @@ function AI_CAP_ZONE:onafterDetected( Controllable, From, Event, To )
end
if Engage == true then
self:E( 'Detected -> Engaging' )
self:F( 'Detected -> Engaging' )
self:__Engage( 1 )
end
end
@ -440,7 +444,7 @@ function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To )
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToEngageZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:RoutePointAir(
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
@ -464,7 +468,7 @@ function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To )
local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y )
--- Create a route point of type air.
local ToPatrolRoutePoint = ToTargetPointVec3:RoutePointAir(
local ToPatrolRoutePoint = ToTargetPointVec3:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
@ -475,7 +479,7 @@ function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To )
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
Controllable:OptionROEOpenFire()
Controllable:OptionROTPassiveDefense()
Controllable:OptionROTEvadeFire()
local AttackTasks = {}
@ -485,13 +489,13 @@ function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To )
if DetectedUnit:IsAlive() and DetectedUnit:IsAir() then
if self.EngageZone then
if DetectedUnit:IsInZone( self.EngageZone ) then
self:E( {"Within Zone and Engaging ", DetectedUnit } )
self:F( {"Within Zone and Engaging ", DetectedUnit } )
AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit )
end
else
if self.EngageRange then
if DetectedUnit:GetPointVec3():Get2DDistance(Controllable:GetPointVec3() ) <= self.EngageRange then
self:E( {"Within Range and Engaging", DetectedUnit } )
self:F( {"Within Range and Engaging", DetectedUnit } )
AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit )
end
else
@ -503,28 +507,20 @@ function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To )
end
end
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
self.Controllable:WayPointInitialize( EngageRoute )
if #AttackTasks == 0 then
self:E("No targets found -> Going back to Patrolling")
self:F("No targets found -> Going back to Patrolling")
self:__Abort( 1 )
self:__Route( 1 )
self:SetDetectionActivated()
else
AttackTasks[#AttackTasks+1] = Controllable:TaskFunction( "AI_CAP_ZONE.EngageRoute", self )
EngageRoute[1].task = Controllable:TaskCombo( AttackTasks )
--- Do a trick, link the NewEngageRoute function of the object to the AIControllable in a temporary variable ...
self.Controllable:SetState( self.Controllable, "EngageZone", self )
self.Controllable:WayPointFunction( #EngageRoute, 1, "_NewEngageCapRoute" )
self:SetDetectionDeactivated()
end
--- NOW ROUTE THE GROUP!
self.Controllable:WayPointExecute( 1, 2 )
Controllable:Route( EngageRoute, 0.5 )
end
end

View File

@ -373,12 +373,15 @@ function AI_CAS_ZONE:onafterStart( Controllable, From, Event, To )
self:SetDetectionDeactivated() -- When not engaging, set the detection off.
end
--- @param Wrapper.Controllable#CONTROLLABLE AIControllable
function _NewEngageRoute( AIControllable )
--- @param AI.AI_CAS#AI_CAS_ZONE
-- @param Wrapper.Group#GROUP EngageGroup
function AI_CAS_ZONE.EngageRoute( EngageGroup, Fsm )
AIControllable:T( "NewEngageRoute" )
local EngageZone = AIControllable:GetState( AIControllable, "EngageZone" ) -- AI.AI_Cas#AI_CAS_ZONE
EngageZone:__Engage( 1, EngageZone.EngageSpeed, EngageZone.EngageAltitude, EngageZone.EngageWeaponExpend, EngageZone.EngageAttackQty, EngageZone.EngageDirection )
EngageGroup:F( { "AI_CAS_ZONE.EngageRoute:", EngageGroup:GetName() } )
if EngageGroup:IsAlive() then
Fsm:__Engage( 1, Fsm.EngageSpeed, Fsm.EngageAltitude, Fsm.EngageWeaponExpend, Fsm.EngageAttackQty, Fsm.EngageDirection )
end
end
@ -464,6 +467,9 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
if Controllable:IsAlive() then
Controllable:OptionROEOpenFire()
Controllable:OptionROTVertical()
local EngageRoute = {}
--- Calculate the current route point.
@ -473,7 +479,7 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToEngageZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:RoutePointAir(
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
@ -485,7 +491,7 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
local AttackTasks = {}
for DetectedUnitID, DetectedUnit in pairs( self.DetectedUnits ) do
for DetectedUnit, Detected in pairs( self.DetectedUnits ) do
local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT
self:T( DetectedUnit )
if DetectedUnit:IsAlive() then
@ -503,7 +509,8 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
end
end
EngageRoute[1].task = Controllable:TaskCombo( AttackTasks )
AttackTasks[#AttackTasks+1] = Controllable:TaskFunction( "AI_CAS_ZONE.EngageRoute", self )
EngageRoute[#EngageRoute].task = Controllable:TaskCombo( AttackTasks )
--- Define a random point in the @{Zone}. The AI will fly to that point within the zone.
@ -515,7 +522,7 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y )
--- Create a route point of type air.
local ToTargetRoutePoint = ToTargetPointVec3:RoutePointAir(
local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
@ -524,22 +531,10 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
)
EngageRoute[#EngageRoute+1] = ToTargetRoutePoint
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
Controllable:WayPointInitialize( EngageRoute )
Controllable:Route( EngageRoute, 0.5 )
--- Do a trick, link the NewEngageRoute function of the object to the AIControllable in a temporary variable ...
Controllable:SetState( Controllable, "EngageZone", self )
Controllable:WayPointFunction( #EngageRoute, 1, "_NewEngageRoute" )
--- NOW ROUTE THE GROUP!
Controllable:WayPointExecute( 1 )
Controllable:OptionROEOpenFire()
Controllable:OptionROTVertical()
self:SetDetectionInterval( 2 )
self:SetRefreshTimeInterval( 2 )
self:SetDetectionActivated()
self:__Target( -2 ) -- Start Targetting
end

View File

@ -958,7 +958,7 @@ function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1
-- @param Wrapper.Unit#UNIT ClientUnit
function( FollowGroup, Formation, ClientUnit, CT1, CV1, CT2, CV2 )
FollowGroup:OptionROTPassiveDefense()
FollowGroup:OptionROTEvadeFire()
FollowGroup:OptionROEReturnFire()
local GroupUnit = FollowGroup:GetUnit( 1 )

View File

@ -47,7 +47,7 @@
-- @field Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @field Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h.
-- @field Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h.
-- @field Functional.Spawn#SPAWN CoordTest
-- @field Core.Spawn#SPAWN CoordTest
-- @extends Core.Fsm#FSM_CONTROLLABLE
--- # AI_PATROL_ZONE class, extends @{Fsm#FSM_CONTROLLABLE}
@ -128,7 +128,7 @@
-- * @{#AI_PATROL_ZONE.SetDetectionOn}(): Set the detection on. The AI will detect for targets.
-- * @{#AI_PATROL_ZONE.SetDetectionOff}(): Set the detection off, the AI will not detect for targets. The existing target list will NOT be erased.
--
-- The detection frequency can be set with @{#AI_PATROL_ZONE.SetDetectionInterval}( seconds ), where the amount of seconds specify how much seconds will be waited before the next detection.
-- The detection frequency can be set with @{#AI_PATROL_ZONE.SetRefreshTimeInterval}( seconds ), where the amount of seconds specify how much seconds will be waited before the next detection.
-- Use the method @{#AI_PATROL_ZONE.GetDetectedUnits}() to obtain a list of the @{Unit}s detected by the AI.
--
-- The detection can be filtered to potential targets in a specific zone.
@ -187,7 +187,7 @@ function AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltit
-- defafult PatrolAltType to "RADIO" if not specified
self.PatrolAltType = PatrolAltType or "RADIO"
self:SetDetectionInterval( 30 )
self:SetRefreshTimeInterval( 30 )
self.CheckStatus = true
@ -544,7 +544,7 @@ end
-- @param #AI_PATROL_ZONE self
-- @param #number Seconds The interval in seconds.
-- @return #AI_PATROL_ZONE self
function AI_PATROL_ZONE:SetDetectionInterval( Seconds )
function AI_PATROL_ZONE:SetRefreshTimeInterval( Seconds )
self:F2()
if Seconds then
@ -591,13 +591,13 @@ end
-- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_PATROL_ZONE.
-- Once the time is finished, the old AI will return to the base.
-- @param #AI_PATROL_ZONE self
-- @param #number PatrolFuelTresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
-- @param #number PatrolFuelThresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
-- @param #number PatrolOutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base.
-- @return #AI_PATROL_ZONE self
function AI_PATROL_ZONE:ManageFuel( PatrolFuelTresholdPercentage, PatrolOutOfFuelOrbitTime )
function AI_PATROL_ZONE:ManageFuel( PatrolFuelThresholdPercentage, PatrolOutOfFuelOrbitTime )
self.PatrolManageFuel = true
self.PatrolFuelTresholdPercentage = PatrolFuelTresholdPercentage
self.PatrolFuelThresholdPercentage = PatrolFuelThresholdPercentage
self.PatrolOutOfFuelOrbitTime = PatrolOutOfFuelOrbitTime
return self
@ -610,12 +610,12 @@ end
-- Note that for groups, the average damage of the complete group will be calculated.
-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage treshold will be 0.25.
-- @param #AI_PATROL_ZONE self
-- @param #number PatrolDamageTreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged.
-- @param #number PatrolDamageThreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged.
-- @return #AI_PATROL_ZONE self
function AI_PATROL_ZONE:ManageDamage( PatrolDamageTreshold )
function AI_PATROL_ZONE:ManageDamage( PatrolDamageThreshold )
self.PatrolManageDamage = true
self.PatrolDamageTreshold = PatrolDamageTreshold
self.PatrolDamageThreshold = PatrolDamageThreshold
return self
end
@ -748,7 +748,7 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:RoutePointAir(
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TakeOffParking,
POINT_VEC3.RoutePointAction.FromParkingArea,
@ -763,7 +763,7 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:RoutePointAir(
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
@ -789,7 +789,7 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y )
--- Create a route point of type air.
local ToTargetRoutePoint = ToTargetPointVec3:RoutePointAir(
local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
@ -831,10 +831,9 @@ function AI_PATROL_ZONE:onafterStatus()
local RTB = false
local Fuel = self.Controllable:GetUnit(1):GetFuel()
if Fuel < self.PatrolFuelTresholdPercentage then
if Fuel < self.PatrolFuelThresholdPercentage then
self:E( self.Controllable:GetName() .. " is out of fuel:" .. Fuel .. ", RTB!" )
local OldAIControllable = self.Controllable
local AIControllableTemplate = self.Controllable:GetTemplate()
local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
local TimedOrbitTask = OldAIControllable:TaskControlled( OrbitTask, OldAIControllable:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil ) )
@ -846,7 +845,7 @@ function AI_PATROL_ZONE:onafterStatus()
-- TODO: Check GROUP damage function.
local Damage = self.Controllable:GetLife()
if Damage <= self.PatrolDamageTreshold then
if Damage <= self.PatrolDamageThreshold then
self:E( self.Controllable:GetName() .. " is damaged:" .. Damage .. ", RTB!" )
RTB = true
end
@ -877,7 +876,7 @@ function AI_PATROL_ZONE:onafterRTB()
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:RoutePointAir(
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,

View File

@ -91,7 +91,7 @@ do -- ACT_ACCOUNT
--- StateMachine callback function
-- @param #ACT_ACCOUNT self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
@ -107,7 +107,7 @@ do -- ACT_ACCOUNT
--- StateMachine callback function
-- @param #ACT_ACCOUNT self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
@ -125,7 +125,7 @@ do -- ACT_ACCOUNT
--- StateMachine callback function
-- @param #ACT_ACCOUNT self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
@ -155,7 +155,6 @@ do -- ACT_ACCOUNT_DEADS
-- @extends #ACT_ACCOUNT
ACT_ACCOUNT_DEADS = {
ClassName = "ACT_ACCOUNT_DEADS",
TargetSetUnit = nil,
}
@ -163,13 +162,10 @@ do -- ACT_ACCOUNT_DEADS
-- @param #ACT_ACCOUNT_DEADS self
-- @param Set#SET_UNIT TargetSetUnit
-- @param #string TaskName
function ACT_ACCOUNT_DEADS:New( TargetSetUnit, TaskName )
function ACT_ACCOUNT_DEADS:New()
-- Inherits from BASE
local self = BASE:Inherit( self, ACT_ACCOUNT:New() ) -- #ACT_ACCOUNT_DEADS
self.TargetSetUnit = TargetSetUnit
self.TaskName = TaskName
self.DisplayInterval = 30
self.DisplayCount = 30
self.DisplayMessage = true
@ -181,38 +177,39 @@ do -- ACT_ACCOUNT_DEADS
function ACT_ACCOUNT_DEADS:Init( FsmAccount )
self.TargetSetUnit = FsmAccount.TargetSetUnit
self.TaskName = FsmAccount.TaskName
self.Task = self:GetTask()
self.TaskName = self.Task:GetName()
end
--- Process Events
--- StateMachine callback function
-- @param #ACT_ACCOUNT_DEADS self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ACCOUNT_DEADS:onenterReport( ProcessUnit, Task, From, Event, To )
self:E( { ProcessUnit, From, Event, To } )
self:Message( "Your group with assigned " .. self.TaskName .. " task has " .. self.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed." )
local MessageText = "Your group with assigned " .. self.TaskName .. " task has " .. Task.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed."
self:GetCommandCenter():MessageTypeToGroup( MessageText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
end
--- StateMachine callback function
-- @param #ACT_ACCOUNT_DEADS self
-- @param Wrapper.Client#CLIENT ProcessClient
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Tasking.Task#TASK Task
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Event#EVENTDATA EventData
function ACT_ACCOUNT_DEADS:onafterEvent( ProcessClient, Task, From, Event, To, EventData )
self:T( { ProcessClient:GetName(), Task:GetName(), From, Event, To, EventData } )
function ACT_ACCOUNT_DEADS:onafterEvent( ProcessUnit, Task, From, Event, To, EventData )
self:T( { ProcessUnit:GetName(), Task:GetName(), From, Event, To, EventData } )
if self.TargetSetUnit:FindUnit( EventData.IniUnitName ) then
local PlayerName = ProcessClient:GetPlayerName()
if Task.TargetSetUnit:FindUnit( EventData.IniUnitName ) then
local PlayerName = ProcessUnit:GetPlayerName()
local PlayerHit = self.PlayerHits and self.PlayerHits[EventData.IniUnitName]
if PlayerHit == PlayerName then
self:Player( EventData )
@ -224,24 +221,26 @@ do -- ACT_ACCOUNT_DEADS
--- StateMachine callback function
-- @param #ACT_ACCOUNT_DEADS self
-- @param Wrapper.Client#CLIENT ProcessClient
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Tasking.Task#TASK Task
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Event#EVENTDATA EventData
function ACT_ACCOUNT_DEADS:onenterAccountForPlayer( ProcessClient, Task, From, Event, To, EventData )
self:T( { ProcessClient:GetName(), Task:GetName(), From, Event, To, EventData } )
function ACT_ACCOUNT_DEADS:onenterAccountForPlayer( ProcessUnit, Task, From, Event, To, EventData )
self:T( { ProcessUnit:GetName(), Task:GetName(), From, Event, To, EventData } )
local TaskGroup = ProcessClient:GetGroup()
local TaskGroup = ProcessUnit:GetGroup()
self.TargetSetUnit:Remove( EventData.IniUnitName )
self:Message( "You have destroyed a target. Your group assigned with task " .. self.TaskName .. " has " .. self.TargetSetUnit:Count() .. " targets ( " .. self.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed." )
local PlayerName = ProcessClient:GetPlayerName()
Task.TargetSetUnit:Remove( EventData.IniUnitName )
local MessageText = "You have destroyed a target.\nYour group assigned with task " .. self.TaskName .. " has\n" .. Task.TargetSetUnit:Count() .. " targets ( " .. Task.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed."
self:GetCommandCenter():MessageTypeToGroup( MessageText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
local PlayerName = ProcessUnit:GetPlayerName()
Task:AddProgress( PlayerName, "Destroyed " .. EventData.IniTypeName, timer.getTime(), 1 )
if self.TargetSetUnit:Count() > 0 then
if Task.TargetSetUnit:Count() > 0 then
self:__More( 1 )
else
self:__NoMore( 1 )
@ -250,20 +249,22 @@ do -- ACT_ACCOUNT_DEADS
--- StateMachine callback function
-- @param #ACT_ACCOUNT_DEADS self
-- @param Wrapper.Client#CLIENT ProcessClient
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Tasking.Task#TASK Task
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Event#EVENTDATA EventData
function ACT_ACCOUNT_DEADS:onenterAccountForOther( ProcessClient, Task, From, Event, To, EventData )
self:T( { ProcessClient:GetName(), Task:GetName(), From, Event, To, EventData } )
function ACT_ACCOUNT_DEADS:onenterAccountForOther( ProcessUnit, Task, From, Event, To, EventData )
self:T( { ProcessUnit:GetName(), Task:GetName(), From, Event, To, EventData } )
local TaskGroup = ProcessClient:GetGroup()
self.TargetSetUnit:Remove( EventData.IniUnitName )
self:Message( "One of the task targets has been destroyed. Your group assigned with task " .. self.TaskName .. " has " .. self.TargetSetUnit:Count() .. " targets ( " .. self.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed." )
local TaskGroup = ProcessUnit:GetGroup()
Task.TargetSetUnit:Remove( EventData.IniUnitName )
local MessageText = "One of the task targets has been destroyed.\nYour group assigned with task " .. self.TaskName .. " has\n" .. Task.TargetSetUnit:Count() .. " targets ( " .. Task.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed."
self:GetCommandCenter():MessageTypeToGroup( MessageText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
if self.TargetSetUnit:Count() > 0 then
if Task.TargetSetUnit:Count() > 0 then
self:__More( 1 )
else
self:__NoMore( 1 )

View File

@ -229,14 +229,14 @@ do -- ACT_ASSIGN_MENU_ACCEPT
--- StateMachine callback function
-- @param #ACT_ASSIGN_MENU_ACCEPT self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ASSIGN_MENU_ACCEPT:onafterStart( ProcessUnit, From, Event, To )
self:E( { ProcessUnit, From, Event, To } )
self:Message( "Access the radio menu to accept the task. You have 30 seconds or the assignment will be cancelled." )
self:GetCommandCenter():MessageTypeToGroup( "Access the radio menu to accept the task. You have 30 seconds or the assignment will be cancelled.", ProcessUnit:GetGroup(), MESSAGE.Type.Information )
local ProcessGroup = ProcessUnit:GetGroup()
@ -263,7 +263,7 @@ do -- ACT_ASSIGN_MENU_ACCEPT
--- StateMachine callback function
-- @param #ACT_ASSIGN_MENU_ACCEPT self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
@ -275,7 +275,7 @@ do -- ACT_ASSIGN_MENU_ACCEPT
--- StateMachine callback function
-- @param #ACT_ASSIGN_MENU_ACCEPT self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To

View File

@ -154,7 +154,7 @@ do -- ACT_ROUTE
--- Get the routing text to be displayed.
-- The route mode determines the text displayed.
-- @param #ACT_ROUTE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable
-- @param Wrapper.Unit#UNIT Controllable
-- @return #string
function ACT_ROUTE:GetRouteText( Controllable )
@ -174,6 +174,7 @@ do -- ACT_ROUTE
end
local Task = self:GetTask() -- This is to dermine that the coordinates are for a specific task mode (A2A or A2G).
local CC = self:GetTask():GetMission():GetCommandCenter()
if CC then
if CC:IsModeWWII() then
@ -198,7 +199,7 @@ do -- ACT_ROUTE
RouteText = Coordinate:ToStringFromRP( ShortestReferencePoint, ShortestReferenceName, Controllable )
end
else
RouteText = Coordinate:ToString( Controllable )
RouteText = Coordinate:ToString( Controllable, nil, Task )
end
end
@ -214,7 +215,7 @@ do -- ACT_ROUTE
--- StateMachine callback function
-- @param #ACT_ROUTE self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
@ -226,7 +227,7 @@ do -- ACT_ROUTE
--- Check if the controllable has arrived.
-- @param #ACT_ROUTE self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @return #boolean
function ACT_ROUTE:onfuncHasArrived( ProcessUnit )
return false
@ -234,7 +235,7 @@ do -- ACT_ROUTE
--- StateMachine callback function
-- @param #ACT_ROUTE self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
@ -351,7 +352,7 @@ do -- ACT_ROUTE_POINT
--- Method override to check if the controllable has arrived.
-- @param #ACT_ROUTE_POINT self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @return #boolean
function ACT_ROUTE_POINT:onfuncHasArrived( ProcessUnit )
@ -360,7 +361,7 @@ do -- ACT_ROUTE_POINT
if Distance <= self.Range then
local RouteText = "You have arrived."
self:Message( RouteText )
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
return true
end
end
@ -372,14 +373,15 @@ do -- ACT_ROUTE_POINT
--- StateMachine callback function
-- @param #ACT_ROUTE_POINT self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ROUTE_POINT:onafterReport( ProcessUnit, From, Event, To )
local RouteText = self:GetRouteText( ProcessUnit )
self:Message( RouteText )
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Update )
end
end -- ACT_ROUTE_POINT
@ -444,13 +446,13 @@ do -- ACT_ROUTE_ZONE
--- Method override to check if the controllable has arrived.
-- @param #ACT_ROUTE self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @return #boolean
function ACT_ROUTE_ZONE:onfuncHasArrived( ProcessUnit )
if ProcessUnit:IsInZone( self.Zone ) then
local RouteText = "You have arrived within the zone."
self:Message( RouteText )
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
end
return ProcessUnit:IsInZone( self.Zone )
@ -460,7 +462,7 @@ do -- ACT_ROUTE_ZONE
--- StateMachine callback function
-- @param #ACT_ROUTE_ZONE self
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
@ -468,7 +470,7 @@ do -- ACT_ROUTE_ZONE
self:E( { ProcessUnit = ProcessUnit } )
local RouteText = self:GetRouteText( ProcessUnit )
self:Message( RouteText )
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Update )
end
end -- ACT_ROUTE_ZONE

View File

@ -4,10 +4,6 @@
--
-- ===
--
-- The @{#BASE} class is the core root class from where every other class in moose is derived.
--
-- ===
--
-- ### Author: **Sven Van de Velde (FlightControl)**
-- ### Contributions:
--
@ -202,7 +198,15 @@ BASE = {
ClassID = 0,
Events = {},
States = {},
_ = {},
}
--- @field #BASE.__
BASE.__ = {}
--- @field #BASE._
BASE._ = {
Schedules = {} --- Contains the Schedulers Active
}
--- The Formation Class
@ -228,47 +232,19 @@ FORMATION = {
-- @return #BASE
function BASE:New()
local self = routines.utils.deepCopy( self ) -- Create a new self instance
local MetaTable = {}
setmetatable( self, MetaTable )
self.__index = self
_ClassID = _ClassID + 1
self.ClassID = _ClassID
-- This is for "private" methods...
-- When a __ is passed to a method as "self", the __index will search for the method on the public method list too!
-- if rawget( self, "__" ) then
--setmetatable( self, { __index = self.__ } )
-- end
return self
end
function BASE:_Destructor()
--self:E("_Destructor")
--self:EventRemoveAll()
end
-- THIS IS WHY WE NEED LUA 5.2 ...
function BASE:_SetDestructor()
-- TODO: Okay, this is really technical...
-- When you set a proxy to a table to catch __gc, weak tables don't behave like weak...
-- Therefore, I am parking this logic until I've properly discussed all this with the community.
local proxy = newproxy(true)
local proxyMeta = getmetatable(proxy)
proxyMeta.__gc = function ()
env.info("In __gc for " .. self:GetClassNameAndID() )
if self._Destructor then
self:_Destructor()
end
end
-- keep the userdata from newproxy reachable until the object
-- table is about to be garbage-collected - then the __gc hook
-- will be invoked and the destructor called
rawset( self, '__proxy', proxy )
end
--- This is the worker method to inherit from a parent class.
-- @param #BASE self
-- @param Child is the Child class that inherits.
@ -276,18 +252,40 @@ end
-- @return #BASE Child
function BASE:Inherit( Child, Parent )
local Child = routines.utils.deepCopy( Child )
--local Parent = routines.utils.deepCopy( Parent )
--local Parent = Parent
if Child ~= nil then
setmetatable( Child, Parent )
Child.__index = Child
-- This is for "private" methods...
-- When a __ is passed to a method as "self", the __index will search for the method on the public method list of the same object too!
if rawget( Child, "__" ) then
setmetatable( Child, { __index = Child.__ } )
setmetatable( Child.__, { __index = Parent } )
else
setmetatable( Child, { __index = Parent } )
end
--Child:_SetDestructor()
end
--self:T( 'Inherited from ' .. Parent.ClassName )
return Child
end
local function getParent( Child )
local Parent = nil
if Child.ClassName == 'BASE' then
Parent = nil
else
if rawget( Child, "__" ) then
Parent = getmetatable( Child.__ ).__index
else
Parent = getmetatable( Child ).__index
end
end
return Parent
end
--- This is the worker method to retrieve the Parent class.
-- Note that the Parent class must be passed to call the parent class method.
--
@ -297,12 +295,91 @@ end
-- @param #BASE self
-- @param #BASE Child is the Child class from which the Parent class needs to be retrieved.
-- @return #BASE
function BASE:GetParent( Child )
local Parent = getmetatable( Child )
-- env.info('Inherited class of ' .. Child.ClassName .. ' is ' .. Parent.ClassName )
return Parent
function BASE:GetParent( Child, FromClass )
local Parent
-- BASE class has no parent
if Child.ClassName == 'BASE' then
Parent = nil
else
self:E({FromClass = FromClass})
self:E({Child = Child.ClassName})
if FromClass then
while( Child.ClassName ~= "BASE" and Child.ClassName ~= FromClass.ClassName ) do
Child = getParent( Child )
self:E({Child.ClassName})
end
end
if Child.ClassName == 'BASE' then
Parent = nil
else
Parent = getParent( Child )
end
end
self:E({Parent.ClassName})
return Parent
end
--- This is the worker method to check if an object is an (sub)instance of a class.
--
-- ### Examples:
--
-- * ZONE:New( 'some zone' ):IsInstanceOf( ZONE ) will return true
-- * ZONE:New( 'some zone' ):IsInstanceOf( 'ZONE' ) will return true
-- * ZONE:New( 'some zone' ):IsInstanceOf( 'zone' ) will return true
-- * ZONE:New( 'some zone' ):IsInstanceOf( 'BASE' ) will return true
--
-- * ZONE:New( 'some zone' ):IsInstanceOf( 'GROUP' ) will return false
--
-- @param #BASE self
-- @param ClassName is the name of the class or the class itself to run the check against
-- @return #boolean
function BASE:IsInstanceOf( ClassName )
-- Is className NOT a string ?
if type( ClassName ) ~= 'string' then
-- Is className a Moose class ?
if type( ClassName ) == 'table' and ClassName.ClassName ~= nil then
-- Get the name of the Moose class as a string
ClassName = ClassName.ClassName
-- className is neither a string nor a Moose class, throw an error
else
-- I'm not sure if this should take advantage of MOOSE logging function, or throw an error for pcall
local err_str = 'className parameter should be a string; parameter received: '..type( ClassName )
self:E( err_str )
-- error( err_str )
return false
end
end
ClassName = string.upper( ClassName )
if string.upper( self.ClassName ) == ClassName then
return true
end
local Parent = getParent(self)
while Parent do
if string.upper( Parent.ClassName ) == ClassName then
return true
end
Parent = getParent( Parent )
end
return false
end
--- Get the ClassName + ClassID of the class instance.
-- The ClassName + ClassID is formatted as '%s#%09d'.
-- @param #BASE self
@ -383,7 +460,7 @@ do -- Event Handling
-- @return #BASE
function BASE:UnHandleEvent( Event )
self:EventDispatcher():Remove( self, Event )
self:EventDispatcher():RemoveEvent( self, Event )
return self
end
@ -566,6 +643,22 @@ function BASE:CreateEventCrash( EventTime, Initiator )
world.onEvent( Event )
end
--- Creation of a Takeoff Event.
-- @param #BASE self
-- @param Dcs.DCSTypes#Time EventTime The time stamp of the event.
-- @param Dcs.DCSWrapper.Object#Object Initiator The initiating object of the event.
function BASE:CreateEventTakeoff( EventTime, Initiator )
self:F( { EventTime, Initiator } )
local Event = {
id = world.event.S_EVENT_TAKEOFF,
time = EventTime,
initiator = Initiator,
}
world.onEvent( Event )
end
-- TODO: Complete Dcs.DCSTypes#Event structure.
--- The main event handling function... This function captures all events generated for the class.
-- @param #BASE self
@ -596,6 +689,86 @@ function BASE:onEvent(event)
end
end
do -- Scheduling
--- Schedule a new time event. Note that the schedule will only take place if the scheduler is *started*. Even for a single schedule event, the scheduler needs to be started also.
-- @param #BASE self
-- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called.
-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.
-- @param #table ... Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
-- @return #number The ScheduleID of the planned schedule.
function BASE:ScheduleOnce( Start, SchedulerFunction, ... )
self:F2( { Start } )
self:T3( { ... } )
local ObjectName = "-"
ObjectName = self.ClassName .. self.ClassID
self:F3( { "ScheduleOnce: ", ObjectName, Start } )
self.SchedulerObject = self
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule(
self,
SchedulerFunction,
{ ... },
Start,
nil,
nil,
nil
)
self._.Schedules[#self.Schedules+1] = ScheduleID
return self._.Schedules
end
--- Schedule a new time event. Note that the schedule will only take place if the scheduler is *started*. Even for a single schedule event, the scheduler needs to be started also.
-- @param #BASE self
-- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called.
-- @param #number Repeat Specifies the interval in seconds when the scheduler will call the event function.
-- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat.
-- @param #number Stop Specifies the amount of seconds when the scheduler will be stopped.
-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.
-- @param #table ... Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
-- @return #number The ScheduleID of the planned schedule.
function BASE:ScheduleRepeat( Start, Repeat, RandomizeFactor, Stop, SchedulerFunction, ... )
self:F2( { Start } )
self:T3( { ... } )
local ObjectName = "-"
ObjectName = self.ClassName .. self.ClassID
self:F3( { "ScheduleRepeat: ", ObjectName, Start, Repeat, RandomizeFactor, Stop } )
self.SchedulerObject = self
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule(
self,
SchedulerFunction,
{ ... },
Start,
Repeat,
RandomizeFactor,
Stop
)
self._.Schedules[SchedulerFunction] = ScheduleID
return self._.Schedules
end
--- Stops the Schedule.
-- @param #BASE self
-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.
function BASE:ScheduleStop( SchedulerFunction )
self:F3( { "ScheduleStop:" } )
_SCHEDULEDISPATCHER:Stop( self, self._.Schedules[SchedulerFunction] )
end
end
--- Set a state or property of the Object given a Key and a Value.
-- Note that if the Object is destroyed, nillified or garbage collected, then the Values and Keys will also be gone.
-- @param #BASE self
@ -610,7 +783,6 @@ function BASE:SetState( Object, Key, Value )
self.States[ClassNameAndID] = self.States[ClassNameAndID] or {}
self.States[ClassNameAndID][Key] = Value
self:T2( { ClassNameAndID, Key, Value } )
return self.States[ClassNameAndID][Key]
end
@ -628,7 +800,6 @@ function BASE:GetState( Object, Key )
if self.States[ClassNameAndID] then
local Value = self.States[ClassNameAndID][Key] or false
self:T2( { ClassNameAndID, Key, Value } )
return Value
end
@ -899,3 +1070,35 @@ end
--- old stuff
--function BASE:_Destructor()
-- --self:E("_Destructor")
--
-- --self:EventRemoveAll()
--end
-- THIS IS WHY WE NEED LUA 5.2 ...
--function BASE:_SetDestructor()
--
-- -- TODO: Okay, this is really technical...
-- -- When you set a proxy to a table to catch __gc, weak tables don't behave like weak...
-- -- Therefore, I am parking this logic until I've properly discussed all this with the community.
--
-- local proxy = newproxy(true)
-- local proxyMeta = getmetatable(proxy)
--
-- proxyMeta.__gc = function ()
-- env.info("In __gc for " .. self:GetClassNameAndID() )
-- if self._Destructor then
-- self:_Destructor()
-- end
-- end
--
-- -- keep the userdata from newproxy reachable until the object
-- -- table is about to be garbage-collected - then the __gc hook
-- -- will be invoked and the destructor called
-- rawset( self, '__proxy', proxy )
--
--end

File diff suppressed because it is too large Load Diff

View File

@ -2,8 +2,19 @@
--
-- ====
--
-- 1) @{#DATABASE} class, extends @{Base#BASE}
-- ===================================================
-- ### Author: **Sven Van de Velde (FlightControl)**
-- ### Contributions:
--
-- ====
--
-- @module Database
--- @type DATABASE
-- @extends Core.Base#BASE
--- # DATABASE class, extends @{Base#BASE}
--
-- Mission designers can use the DATABASE class to refer to:
--
-- * STATICS
@ -17,35 +28,10 @@
--
-- On top, for internal MOOSE administration purposes, the DATBASE administers the Unit and Group TEMPLATES as defined within the Mission Editor.
--
-- Moose will automatically create one instance of the DATABASE class into the **global** object _DATABASE.
-- Moose refers to _DATABASE within the framework extensively, but you can also refer to the _DATABASE object within your missions if required.
-- The singleton object **_DATABASE** is automatically created by MOOSE, that administers all objects within the mission.
-- Moose refers to **_DATABASE** within the framework extensively, but you can also refer to the _DATABASE object within your missions if required.
--
-- 1.1) DATABASE iterators
-- -----------------------
-- You can iterate the database with the available iterator methods.
-- The iterator methods will walk the DATABASE set, and call for each element within the set a function that you provide.
-- The following iterator methods are currently available within the DATABASE:
--
-- * @{#DATABASE.ForEachUnit}: Calls a function for each @{UNIT} it finds within the DATABASE.
-- * @{#DATABASE.ForEachGroup}: Calls a function for each @{GROUP} it finds within the DATABASE.
-- * @{#DATABASE.ForEachPlayer}: Calls a function for each alive player it finds within the DATABASE.
-- * @{#DATABASE.ForEachPlayerJoined}: Calls a function for each joined player it finds within the DATABASE.
-- * @{#DATABASE.ForEachClient}: Calls a function for each @{CLIENT} it finds within the DATABASE.
-- * @{#DATABASE.ForEachClientAlive}: Calls a function for each alive @{CLIENT} it finds within the DATABASE.
--
-- ===
--
--
-- ### Author: **Sven Van de Velde (FlightControl)**
-- ### Contributions:
--
-- ====
-- @module Database
--- DATABASE class
-- @type DATABASE
-- @extends Core.Base#BASE
-- @field #DATABASE
DATABASE = {
ClassName = "DATABASE",
Templates = {
@ -70,6 +56,8 @@ DATABASE = {
NavPoints = {},
PLAYERSETTINGS = {},
ZONENAMES = {},
HITS = {},
DESTROYS = {},
}
local _DATABASECoalition =
@ -104,6 +92,7 @@ function DATABASE:New()
self:HandleEvent( EVENTS.Birth, self._EventOnBirth )
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.Hit, self.AccountHits )
self:HandleEvent( EVENTS.NewCargo )
self:HandleEvent( EVENTS.DeleteCargo )
@ -214,6 +203,16 @@ function DATABASE:FindStatic( StaticName )
return StaticFound
end
--- Finds a AIRBASE based on the AirbaseName.
-- @param #DATABASE self
-- @param #string AirbaseName
-- @return Wrapper.Airbase#AIRBASE The found AIRBASE.
function DATABASE:FindAirbase( AirbaseName )
local AirbaseFound = self.AIRBASES[AirbaseName]
return AirbaseFound
end
--- Adds a Airbase based on the Airbase Name in the DATABASE.
-- @param #DATABASE self
-- @param #string AirbaseName The name of the airbase
@ -375,7 +374,12 @@ function DATABASE:Spawn( SpawnTemplate )
SpawnTemplate.CountryID = SpawnCountryID
SpawnTemplate.CategoryID = SpawnCategoryID
-- Ensure that for the spawned group and its units, there are GROUP and UNIT objects created in the DATABASE.
local SpawnGroup = self:AddGroup( SpawnTemplate.name )
for UnitID, UnitData in pairs( SpawnTemplate.units ) do
self:AddUnit( UnitData.name )
end
return SpawnGroup
end
@ -400,10 +404,13 @@ end
--- Private method that registers new Group Templates within the DATABASE Object.
-- @param #DATABASE self
-- @param #table GroupTemplate
-- @param Dcs.DCScoalition#coalition.side CoalitionSide The coalition.side of the object.
-- @param Dcs.DCSObject#Object.Category CategoryID The Object.category of the object.
-- @param Dcs.DCScountry#country.id CountryID the country.id of the object
-- @return #DATABASE self
function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionID, CategoryID, CountryID )
function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, CategoryID, CountryID, GroupName )
local GroupTemplateName = env.getValueDictByKey(GroupTemplate.name)
local GroupTemplateName = GroupName or env.getValueDictByKey( GroupTemplate.name )
local TraceTable = {}
@ -418,7 +425,7 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionID, CategoryID
end
GroupTemplate.CategoryID = CategoryID
GroupTemplate.CoalitionID = CoalitionID
GroupTemplate.CoalitionID = CoalitionSide
GroupTemplate.CountryID = CountryID
self.Templates.Groups[GroupTemplateName].GroupName = GroupTemplateName
@ -427,7 +434,7 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionID, CategoryID
self.Templates.Groups[GroupTemplateName].UnitCount = #GroupTemplate.units
self.Templates.Groups[GroupTemplateName].Units = GroupTemplate.units
self.Templates.Groups[GroupTemplateName].CategoryID = CategoryID
self.Templates.Groups[GroupTemplateName].CoalitionID = CoalitionID
self.Templates.Groups[GroupTemplateName].CoalitionID = CoalitionSide
self.Templates.Groups[GroupTemplateName].CountryID = CountryID
@ -454,13 +461,13 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionID, CategoryID
self.Templates.Units[UnitTemplate.name].GroupTemplate = GroupTemplate
self.Templates.Units[UnitTemplate.name].GroupId = GroupTemplate.groupId
self.Templates.Units[UnitTemplate.name].CategoryID = CategoryID
self.Templates.Units[UnitTemplate.name].CoalitionID = CoalitionID
self.Templates.Units[UnitTemplate.name].CoalitionID = CoalitionSide
self.Templates.Units[UnitTemplate.name].CountryID = CountryID
if UnitTemplate.skill and (UnitTemplate.skill == "Client" or UnitTemplate.skill == "Player") then
self.Templates.ClientsByName[UnitTemplate.name] = UnitTemplate
self.Templates.ClientsByName[UnitTemplate.name].CategoryID = CategoryID
self.Templates.ClientsByName[UnitTemplate.name].CoalitionID = CoalitionID
self.Templates.ClientsByName[UnitTemplate.name].CoalitionID = CoalitionSide
self.Templates.ClientsByName[UnitTemplate.name].CountryID = CountryID
self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate
end
@ -504,7 +511,7 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category
TraceTable[#TraceTable+1] = "Static"
TraceTable[#TraceTable+1] = self.Templates.Statics[StaticTemplateName].GroupName
TraceTable[#TraceTable+1] = self.Templates.Statics[StaticTemplateName].StaticName
TraceTable[#TraceTable+1] = "Coalition"
TraceTable[#TraceTable+1] = self.Templates.Statics[StaticTemplateName].CoalitionID
@ -631,6 +638,7 @@ end
function DATABASE:_RegisterStatics()
local CoalitionsData = { GroupsRed = coalition.getStaticObjects( coalition.side.RED ), GroupsBlue = coalition.getStaticObjects( coalition.side.BLUE ) }
self:E( { Statics = CoalitionsData } )
for CoalitionId, CoalitionData in pairs( CoalitionsData ) do
for DCSStaticId, DCSStatic in pairs( CoalitionData ) do
@ -707,6 +715,8 @@ function DATABASE:_EventOnDeadOrCrash( Event )
end
end
end
self:AccountDestroys( Event )
end
@ -929,7 +939,7 @@ end
-- @param #string PlayerName
-- @return Core.Settings#SETTINGS
function DATABASE:GetPlayerSettings( PlayerName )
self:E({PlayerName})
self:F2( { PlayerName } )
return self.PLAYERSETTINGS[PlayerName]
end
@ -940,7 +950,7 @@ end
-- @param Core.Settings#SETTINGS Settings
-- @return Core.Settings#SETTINGS
function DATABASE:SetPlayerSettings( PlayerName, Settings )
self:E({PlayerName, Settings})
self:F2( { PlayerName, Settings } )
self.PLAYERSETTINGS[PlayerName] = Settings
end
@ -1038,6 +1048,101 @@ function DATABASE:_RegisterTemplates()
return self
end
--- Account the Hits of the Players.
-- @param #DATABASE self
-- @param Core.Event#EVENTDATA Event
function DATABASE:AccountHits( Event )
self:F( { Event } )
if Event.IniPlayerName ~= nil then -- It is a player that is hitting something
self:T( "Hitting Something" )
-- What is he hitting?
if Event.TgtCategory then
-- A target got hit
self.HITS[Event.TgtUnitName] = self.HITS[Event.TgtUnitName] or {}
local Hit = self.HITS[Event.TgtUnitName]
Hit.Players = Hit.Players or {}
Hit.Players[Event.IniPlayerName] = true
end
end
-- It is a weapon initiated by a player, that is hitting something
-- This seems to occur only with scenery and static objects.
if Event.WeaponPlayerName ~= nil then
self:T( "Hitting Scenery" )
-- What is he hitting?
if Event.TgtCategory then
if Event.IniCoalition then -- A coalition object was hit, probably a static.
-- A target got hit
self.HITS[Event.TgtUnitName] = self.HITS[Event.TgtUnitName] or {}
local Hit = self.HITS[Event.TgtUnitName]
Hit.Players = Hit.Players or {}
Hit.Players[Event.WeaponPlayerName] = true
else -- A scenery object was hit.
end
end
end
end
--- Account the destroys.
-- @param #DATABASE self
-- @param Core.Event#EVENTDATA Event
function DATABASE:AccountDestroys( Event )
self:F( { Event } )
local TargetUnit = nil
local TargetGroup = nil
local TargetUnitName = ""
local TargetGroupName = ""
local TargetPlayerName = ""
local TargetCoalition = nil
local TargetCategory = nil
local TargetType = nil
local TargetUnitCoalition = nil
local TargetUnitCategory = nil
local TargetUnitType = nil
if Event.IniDCSUnit then
TargetUnit = Event.IniUnit
TargetUnitName = Event.IniDCSUnitName
TargetGroup = Event.IniDCSGroup
TargetGroupName = Event.IniDCSGroupName
TargetPlayerName = Event.IniPlayerName
TargetCoalition = Event.IniCoalition
--TargetCategory = TargetUnit:getCategory()
--TargetCategory = TargetUnit:getDesc().category -- Workaround
TargetCategory = Event.IniCategory
TargetType = Event.IniTypeName
TargetUnitType = TargetType
self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType } )
end
self:T( "Something got destroyed" )
local Destroyed = false
-- What is the player destroying?
if self.HITS[Event.IniUnitName] then -- Was there a hit for this unit for this player before registered???
self.DESTROYS[Event.IniUnitName] = self.DESTROYS[Event.IniUnitName] or {}
self.DESTROYS[Event.IniUnitName] = true
end
end

View File

@ -450,16 +450,16 @@ end
-- @param Core.Base#BASE EventClass The self instance of the class for which the event is.
-- @param Dcs.DCSWorld#world.event EventID
-- @return #EVENT.Events
function EVENT:Remove( EventClass, EventID )
function EVENT:RemoveEvent( EventClass, EventID )
self:E( { "Removing subscription for class: ", EventClass:GetClassNameAndID() } )
self:F2( { "Removing subscription for class: ", EventClass:GetClassNameAndID() } )
local EventPriority = EventClass:GetEventPriority()
self.EventsDead = self.EventsDead or {}
self.EventsDead[EventID] = self.EventsDead[EventID] or {}
self.EventsDead[EventID][EventPriority] = self.EventsDead[EventID][EventPriority] or {}
self.EventsDead[EventID][EventPriority][EventClass] = self.Events[EventID][EventPriority][EventClass]
self.Events = self.Events or {}
self.Events[EventID] = self.Events[EventID] or {}
self.Events[EventID][EventPriority] = self.Events[EventID][EventPriority] or {}
self.Events[EventID][EventPriority][EventClass] = self.Events[EventID][EventPriority][EventClass]
self.Events[EventID][EventPriority][EventClass] = nil
@ -561,12 +561,13 @@ end
-- @param Core.Base#BASE EventClass The self instance of the class for which the event is.
-- @param EventID
-- @return #EVENT
function EVENT:OnEventForGroup( GroupName, EventFunction, EventClass, EventID )
self:F2( GroupName )
function EVENT:OnEventForGroup( GroupName, EventFunction, EventClass, EventID, ... )
self:E( GroupName )
local Event = self:Init( EventID, EventClass )
Event.EventGroup = true
Event.EventFunction = EventFunction
Event.Params = arg
return self
end
@ -743,10 +744,15 @@ function EVENT:onEvent( Event )
local EventMeta = _EVENTMETA[Event.id]
if self and self.Events and self.Events[Event.id] then
--self:E( { EventMeta.Text, Event } ) -- Activate the see all incoming events ...
if self and
self.Events and
self.Events[Event.id] and
( Event.initiator ~= nil or ( Event.initiator == nil and Event.id ~= EVENTS.PlayerLeaveUnit ) ) then
if Event.initiator then
Event.IniObjectCategory = Event.initiator:getCategory()
if Event.IniObjectCategory == Object.Category.UNIT then
@ -868,9 +874,9 @@ function EVENT:onEvent( Event )
-- Okay, we got the event from DCS. Now loop the SORTED self.EventSorted[] table for the received Event.id, and for each EventData registered, check if a function needs to be called.
for EventClass, EventData in pairs( self.Events[Event.id][EventPriority] ) do
if Event.IniObjectCategory ~= Object.Category.STATIC then
--self:E( { "Evaluating: ", EventClass:GetClassNameAndID() } )
end
--if Event.IniObjectCategory ~= Object.Category.STATIC then
-- self:E( { "Evaluating: ", EventClass:GetClassNameAndID() } )
--end
Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName )
Event.TgtGroup = GROUP:FindByName( Event.TgtDCSGroupName )
@ -920,7 +926,7 @@ function EVENT:onEvent( Event )
end
else
-- The EventClass is not alive anymore, we remove it from the EventHandlers...
self:Remove( EventClass, Event.id )
self:RemoveEvent( EventClass, Event.id )
end
else
@ -947,7 +953,7 @@ function EVENT:onEvent( Event )
local Result, Value = xpcall(
function()
return EventData.EventFunction( EventClass, Event )
return EventData.EventFunction( EventClass, Event, unpack( EventData.Params ) )
end, ErrorHandler )
else
@ -963,14 +969,14 @@ function EVENT:onEvent( Event )
local Result, Value = xpcall(
function()
return EventFunction( EventClass, Event )
return EventFunction( EventClass, Event, unpack( EventData.Params ) )
end, ErrorHandler )
end
end
end
else
-- The EventClass is not alive anymore, we remove it from the EventHandlers...
self:Remove( EventClass, Event.id )
--self:RemoveEvent( EventClass, Event.id )
end
else
@ -1015,7 +1021,7 @@ function EVENT:onEvent( Event )
end
end
else
self:E( { EventMeta.Text, Event } )
self:T( { EventMeta.Text, Event } )
end
Event = nil

View File

@ -418,7 +418,7 @@ do -- FSM
-- @param #table ReturnEvents A table indicating for which returned events of the SubFSM which Event must be triggered in the FSM.
-- @return Core.Fsm#FSM_PROCESS The SubFSM.
function FSM:AddProcess( From, Event, Process, ReturnEvents )
self:T( { From, Event, Process, ReturnEvents } )
self:T( { From, Event } )
local Sub = {}
Sub.From = From
@ -541,7 +541,7 @@ do -- FSM
end
function FSM:_submap( subs, sub, name )
self:F( { sub = sub, name = name } )
--self:F( { sub = sub, name = name } )
subs[sub.From] = subs[sub.From] or {}
subs[sub.From][sub.Event] = subs[sub.From][sub.Event] or {}
@ -844,7 +844,7 @@ do -- FSM_CONTROLLABLE
-- @param Wrapper.Controllable#CONTROLLABLE FSMControllable
-- @return #FSM_CONTROLLABLE
function FSM_CONTROLLABLE:SetControllable( FSMControllable )
self:F( FSMControllable )
--self:F( FSMControllable:GetName() )
self.Controllable = FSMControllable
end
@ -904,7 +904,7 @@ do -- FSM_PROCESS
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- Core.Fsm#FSM_PROCESS
self:F( Controllable, Task )
--self:F( Controllable )
self:Assign( Controllable, Task )
@ -960,7 +960,7 @@ do -- FSM_PROCESS
-- Copy Processes
for ProcessID, Process in pairs( self:GetProcesses() ) do
self:E( { Process} )
--self:E( { Process:GetName() } )
local FsmProcess = NewFsm:AddProcess( Process.From, Process.Event, Process.fsm:Copy( Controllable, Task ), Process.ReturnEvents )
end
@ -990,7 +990,6 @@ do -- FSM_PROCESS
-- Copy Processes
for ProcessID, Process in pairs( self:GetProcesses() ) do
self:E( { Process} )
if Process.fsm then
Process.fsm:Remove()
Process.fsm = nil
@ -1063,7 +1062,7 @@ end
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @return #FSM_PROCESS self
function FSM_PROCESS:Assign( ProcessUnit, Task )
self:T( { Task, ProcessUnit } )
--self:T( { Task:GetName(), ProcessUnit:GetName() } )
self:SetControllable( ProcessUnit )
self:SetTask( Task )
@ -1093,7 +1092,7 @@ end
-- @param #string From
-- @param #string To
function FSM_PROCESS:onstatechange( ProcessUnit, Task, From, Event, To, Dummy )
self:T( { ProcessUnit, From, Event, To, Dummy, self:IsTrace() } )
self:T( { ProcessUnit:GetName(), From, Event, To, Dummy, self:IsTrace() } )
if self:IsTrace() then
--MESSAGE:New( "@ Process " .. self:GetClassNameAndID() .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll()

View File

@ -0,0 +1,146 @@
--- **Core (WIP)** -- Base class to allow the modeling of processes to achieve Goals.
--
-- ====
--
-- GOAL models processes that have an objective with a defined achievement. Derived classes implement the ways how the achievements can be realized.
--
-- ====
--
-- ### Author: **Sven Van de Velde (FlightControl)**
--
-- ====
--
-- @module Goal
do -- Goal
--- @type GOAL
-- @extends Core.Fsm#FSM
--- # GOAL class, extends @{Fsm#FSM}
--
-- GOAL models processes that have an objective with a defined achievement. Derived classes implement the ways how the achievements can be realized.
--
-- ## 1. GOAL constructor
--
-- * @{#GOAL.New}(): Creates a new GOAL object.
--
-- ## 2. GOAL is a finite state machine (FSM).
--
-- ### 2.1 GOAL States
--
-- * **Pending**: The goal object is in progress.
-- * **Achieved**: The goal objective is Achieved.
--
-- ### 2.2 GOAL Events
--
-- * **Achieved**: Set the goal objective to Achieved.
--
-- @field #GOAL
GOAL = {
ClassName = "GOAL",
}
--- @field #table GOAL.Players
GOAL.Players = {}
--- @field #number GOAL.TotalContributions
GOAL.TotalContributions = 0
--- GOAL Constructor.
-- @param #GOAL self
-- @return #GOAL
function GOAL:New()
local self = BASE:Inherit( self, FSM:New() ) -- #GOAL
self:F( {} )
--- Achieved State for GOAL
-- @field GOAL.Achieved
--- Achieved State Handler OnLeave for GOAL
-- @function [parent=#GOAL] OnLeaveAchieved
-- @param #GOAL self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Achieved State Handler OnEnter for GOAL
-- @function [parent=#GOAL] OnEnterAchieved
-- @param #GOAL self
-- @param #string From
-- @param #string Event
-- @param #string To
self:SetStartState( "Pending" )
self:AddTransition( "*", "Achieved", "Achieved" )
--- Achieved Handler OnBefore for GOAL
-- @function [parent=#GOAL] OnBeforeAchieved
-- @param #GOAL self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Achieved Handler OnAfter for GOAL
-- @function [parent=#GOAL] OnAfterAchieved
-- @param #GOAL self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Achieved Trigger for GOAL
-- @function [parent=#GOAL] Achieved
-- @param #GOAL self
--- Achieved Asynchronous Trigger for GOAL
-- @function [parent=#GOAL] __Achieved
-- @param #GOAL self
-- @param #number Delay
self:SetEventPriority( 5 )
return self
end
--- @param #GOAL self
-- @param #string PlayerName
function GOAL:AddPlayerContribution( PlayerName )
self.Players[PlayerName] = self.Players[PlayerName] or 0
self.Players[PlayerName] = self.Players[PlayerName] + 1
self.TotalContributions = self.TotalContributions + 1
end
--- @param #GOAL self
-- @param #number Player contribution.
function GOAL:GetPlayerContribution( PlayerName )
return self.Players[PlayerName] or 0
end
--- @param #GOAL self
function GOAL:GetPlayerContributions()
return self.Players or {}
end
--- @param #GOAL self
function GOAL:GetTotalContributions()
return self.TotalContributions or 0
end
--- @param #GOAL self
-- @return #boolean true if the goal is Achieved
function GOAL:IsAchieved()
return self:Is( "Achieved" )
end
end

View File

@ -103,6 +103,15 @@ do -- MENU_BASE
return self
end
--- Sets a tag for later selection of menu refresh.
-- @param #MENU_BASE self
-- @param #string MenuTag A Tag or Key that will filter only menu items set with this key.
-- @return #MENU_BASE
function MENU_BASE:SetTag( MenuTag )
self.MenuTag = MenuTag
return self
end
end
do -- MENU_COMMAND_BASE
@ -115,6 +124,7 @@ do -- MENU_COMMAND_BASE
-- ----------------------------------------------------------
-- The MENU_COMMAND_BASE class defines the main MENU class where other MENU COMMAND_
-- classes are derived from, in order to set commands.
--
-- @field #MENU_COMMAND_BASE
MENU_COMMAND_BASE = {
ClassName = "MENU_COMMAND_BASE",
@ -128,15 +138,51 @@ do -- MENU_COMMAND_BASE
-- @return #MENU_COMMAND_BASE
function MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, CommandMenuArguments )
local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) )
local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) -- #MENU_COMMAND_BASE
-- When a menu function goes into error, DCS displays an obscure menu message.
-- This error handler catches the menu error and displays the full call stack.
local ErrorHandler = function( errmsg )
env.info( "MOOSE error in MENU COMMAND function: " .. errmsg )
if debug ~= nil then
env.info( debug.traceback() )
end
return errmsg
end
self.CommandMenuFunction = CommandMenuFunction
self.MenuCallHandler = function( CommandMenuArguments )
self.CommandMenuFunction( unpack( CommandMenuArguments ) )
self:SetCommandMenuFunction( CommandMenuFunction )
self:SetCommandMenuArguments( CommandMenuArguments )
self.MenuCallHandler = function()
local function MenuFunction()
return self.CommandMenuFunction( unpack( self.CommandMenuArguments ) )
end
local Status, Result = xpcall( MenuFunction, ErrorHandler )
end
return self
end
--- This sets the new command function of a menu,
-- so that if a menu is regenerated, or if command function changes,
-- that the function set for the menu is loosely coupled with the menu itself!!!
-- If the function changes, no new menu needs to be generated if the menu text is the same!!!
-- @param #MENU_COMMAND_BASE
-- @return #MENU_COMMAND_BASE
function MENU_COMMAND_BASE:SetCommandMenuFunction( CommandMenuFunction )
self.CommandMenuFunction = CommandMenuFunction
return self
end
--- This sets the new command arguments of a menu,
-- so that if a menu is regenerated, or if command arguments change,
-- that the arguments set for the menu are loosely coupled with the menu itself!!!
-- If the arguments change, no new menu needs to be generated if the menu text is the same!!!
-- @param #MENU_COMMAND_BASE
-- @return #MENU_COMMAND_BASE
function MENU_COMMAND_BASE:SetCommandMenuArguments( CommandMenuArguments )
self.CommandMenuArguments = CommandMenuArguments
return self
end
end
@ -247,7 +293,7 @@ do -- MENU_MISSION_COMMAND
self:T( { MenuText, CommandMenuFunction, arg } )
self.MenuPath = missionCommands.addCommand( MenuText, self.MenuParentPath, self.MenuCallHandler, arg )
self.MenuPath = missionCommands.addCommand( MenuText, self.MenuParentPath, self.MenuCallHandler )
ParentMenu.Menus[self.MenuPath] = self
@ -420,7 +466,7 @@ do -- MENU_COALITION_COMMAND
self:T( { MenuText, CommandMenuFunction, arg } )
self.MenuPath = missionCommands.addCommandForCoalition( self.MenuCoalition, MenuText, self.MenuParentPath, self.MenuCallHandler, arg )
self.MenuPath = missionCommands.addCommandForCoalition( self.MenuCoalition, MenuText, self.MenuParentPath, self.MenuCallHandler )
ParentMenu.Menus[self.MenuPath] = self
@ -653,7 +699,7 @@ do -- MENU_CLIENT
missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), MenuPath[MenuPathID] )
end
self.MenuPath = missionCommands.addCommandForGroup( self.MenuClient:GetClientGroupID(), MenuText, MenuParentPath, self.MenuCallHandler, arg )
self.MenuPath = missionCommands.addCommandForGroup( self.MenuClient:GetClientGroupID(), MenuText, MenuParentPath, self.MenuCallHandler )
MenuPath[MenuPathID] = self.MenuPath
if ParentMenu and ParentMenu.Menus then
@ -805,13 +851,14 @@ do
--- Removes the sub menus recursively of this MENU_GROUP.
-- @param #MENU_GROUP self
-- @param MenuTime
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
-- @return #MENU_GROUP self
function MENU_GROUP:RemoveSubMenus( MenuTime )
function MENU_GROUP:RemoveSubMenus( MenuTime, MenuTag )
--self:F2( { self.MenuPath, MenuTime, self.MenuTime } )
--self:T( { "Removing Group SubMenus:", self.MenuGroup:GetName(), self.MenuPath } )
self:T( { "Removing Group SubMenus:", MenuTime, MenuTag, self.MenuGroup:GetName(), self.MenuPath } )
for MenuText, Menu in pairs( self.Menus ) do
Menu:Remove( MenuTime )
Menu:Remove( MenuTime, MenuTag )
end
end
@ -820,28 +867,31 @@ do
--- Removes the main menu and sub menus recursively of this MENU_GROUP.
-- @param #MENU_GROUP self
-- @param MenuTime
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
-- @return #nil
function MENU_GROUP:Remove( MenuTime )
function MENU_GROUP:Remove( MenuTime, MenuTag )
--self:F2( { self.MenuGroupID, self.MenuPath, MenuTime, self.MenuTime } )
self:RemoveSubMenus( MenuTime )
self:RemoveSubMenus( MenuTime, MenuTag )
if not MenuTime or self.MenuTime ~= MenuTime then
if self.MenuGroup._Menus[self.Path] then
self = self.MenuGroup._Menus[self.Path]
missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath )
if self.ParentMenu then
self.ParentMenu.Menus[self.MenuText] = nil
self.ParentMenu.MenuCount = self.ParentMenu.MenuCount - 1
if self.ParentMenu.MenuCount == 0 then
if self.MenuRemoveParent == true then
self:T2( "Removing Parent Menu " )
self.ParentMenu:Remove()
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
if self.MenuGroup._Menus[self.Path] then
self = self.MenuGroup._Menus[self.Path]
missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath )
if self.ParentMenu then
self.ParentMenu.Menus[self.MenuText] = nil
self.ParentMenu.MenuCount = self.ParentMenu.MenuCount - 1
if self.ParentMenu.MenuCount == 0 then
if self.MenuRemoveParent == true then
self:T2( "Removing Parent Menu " )
self.ParentMenu:Remove()
end
end
end
end
self:T( { "Removing Group Menu:", MenuGroup = self.MenuGroup:GetName(), MenuPath = self.MenuGroup._Menus[self.Path].Path } )
self:T( { "Removing Group Menu:", MenuGroup = self.MenuGroup:GetName() } )
self.MenuGroup._Menus[self.Path] = nil
self = nil
end
@ -852,7 +902,7 @@ do
--- @type MENU_GROUP_COMMAND
-- @extends Core.Menu#MENU_BASE
-- @extends Core.Menu#MENU_COMMAND_BASE
--- # MENU_GROUP_COMMAND class, extends @{Menu#MENU_COMMAND_BASE}
--
@ -876,32 +926,37 @@ do
function MENU_GROUP_COMMAND:New( MenuGroup, MenuText, ParentMenu, CommandMenuFunction, ... )
MenuGroup._Menus = MenuGroup._Menus or {}
local Path = ( ParentMenu and ( table.concat( ParentMenu.MenuPath or {}, "@" ) .. "@" .. MenuText ) ) or MenuText
local Path = ( ParentMenu and ( table.concat( ParentMenu.MenuPath or {}, "@" ) .. "@" .. MenuText ) ) or MenuText
if MenuGroup._Menus[Path] then
self = MenuGroup._Menus[Path]
self:F2( { "Re-using Group Command Menu:", MenuGroup:GetName(), MenuText } )
else
self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) )
--if MenuGroup:IsAlive() then
MenuGroup._Menus[Path] = self
--end
self.Path = Path
self.MenuGroup = MenuGroup
self.MenuGroupID = MenuGroup:GetID()
self.MenuText = MenuText
self.ParentMenu = ParentMenu
self:F( { "Adding Group Command Menu:", MenuGroup = MenuGroup:GetName(), MenuText = MenuText, MenuPath = self.MenuParentPath } )
self.MenuPath = missionCommands.addCommandForGroup( self.MenuGroupID, MenuText, self.MenuParentPath, self.MenuCallHandler, arg )
if self.ParentMenu and self.ParentMenu.Menus then
self.ParentMenu.Menus[MenuText] = self
self.ParentMenu.MenuCount = self.ParentMenu.MenuCount + 1
self:F2( { ParentMenu.Menus, MenuText } )
end
--self:E( { Path=Path } )
--self:E( { self.MenuTag, self.MenuTime, "Re-using Group Command Menu:", MenuGroup:GetName(), MenuText } )
self:SetCommandMenuFunction( CommandMenuFunction )
self:SetCommandMenuArguments( arg )
return self
end
self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) )
--if MenuGroup:IsAlive() then
MenuGroup._Menus[Path] = self
--end
--self:E({Path=Path})
self.Path = Path
self.MenuGroup = MenuGroup
self.MenuGroupID = MenuGroup:GetID()
self.MenuText = MenuText
self.ParentMenu = ParentMenu
self:F( { "Adding Group Command Menu:", MenuGroup = MenuGroup:GetName(), MenuText = MenuText, MenuPath = self.MenuParentPath } )
self.MenuPath = missionCommands.addCommandForGroup( self.MenuGroupID, MenuText, self.MenuParentPath, self.MenuCallHandler )
if self.ParentMenu and self.ParentMenu.Menus then
self.ParentMenu.Menus[MenuText] = self
self.ParentMenu.MenuCount = self.ParentMenu.MenuCount + 1
self:F2( { ParentMenu.Menus, MenuText } )
end
-- end
return self
end
@ -909,28 +964,32 @@ do
--- Removes a menu structure for a group.
-- @param #MENU_GROUP_COMMAND self
-- @param MenuTime
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
-- @return #nil
function MENU_GROUP_COMMAND:Remove( MenuTime )
function MENU_GROUP_COMMAND:Remove( MenuTime, MenuTag )
--self:F2( { self.MenuGroupID, self.MenuPath, MenuTime, self.MenuTime } )
--self:E( { MenuTag = MenuTag, MenuTime = self.MenuTime, Path = self.Path } )
if not MenuTime or self.MenuTime ~= MenuTime then
if self.MenuGroup._Menus[self.Path] then
self = self.MenuGroup._Menus[self.Path]
missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath )
self:T( { "Removing Group Command Menu:", MenuGroup = self.MenuGroup:GetName(), MenuText = self.MenuText, MenuPath = self.Path } )
self.ParentMenu.Menus[self.MenuText] = nil
self.ParentMenu.MenuCount = self.ParentMenu.MenuCount - 1
if self.ParentMenu.MenuCount == 0 then
if self.MenuRemoveParent == true then
self:T2( "Removing Parent Menu " )
self.ParentMenu:Remove()
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
if self.MenuGroup._Menus[self.Path] then
self = self.MenuGroup._Menus[self.Path]
missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath )
--self:E( { "Removing Group Command Menu:", MenuGroup = self.MenuGroup:GetName(), MenuText = self.MenuText, MenuPath = self.Path } )
self.ParentMenu.Menus[self.MenuText] = nil
self.ParentMenu.MenuCount = self.ParentMenu.MenuCount - 1
if self.ParentMenu.MenuCount == 0 then
if self.MenuRemoveParent == true then
self:T2( "Removing Parent Menu " )
self.ParentMenu:Remove()
end
end
self.MenuGroup._Menus[self.Path] = nil
self = nil
end
self.MenuGroup._Menus[self.Path] = nil
self = nil
end
end

View File

@ -53,6 +53,16 @@ MESSAGE = {
MessageID = 0,
}
--- Message Types
-- @type MESSAGE.Type
MESSAGE.Type = {
Update = "Update",
Information = "Information",
Briefing = "Briefing Report",
Overview = "Overview Report",
Detailed = "Detailed Report"
}
--- Creates a new MESSAGE object. Note that these MESSAGE objects are not yet displayed on the display panel. You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients.
-- @param self
@ -74,6 +84,9 @@ function MESSAGE:New( MessageText, MessageDuration, MessageCategory )
local self = BASE:Inherit( self, BASE:New() )
self:F( { MessageText, MessageDuration, MessageCategory } )
self.MessageType = nil
-- When no MessageCategory is given, we don't show it as a title...
if MessageCategory and MessageCategory ~= "" then
if MessageCategory:sub(-1) ~= "\n" then
@ -96,6 +109,37 @@ function MESSAGE:New( MessageText, MessageDuration, MessageCategory )
return self
end
--- Creates a new MESSAGE object of a certain type.
-- Note that these MESSAGE objects are not yet displayed on the display panel.
-- You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients.
-- The message display times are automatically defined based on the timing settings in the @{Settings} menu.
-- @param self
-- @param #string MessageText is the text of the Message.
-- @param #MESSAGE.Type MessageType The type of the message.
-- @return #MESSAGE
-- @usage
-- MessageAll = MESSAGE:NewType( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", MESSAGE.Type.Information )
-- MessageRED = MESSAGE:NewType( "To the RED Players: You receive a penalty because you've killed one of your own units", MESSAGE.Type.Information )
-- MessageClient1 = MESSAGE:NewType( "Congratulations, you've just hit a target", MESSAGE.Type.Update )
-- MessageClient2 = MESSAGE:NewType( "Congratulations, you've just killed a target", MESSAGE.Type.Update )
function MESSAGE:NewType( MessageText, MessageType )
local self = BASE:Inherit( self, BASE:New() )
self:F( { MessageText } )
self.MessageType = MessageType
self.MessageTime = timer.getTime()
self.MessageText = MessageText:gsub("^\n","",1):gsub("\n$","",1)
return self
end
--- Sends a MESSAGE to a Client Group. Note that the Group needs to be defined within the ME with the skillset "Client" or "Player".
-- @param #MESSAGE self
-- @param Wrapper.Client#CLIENT Client is the Group of the Client.
@ -115,14 +159,22 @@ end
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" )
-- MessageClient1:ToClient( ClientGroup )
-- MessageClient2:ToClient( ClientGroup )
function MESSAGE:ToClient( Client )
function MESSAGE:ToClient( Client, Settings )
self:F( Client )
if Client and Client:GetClientGroupID() then
local ClientGroupID = Client:GetClientGroupID()
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration )
if self.MessageType then
local Settings = Settings or ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
self.MessageCategory = "" -- self.MessageType .. ": "
end
if self.MessageDuration ~= 0 then
local ClientGroupID = Client:GetClientGroupID()
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration )
end
end
return self
@ -132,13 +184,21 @@ end
-- @param #MESSAGE self
-- @param Wrapper.Group#GROUP Group is the Group.
-- @return #MESSAGE
function MESSAGE:ToGroup( Group )
function MESSAGE:ToGroup( Group, Settings )
self:F( Group.GroupName )
if Group then
if self.MessageType then
local Settings = Settings or ( Group and _DATABASE:GetPlayerSettings( Group:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
self.MessageCategory = "" -- self.MessageType .. ": "
end
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration )
if self.MessageDuration ~= 0 then
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration )
end
end
return self
@ -193,12 +253,20 @@ end
-- or
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
-- MessageRED:ToCoalition( coalition.side.RED )
function MESSAGE:ToCoalition( CoalitionSide )
function MESSAGE:ToCoalition( CoalitionSide, Settings )
self:F( CoalitionSide )
if self.MessageType then
local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
self.MessageCategory = "" -- self.MessageType .. ": "
end
if CoalitionSide then
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
trigger.action.outTextForCoalition( CoalitionSide, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration )
if self.MessageDuration ~= 0 then
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
trigger.action.outTextForCoalition( CoalitionSide, self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration )
end
end
return self
@ -232,8 +300,16 @@ end
function MESSAGE:ToAll()
self:F()
self:ToCoalition( coalition.side.RED )
self:ToCoalition( coalition.side.BLUE )
if self.MessageType then
local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
self.MessageCategory = "" -- self.MessageType .. ": "
end
if self.MessageDuration ~= 0 then
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
trigger.action.outText( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration )
end
return self
end
@ -245,8 +321,7 @@ end
function MESSAGE:ToAllIf( Condition )
if Condition and Condition == true then
self:ToCoalition( coalition.side.RED )
self:ToCoalition( coalition.side.BLUE )
self:ToAll()
end
return self

View File

@ -54,8 +54,8 @@ do -- COORDINATE
--
-- A COORDINATE can prepare waypoints for Ground and Air groups to be embedded into a Route.
--
-- * @{#COORDINATE.RoutePointAir}(): Build an air route point.
-- * @{#COORDINATE.RoutePointGround}(): Build a ground route point.
-- * @{#COORDINATE.WaypointAir}(): Build an air route point.
-- * @{#COORDINATE.WaypointGround}(): Build a ground route point.
--
-- Route points can be used in the Route methods of the @{Group#GROUP} class.
--
@ -90,6 +90,18 @@ do -- COORDINATE
-- * @{#COORDINATE.IlluminationBomb}(): To illuminate the point.
--
--
-- ## Markings
--
-- Place markers (text boxes with clarifications for briefings, target locations or any other reference point) on the map for all players, coalitions or specific groups:
--
-- * @{#COORDINATE.MarkToAll}(): Place a mark to all players.
-- * @{#COORDINATE.MarkToCoalition}(): Place a mark to a coalition.
-- * @{#COORDINATE.MarkToCoalitionRed}(): Place a mark to the red coalition.
-- * @{#COORDINATE.MarkToCoalitionBlue}(): Place a mark to the blue coalition.
-- * @{#COORDINATE.MarkToGroup}(): Place a mark to a group (needs to have a client in it or a CA group (CA group is bugged)).
-- * @{#COORDINATE.RemoveMark}(): Removes a mark from the map.
--
--
-- ## 3D calculation methods
--
-- Various calculation methods exist to use or manipulate 3D space. Find below a short description of each method:
@ -138,6 +150,31 @@ do -- COORDINATE
ClassName = "COORDINATE",
}
--- @field COORDINATE.WaypointAltType
COORDINATE.WaypointAltType = {
BARO = "BARO",
RADIO = "RADIO",
}
--- @field COORDINATE.WaypointAction
COORDINATE.WaypointAction = {
TurningPoint = "Turning Point",
FlyoverPoint = "Fly Over Point",
FromParkingArea = "From Parking Area",
FromParkingAreaHot = "From Parking Area Hot",
FromRunway = "From Runway",
Landing = "Landing",
}
--- @field COORDINATE.WaypointType
COORDINATE.WaypointType = {
TakeOffParking = "TakeOffParking",
TakeOffParkingHot = "TakeOffParkingHot",
TakeOff = "TakeOffParkingHot",
TurningPoint = "Turning Point",
Land = "Land",
}
--- COORDINATE constructor.
-- @param #COORDINATE self
@ -279,11 +316,55 @@ do -- COORDINATE
return RandomVec3
end
--- Return the height of the land at the coordinate.
-- @param #COORDINATE self
-- @return #number
function COORDINATE:GetLandHeight()
local Vec2 = { x = self.x, y = self.z }
return land.getHeight( Vec2 )
end
--- Set the heading of the coordinate, if applicable.
-- @param #COORDINATE self
function COORDINATE:SetHeading( Heading )
self.Heading = Heading
end
--- Get the heading of the coordinate, if applicable.
-- @param #COORDINATE self
-- @return #number or nil
function COORDINATE:GetHeading()
return self.Heading
end
--- Set the velocity of the COORDINATE.
-- @param #COORDINATE self
-- @param #string Velocity Velocity in meters per second.
function COORDINATE:SetVelocity( Velocity )
self.Velocity = Velocity
end
--- Return the velocity of the COORDINATE.
-- @param #COORDINATE self
-- @return #number Velocity in meters per second.
function COORDINATE:GetVelocity()
local Velocity = self.Velocity
return Velocity or 0
end
--- Return velocity text of the COORDINATE.
-- @param #COORDINATE self
-- @return #string
function COORDINATE:GetMovingText( Settings )
return self:GetVelocityText( Settings ) .. ", " .. self:GetHeadingText( Settings )
end
--- Return a direction vector Vec3 from COORDINATE to the COORDINATE.
@ -294,6 +375,7 @@ do -- COORDINATE
return { x = TargetCoordinate.x - self.x, y = TargetCoordinate.y - self.y, z = TargetCoordinate.z - self.z }
end
--- Get a correction in radians of the real magnetic north of the COORDINATE.
-- @param #COORDINATE self
-- @return #number CorrectionRadians The correction in radians.
@ -339,6 +421,7 @@ do -- COORDINATE
return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5
end
--- Return the 3D distance in meters between the target COORDINATE and the COORDINATE.
-- @param #COORDINATE self
-- @param #COORDINATE TargetCoordinate The target COORDINATE.
@ -405,6 +488,38 @@ do -- COORDINATE
end
--- Return the velocity text of the COORDINATE.
-- @param #COORDINATE self
-- @return #string Velocity text.
function COORDINATE:GetVelocityText( Settings )
local Velocity = self:GetVelocity()
local Settings = Settings or _SETTINGS
if Velocity then
if Settings:IsMetric() then
return string.format( " moving at %d km/h", UTILS.MpsToKmph( Velocity ) )
else
return string.format( " moving at %d mi/h", UTILS.MpsToKmph( Velocity ) / 1.852 )
end
else
return " stationary"
end
end
--- Return the heading text of the COORDINATE.
-- @param #COORDINATE self
-- @return #string Heading text.
function COORDINATE:GetHeadingText( Settings )
local Heading = self:GetHeading()
if Heading then
return string.format( " bearing %3d°", Heading )
else
return " bearing unknown"
end
end
--- Provides a Bearing / Range string
-- @param #COORDINATE self
-- @param #number AngleRadians The angle in randians
@ -463,25 +578,25 @@ do -- COORDINATE
--- Build an air type route point.
-- @param #COORDINATE self
-- @param #COORDINATE.RoutePointAltType AltType The altitude type.
-- @param #COORDINATE.RoutePointType Type The route point type.
-- @param #COORDINATE.RoutePointAction Action The route point action.
-- @param #COORDINATE.WaypointAltType AltType The altitude type.
-- @param #COORDINATE.WaypointType Type The route point type.
-- @param #COORDINATE.WaypointAction Action The route point action.
-- @param Dcs.DCSTypes#Speed Speed Airspeed in km/h.
-- @param #boolean SpeedLocked true means the speed is locked.
-- @return #table The route point.
function COORDINATE:RoutePointAir( AltType, Type, Action, Speed, SpeedLocked )
function COORDINATE:WaypointAir( AltType, Type, Action, Speed, SpeedLocked )
self:F2( { AltType, Type, Action, Speed, SpeedLocked } )
local RoutePoint = {}
RoutePoint.x = self.x
RoutePoint.y = self.z
RoutePoint.alt = self.y
RoutePoint.alt_type = AltType
RoutePoint.alt_type = AltType or "RADIO"
RoutePoint.type = Type
RoutePoint.action = Action
RoutePoint.type = Type or nil
RoutePoint.action = Action or nil
RoutePoint.speed = Speed / 3.6
RoutePoint.speed = ( Speed and Speed / 3.6 ) or ( 500 / 3.6 )
RoutePoint.speed_locked = true
-- ["task"] =
@ -505,12 +620,81 @@ do -- COORDINATE
return RoutePoint
end
--- Build a Waypoint Air "Turning Point".
-- @param #COORDINATE self
-- @param #COORDINATE.WaypointAltType AltType The altitude type.
-- @param Dcs.DCSTypes#Speed Speed Airspeed in km/h.
-- @return #table The route point.
function COORDINATE:WaypointAirTurningPoint( AltType, Speed )
return self:WaypointAir( AltType, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, Speed )
end
--- Build a Waypoint Air "Fly Over Point".
-- @param #COORDINATE self
-- @param #COORDINATE.WaypointAltType AltType The altitude type.
-- @param Dcs.DCSTypes#Speed Speed Airspeed in km/h.
-- @return #table The route point.
function COORDINATE:WaypointAirFlyOverPoint( AltType, Speed )
return self:WaypointAir( AltType, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.FlyoverPoint, Speed )
end
--- Build a Waypoint Air "Take Off Parking Hot".
-- @param #COORDINATE self
-- @param #COORDINATE.WaypointAltType AltType The altitude type.
-- @param Dcs.DCSTypes#Speed Speed Airspeed in km/h.
-- @return #table The route point.
function COORDINATE:WaypointAirTakeOffParkingHot( AltType, Speed )
return self:WaypointAir( AltType, COORDINATE.WaypointType.TakeOffParkingHot, COORDINATE.WaypointAction.FromParkingAreaHot, Speed )
end
--- Build a Waypoint Air "Take Off Parking".
-- @param #COORDINATE self
-- @param #COORDINATE.WaypointAltType AltType The altitude type.
-- @param Dcs.DCSTypes#Speed Speed Airspeed in km/h.
-- @return #table The route point.
function COORDINATE:WaypointAirTakeOffParking( AltType, Speed )
return self:WaypointAir( AltType, COORDINATE.WaypointType.TakeOffParking, COORDINATE.WaypointAction.FromParkingArea, Speed )
end
--- Build a Waypoint Air "Take Off Runway".
-- @param #COORDINATE self
-- @param #COORDINATE.WaypointAltType AltType The altitude type.
-- @param Dcs.DCSTypes#Speed Speed Airspeed in km/h.
-- @return #table The route point.
function COORDINATE:WaypointAirTakeOffRunway( AltType, Speed )
return self:WaypointAir( AltType, COORDINATE.WaypointType.TakeOff, COORDINATE.WaypointAction.FromRunway, Speed )
end
--- Build a Waypoint Air "Landing".
-- @param #COORDINATE self
-- @param Dcs.DCSTypes#Speed Speed Airspeed in km/h.
-- @return #table The route point.
-- @usage
--
-- LandingZone = ZONE:New( "LandingZone" )
-- LandingCoord = LandingZone:GetCoordinate()
-- LandingWaypoint = LandingCoord:WaypointAirLanding( 60 )
-- HeliGroup:Route( { LandWaypoint }, 1 ) -- Start landing the helicopter in one second.
--
function COORDINATE:WaypointAirLanding( Speed )
return self:WaypointAir( nil, COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, Speed )
end
--- Build an ground type route point.
-- @param #COORDINATE self
-- @param Dcs.DCSTypes#Speed Speed Speed in km/h.
-- @param #COORDINATE.RoutePointAction Formation The route point Formation.
-- @param #number Speed (optional) Speed in km/h. The default speed is 999 km/h.
-- @param #string Formation (optional) The route point Formation, which is a text string that specifies exactly the Text in the Type of the route point, like "Vee", "Echelon Right".
-- @return #table The route point.
function COORDINATE:RoutePointGround( Speed, Formation )
function COORDINATE:WaypointGround( Speed, Formation )
self:F2( { Formation, Speed } )
local RoutePoint = {}
@ -520,7 +704,7 @@ do -- COORDINATE
RoutePoint.action = Formation or ""
RoutePoint.speed = Speed / 3.6
RoutePoint.speed = ( Speed or 999 ) / 3.6
RoutePoint.speed_locked = true
-- ["task"] =
@ -642,6 +826,88 @@ do -- COORDINATE
self:F2( Azimuth )
self:Flare( FLARECOLOR.Red, Azimuth )
end
do -- Markings
--- Mark to All
-- @param #COORDINATE self
-- @param #string MarkText Free format text that shows the marking clarification.
-- @return #number The resulting Mark ID which is a number.
-- @usage
-- local TargetCoord = TargetGroup:GetCoordinate()
-- local MarkID = TargetCoord:MarkToAll( "This is a target for all players" )
function COORDINATE:MarkToAll( MarkText )
local MarkID = UTILS.GetMarkID()
trigger.action.markToAll( MarkID, MarkText, self:GetVec3() )
return MarkID
end
--- Mark to Coalition
-- @param #COORDINATE self
-- @param #string MarkText Free format text that shows the marking clarification.
-- @param Coalition
-- @return #number The resulting Mark ID which is a number.
-- @usage
-- local TargetCoord = TargetGroup:GetCoordinate()
-- local MarkID = TargetCoord:MarkToCoalition( "This is a target for the red coalition", coalition.side.RED )
function COORDINATE:MarkToCoalition( MarkText, Coalition )
local MarkID = UTILS.GetMarkID()
trigger.action.markToCoalition( MarkID, MarkText, self:GetVec3(), Coalition )
return MarkID
end
--- Mark to Red Coalition
-- @param #COORDINATE self
-- @param #string MarkText Free format text that shows the marking clarification.
-- @return #number The resulting Mark ID which is a number.
-- @usage
-- local TargetCoord = TargetGroup:GetCoordinate()
-- local MarkID = TargetCoord:MarkToCoalitionRed( "This is a target for the red coalition" )
function COORDINATE:MarkToCoalitionRed( MarkText )
return self:MarkToCoalition( MarkText, coalition.side.RED )
end
--- Mark to Blue Coalition
-- @param #COORDINATE self
-- @param #string MarkText Free format text that shows the marking clarification.
-- @return #number The resulting Mark ID which is a number.
-- @usage
-- local TargetCoord = TargetGroup:GetCoordinate()
-- local MarkID = TargetCoord:MarkToCoalitionBlue( "This is a target for the blue coalition" )
function COORDINATE:MarkToCoalitionBlue( MarkText )
return self:MarkToCoalition( MarkText, coalition.side.BLUE )
end
--- Mark to Group
-- @param #COORDINATE self
-- @param #string MarkText Free format text that shows the marking clarification.
-- @param Wrapper.Group#GROUP MarkGroup The @{Group} that receives the mark.
-- @return #number The resulting Mark ID which is a number.
-- @usage
-- local TargetCoord = TargetGroup:GetCoordinate()
-- local MarkGroup = GROUP:FindByName( "AttackGroup" )
-- local MarkID = TargetCoord:MarkToGroup( "This is a target for the attack group", AttackGroup )
function COORDINATE:MarkToGroup( MarkText, MarkGroup )
local MarkID = UTILS.GetMarkID()
trigger.action.markToGroup( MarkID, MarkText, self:GetVec3(), MarkGroup:GetID() )
return MarkID
end
--- Remove a mark
-- @param #COORDINATE self
-- @param #number MarkID The ID of the mark to be removed.
-- @usage
-- local TargetCoord = TargetGroup:GetCoordinate()
-- local MarkGroup = GROUP:FindByName( "AttackGroup" )
-- local MarkID = TargetCoord:MarkToGroup( "This is a target for the attack group", AttackGroup )
-- <<< logic >>>
-- RemoveMark( MarkID ) -- The mark is now removed
function COORDINATE:RemoveMark( MarkID )
trigger.action.removeMark( MarkID )
end
end -- Markings
--- Returns if a Coordinate has Line of Sight (LOS) with the ToCoordinate.
-- @param #COORDINATE self
@ -662,6 +928,39 @@ do -- COORDINATE
end
--- Returns if a Coordinate is in a certain Radius of this Coordinate in 2D plane using the X and Z axis.
-- @param #COORDINATE self
-- @param #COORDINATE ToCoordinate The coordinate that will be tested if it is in the radius of this coordinate.
-- @param #number Radius The radius of the circle on the 2D plane around this coordinate.
-- @return #boolean true if in the Radius.
function COORDINATE:IsInRadius( Coordinate, Radius )
local InVec2 = self:GetVec2()
local Vec2 = Coordinate:GetVec2()
local InRadius = UTILS.IsInRadius( InVec2, Vec2, Radius)
return InRadius
end
--- Returns if a Coordinate is in a certain radius of this Coordinate in 3D space using the X, Y and Z axis.
-- So Radius defines the radius of the a Sphere in 3D space around this coordinate.
-- @param #COORDINATE self
-- @param #COORDINATE ToCoordinate The coordinate that will be tested if it is in the radius of this coordinate.
-- @param #number Radius The radius of the sphere in the 3D space around this coordinate.
-- @return #boolean true if in the Sphere.
function COORDINATE:IsInSphere( Coordinate, Radius )
local InVec3 = self:GetVec3()
local Vec3 = Coordinate:GetVec3()
local InSphere = UTILS.IsInSphere( InVec3, Vec3, Radius)
return InSphere
end
--- Return a BR string from a COORDINATE to the COORDINATE.
-- @param #COORDINATE self
-- @param #COORDINATE TargetCoordinate The target COORDINATE.
@ -725,16 +1024,26 @@ do -- COORDINATE
return ""
end
--- Provides a Lat Lon string
--- Provides a Lat Lon string in Degree Minute Second format.
-- @param #COORDINATE self
-- @param Core.Settings#SETTINGS Settings (optional) Settings
-- @return #string The LL Text
function COORDINATE:ToStringLL( Settings ) --R2.1 Fixes issue #424.
-- @return #string The LL DMS Text
function COORDINATE:ToStringLLDMS( Settings )
local LL_Accuracy = Settings and Settings.LL_Accuracy or _SETTINGS.LL_Accuracy
local LL_DMS = Settings and Settings.LL_DMS or _SETTINGS.LL_DMS
local lat, lon = coord.LOtoLL( self:GetVec3() )
return "LL, " .. UTILS.tostringLL( lat, lon, LL_Accuracy, LL_DMS )
return "LL DMS, " .. UTILS.tostringLL( lat, lon, LL_Accuracy, true )
end
--- Provides a Lat Lon string in Degree Decimal Minute format.
-- @param #COORDINATE self
-- @param Core.Settings#SETTINGS Settings (optional) Settings
-- @return #string The LL DDM Text
function COORDINATE:ToStringLLDDM( Settings )
local LL_Accuracy = Settings and Settings.LL_Accuracy or _SETTINGS.LL_Accuracy
local lat, lon = coord.LOtoLL( self:GetVec3() )
return "LL DDM, " .. UTILS.tostringLL( lat, lon, LL_Accuracy, false )
end
--- Provides a MGRS string
@ -780,44 +1089,124 @@ do -- COORDINATE
end
--- Provides a coordinate string of the point, based on the A2G coordinate format system.
-- @param #COORDINATE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable
-- @param Core.Settings#SETTINGS Settings
-- @return #string The coordinate Text in the configured coordinate system.
function COORDINATE:ToStringA2G( Controllable, Settings ) -- R2.2
self:F( { Controllable = Controllable and Controllable:GetName() } )
local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS
if Settings:IsA2G_BR() then
-- If no Controllable is given to calculate the BR from, then MGRS will be used!!!
if Controllable then
local Coordinate = Controllable:GetCoordinate()
return Controllable and self:ToStringBR( Coordinate, Settings ) or self:ToStringMGRS( Settings )
else
return self:ToStringMGRS( Settings )
end
end
if Settings:IsA2G_LL_DMS() then
return self:ToStringLLDMS( Settings )
end
if Settings:IsA2G_LL_DDM() then
return self:ToStringLLDDM( Settings )
end
if Settings:IsA2G_MGRS() then
return self:ToStringMGRS( Settings )
end
return nil
end
--- Provides a coordinate string of the point, based on the A2A coordinate format system.
-- @param #COORDINATE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable
-- @param Core.Settings#SETTINGS Settings
-- @return #string The coordinate Text in the configured coordinate system.
function COORDINATE:ToStringA2A( Controllable, Settings ) -- R2.2
self:F( { Controllable = Controllable and Controllable:GetName() } )
local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS
if Settings:IsA2A_BRAA() then
if Controllable then
local Coordinate = Controllable:GetCoordinate()
return self:ToStringBRA( Coordinate, Settings )
else
return self:ToStringMGRS( Settings )
end
end
if Settings:IsA2A_BULLS() then
local Coalition = Controllable:GetCoalition()
return self:ToStringBULLS( Coalition, Settings )
end
if Settings:IsA2A_LL_DMS() then
return self:ToStringLLDMS( Settings )
end
if Settings:IsA2A_LL_DDM() then
return self:ToStringLLDDM( Settings )
end
if Settings:IsA2A_MGRS() then
return self:ToStringMGRS( Settings )
end
return nil
end
--- Provides a coordinate string of the point, based on a coordinate format system:
-- * Uses default settings in COORDINATE.
-- * Can be overridden if for a GROUP containing x clients, a menu was selected to override the default.
-- @param #COORDINATE self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable
-- @param Core.Settings#SETTINGS Settings
-- @param Tasking.Task#TASK Task The task for which coordinates need to be calculated.
-- @return #string The coordinate Text in the configured coordinate system.
function COORDINATE:ToString( Controllable, Settings ) -- R2.2
function COORDINATE:ToString( Controllable, Settings, Task ) -- R2.2
self:E( { Controllable = Controllable } )
self:F( { Controllable = Controllable and Controllable:GetName() } )
local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS
local IsAir = Controllable and Controllable:IsAirPlane() or false
if IsAir then
if Settings:IsA2A_BRA() then
local Coordinate = Controllable:GetCoordinate()
return self:ToStringBRA( Coordinate, Settings )
end
if Settings:IsA2A_BULLS() then
local Coalition = Controllable:GetCoalition()
return self:ToStringBULLS( Coalition, Settings )
local ModeA2A = false
if Task then
if Task:IsInstanceOf( TASK_A2A ) then
ModeA2A = true
else
if Task:IsInstanceOf( TASK_A2G ) then
ModeA2A = false
else
if Task:IsInstanceOf( TASK_CARGO ) then
ModeA2A = false
else
ModeA2A = false
end
end
end
else
if Settings:IsA2G_BRA() then
local Coordinate = Controllable:GetCoordinate()
return Controllable and self:ToStringBR( Coordinate, Settings ) or self:ToStringMGRS( Settings )
end
if Settings:IsA2G_LL() then
return self:ToStringLL( Settings )
end
if Settings:IsA2G_MGRS() then
return self:ToStringMGRS( Settings )
local IsAir = Controllable and Controllable:IsAirPlane() or false
if IsAir then
ModeA2A = true
else
ModeA2A = false
end
end
if ModeA2A == true then
return self:ToStringA2A( Controllable, Settings )
else
return self:ToStringA2G( Controllable, Settings )
end
return nil
end

View File

@ -0,0 +1,86 @@
--- The REPORT class
-- @type REPORT
-- @extends Core.Base#BASE
REPORT = {
ClassName = "REPORT",
Title = "",
}
--- Create a new REPORT.
-- @param #REPORT self
-- @param #string Title
-- @return #REPORT
function REPORT:New( Title )
local self = BASE:Inherit( self, BASE:New() ) -- #REPORT
self.Report = {}
self:SetTitle( Title or "" )
self:SetIndent( 3 )
return self
end
--- Has the REPORT Text?
-- @param #REPORT self
-- @return #boolean
function REPORT:HasText() --R2.1
return #self.Report > 0
end
--- Set indent of a REPORT.
-- @param #REPORT self
-- @param #number Indent
-- @return #REPORT
function REPORT:SetIndent( Indent ) --R2.1
self.Indent = Indent
return self
end
--- Add a new line to a REPORT.
-- @param #REPORT self
-- @param #string Text
-- @return #REPORT
function REPORT:Add( Text )
self.Report[#self.Report+1] = Text
return self
end
--- Add a new line to a REPORT.
-- @param #REPORT self
-- @param #string Text
-- @return #REPORT
function REPORT:AddIndent( Text ) --R2.1
self.Report[#self.Report+1] = string.rep(" ", self.Indent ) .. Text:gsub("\n","\n"..string.rep( " ", self.Indent ) )
return self
end
--- Produces the text of the report, taking into account an optional delimeter, which is \n by default.
-- @param #REPORT self
-- @param #string Delimiter (optional) A delimiter text.
-- @return #string The report text.
function REPORT:Text( Delimiter )
Delimiter = Delimiter or "\n"
local ReportText = ( self.Title ~= "" and self.Title .. Delimiter or self.Title ) .. table.concat( self.Report, Delimiter ) or ""
return ReportText
end
--- Sets the title of the report.
-- @param #REPORT self
-- @param #string Title The title of the report.
-- @return #REPORT
function REPORT:SetTitle( Title )
self.Title = Title
return self
end
--- Gets the amount of report items contained in the report.
-- @param #REPORT self
-- @return #number Returns the number of report items contained in the report. 0 is returned if no report items are contained in the report. The title is not counted for.
function REPORT:GetCount()
return #self.Report
end

View File

@ -55,6 +55,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
self:F2( { Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop } )
self.CallID = self.CallID + 1
local CallID = self.CallID .. "#" .. ( Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "" ) or ""
-- Initialize the ObjectSchedulers array, which is a weakly coupled table.
-- If the object used as the key is nil, then the garbage collector will remove the item from the Functions array.
@ -65,27 +66,27 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } )
if Scheduler.MasterObject then
self.ObjectSchedulers[self.CallID] = Scheduler
self:F3( { CallID = self.CallID, ObjectScheduler = tostring(self.ObjectSchedulers[self.CallID]), MasterObject = tostring(Scheduler.MasterObject) } )
self.ObjectSchedulers[CallID] = Scheduler
self:F3( { CallID = CallID, ObjectScheduler = tostring(self.ObjectSchedulers[CallID]), MasterObject = tostring(Scheduler.MasterObject) } )
else
self.PersistentSchedulers[self.CallID] = Scheduler
self:F3( { CallID = self.CallID, PersistentScheduler = self.PersistentSchedulers[self.CallID] } )
self.PersistentSchedulers[CallID] = Scheduler
self:F3( { CallID = CallID, PersistentScheduler = self.PersistentSchedulers[CallID] } )
end
self.Schedule = self.Schedule or setmetatable( {}, { __mode = "k" } )
self.Schedule[Scheduler] = self.Schedule[Scheduler] or {}
self.Schedule[Scheduler][self.CallID] = {}
self.Schedule[Scheduler][self.CallID].Function = ScheduleFunction
self.Schedule[Scheduler][self.CallID].Arguments = ScheduleArguments
self.Schedule[Scheduler][self.CallID].StartTime = timer.getTime() + ( Start or 0 )
self.Schedule[Scheduler][self.CallID].Start = Start + .1
self.Schedule[Scheduler][self.CallID].Repeat = Repeat
self.Schedule[Scheduler][self.CallID].Randomize = Randomize
self.Schedule[Scheduler][self.CallID].Stop = Stop
self.Schedule[Scheduler][CallID] = {}
self.Schedule[Scheduler][CallID].Function = ScheduleFunction
self.Schedule[Scheduler][CallID].Arguments = ScheduleArguments
self.Schedule[Scheduler][CallID].StartTime = timer.getTime() + ( Start or 0 )
self.Schedule[Scheduler][CallID].Start = Start + .1
self.Schedule[Scheduler][CallID].Repeat = Repeat or 0
self.Schedule[Scheduler][CallID].Randomize = Randomize or 0
self.Schedule[Scheduler][CallID].Stop = Stop
self:T3( self.Schedule[Scheduler][self.CallID] )
self:T3( self.Schedule[Scheduler][CallID] )
self.Schedule[Scheduler][self.CallID].CallHandler = function( CallID )
self.Schedule[Scheduler][CallID].CallHandler = function( CallID )
self:F2( CallID )
local ErrorHandler = function( errmsg )
@ -100,11 +101,12 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
if not Scheduler then
Scheduler = self.PersistentSchedulers[CallID]
end
--self:T3( { Scheduler = Scheduler } )
if Scheduler then
local MasterObject = tostring(Scheduler.MasterObject)
local Schedule = self.Schedule[Scheduler][CallID]
--self:T3( { Schedule = Schedule } )
@ -133,10 +135,13 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
end
local CurrentTime = timer.getTime()
local StartTime = CurrentTime + Start
local StartTime = Schedule.StartTime
self:F3( { Master = MasterObject, CurrentTime = CurrentTime, StartTime = StartTime, Start = Start, Repeat = Repeat, Randomize = Randomize, Stop = Stop } )
if Status and (( Result == nil ) or ( Result and Result ~= false ) ) then
if Repeat ~= 0 and ( Stop == 0 ) or ( Stop ~= 0 and CurrentTime <= StartTime + Stop ) then
if Repeat ~= 0 and ( ( Stop == 0 ) or ( Stop ~= 0 and CurrentTime <= StartTime + Stop ) ) then
local ScheduleTime =
CurrentTime +
Repeat +
@ -160,9 +165,9 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
return nil
end
self:Start( Scheduler, self.CallID )
self:Start( Scheduler, CallID )
return self.CallID
return CallID
end
function SCHEDULEDISPATCHER:RemoveSchedule( Scheduler, CallID )
@ -182,10 +187,11 @@ function SCHEDULEDISPATCHER:Start( Scheduler, CallID )
-- Only start when there is no ScheduleID defined!
-- This prevents to "Start" the scheduler twice with the same CallID...
if not Schedule[CallID].ScheduleID then
Schedule[CallID].StartTime = timer.getTime() -- Set the StartTime field to indicate when the scheduler started.
Schedule[CallID].ScheduleID = timer.scheduleFunction(
Schedule[CallID].CallHandler,
CallID,
timer.getTime() + Schedule[CallID].Start
timer.getTime() + Schedule[CallID].Start
)
end
else

View File

@ -210,7 +210,8 @@ SCHEDULER = {
-- @return #SCHEDULER self.
-- @return #number The ScheduleID of the planned schedule.
function SCHEDULER:New( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop )
local self = BASE:Inherit( self, BASE:New() )
local self = BASE:Inherit( self, BASE:New() ) -- #SCHEDULER
self:F2( { Start, Repeat, RandomizeFactor, Stop } )
local ScheduleID = nil

File diff suppressed because it is too large Load Diff

View File

@ -55,11 +55,15 @@ do -- SETTINGS
if PlayerName == nil then
local self = BASE:Inherit( self, BASE:New() ) -- #SETTINGS
self:SetMetric() -- Defaults
self:SetA2G_MGRS() -- Defaults
self:SetA2A_BRA() -- Defaults
self:SetLL_Accuracy( 2 ) -- Defaults
self:SetLL_DMS( true ) -- Defaults
self:SetA2G_BR() -- Defaults
self:SetA2A_BRAA() -- Defaults
self:SetLL_Accuracy( 3 ) -- Defaults
self:SetMGRS_Accuracy( 5 ) -- Defaults
self:SetMessageTime( MESSAGE.Type.Briefing, 180 )
self:SetMessageTime( MESSAGE.Type.Detailed, 60 )
self:SetMessageTime( MESSAGE.Type.Information, 30 )
self:SetMessageTime( MESSAGE.Type.Overview, 60 )
self:SetMessageTime( MESSAGE.Type.Update, 15 )
return self
else
local Settings = _DATABASE:GetPlayerSettings( PlayerName )
@ -82,7 +86,6 @@ do -- SETTINGS
-- @param #SETTINGS self
-- @return #boolean true if metric.
function SETTINGS:IsMetric()
self:E( {Metric = ( self.Metric ~= nil and self.Metric == true ) or ( self.Metric == nil and _SETTINGS:IsMetric() ) } )
return ( self.Metric ~= nil and self.Metric == true ) or ( self.Metric == nil and _SETTINGS:IsMetric() )
end
@ -96,7 +99,6 @@ do -- SETTINGS
-- @param #SETTINGS self
-- @return #boolean true if imperial.
function SETTINGS:IsImperial()
self:E( {Metric = ( self.Metric ~= nil and self.Metric == false ) or ( self.Metric == nil and _SETTINGS:IsMetric() ) } )
return ( self.Metric ~= nil and self.Metric == false ) or ( self.Metric == nil and _SETTINGS:IsMetric() )
end
@ -111,25 +113,10 @@ do -- SETTINGS
--- Gets the SETTINGS LL accuracy.
-- @param #SETTINGS self
-- @return #number
function SETTINGS:GetLL_Accuracy()
return self.LL_Accuracy or _SETTINGS:GetLL_Accuracy()
function SETTINGS:GetLL_DDM_Accuracy()
return self.LL_DDM_Accuracy or _SETTINGS:GetLL_DDM_Accuracy()
end
--- Sets the SETTINGS LL DMS.
-- @param #SETTINGS self
-- @param #number LL_DMS
-- @return #SETTINGS
function SETTINGS:SetLL_DMS( LL_DMS )
self.LL_DMS = LL_DMS
end
--- Gets the SETTINGS LL DMS.
-- @param #SETTINGS self
-- @return #number
function SETTINGS:GetLL_DMS()
return self.LL_DMS or _SETTINGS:GetLL_DMS()
end
--- Sets the SETTINGS MGRS accuracy.
-- @param #SETTINGS self
-- @param #number MGRS_Accuracy
@ -145,21 +132,50 @@ do -- SETTINGS
return self.MGRS_Accuracy or _SETTINGS:GetMGRS_Accuracy()
end
--- Sets A2G LL
--- Sets the SETTINGS Message Display Timing of a MessageType
-- @param #SETTINGS self
-- @return #SETTINGS
function SETTINGS:SetA2G_LL()
self.A2GSystem = "LL"
-- @param Core.Message#MESSAGE MessageType The type of the message.
-- @param #number MessageTime The display time duration in seconds of the MessageType.
function SETTINGS:SetMessageTime( MessageType, MessageTime )
self.MessageTypeTimings = self.MessageTypeTimings or {}
self.MessageTypeTimings[MessageType] = MessageTime
end
--- Gets the SETTINGS Message Display Timing of a MessageType
-- @param #SETTINGS self
-- @param Core.Message#MESSAGE MessageType The type of the message.
-- @return #number
function SETTINGS:GetMessageTime( MessageType )
return ( self.MessageTypeTimings and self.MessageTypeTimings[MessageType] ) or _SETTINGS:GetMessageTime( MessageType )
end
--- Is LL
--- Sets A2G LL DMS
-- @param #SETTINGS self
-- @return #boolean true if LL
function SETTINGS:IsA2G_LL()
return ( self.A2GSystem and self.A2GSystem == "LL" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_LL() )
-- @return #SETTINGS
function SETTINGS:SetA2G_LL_DMS()
self.A2GSystem = "LL DMS"
end
--- Sets A2G LL DDM
-- @param #SETTINGS self
-- @return #SETTINGS
function SETTINGS:SetA2G_LL_DDM()
self.A2GSystem = "LL DDM"
end
--- Is LL DMS
-- @param #SETTINGS self
-- @return #boolean true if LL DMS
function SETTINGS:IsA2G_LL_DMS()
return ( self.A2GSystem and self.A2GSystem == "LL DMS" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS() )
end
--- Is LL DDM
-- @param #SETTINGS self
-- @return #boolean true if LL DDM
function SETTINGS:IsA2G_LL_DDM()
return ( self.A2GSystem and self.A2GSystem == "LL DDM" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM() )
end
--- Sets A2G MGRS
@ -179,31 +195,30 @@ do -- SETTINGS
--- Sets A2G BRA
-- @param #SETTINGS self
-- @return #SETTINGS
function SETTINGS:SetA2G_BRA()
self.A2GSystem = "BRA"
function SETTINGS:SetA2G_BR()
self.A2GSystem = "BR"
end
--- Is BRA
-- @param #SETTINGS self
-- @return #boolean true if BRA
function SETTINGS:IsA2G_BRA()
self:E( { BRA = ( self.A2GSystem and self.A2GSystem == "BRA" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_BRA() ) } )
return ( self.A2GSystem and self.A2GSystem == "BRA" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_BRA() )
function SETTINGS:IsA2G_BR()
return ( self.A2GSystem and self.A2GSystem == "BR" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_BR() )
end
--- Sets A2A BRA
-- @param #SETTINGS self
-- @return #SETTINGS
function SETTINGS:SetA2A_BRA()
self.A2ASystem = "BRA"
function SETTINGS:SetA2A_BRAA()
self.A2ASystem = "BRAA"
end
--- Is BRA
-- @param #SETTINGS self
-- @return #boolean true if BRA
function SETTINGS:IsA2A_BRA()
self:E( { BRA = ( self.A2ASystem and self.A2ASystem == "BRA" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BRA() ) } )
return ( self.A2ASystem and self.A2ASystem == "BRA" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BRA() )
function SETTINGS:IsA2A_BRAA()
self:E( { BRA = ( self.A2ASystem and self.A2ASystem == "BRAA" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BRAA() ) } )
return ( self.A2ASystem and self.A2ASystem == "BRAA" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BRAA() )
end
--- Sets A2A BULLS
@ -220,69 +235,179 @@ do -- SETTINGS
return ( self.A2ASystem and self.A2ASystem == "BULLS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BULLS() )
end
--- Sets A2A LL DMS
-- @param #SETTINGS self
-- @return #SETTINGS
function SETTINGS:SetA2A_LL_DMS()
self.A2ASystem = "LL DMS"
end
--- Sets A2A LL DDM
-- @param #SETTINGS self
-- @return #SETTINGS
function SETTINGS:SetA2A_LL_DDM()
self.A2ASystem = "LL DDM"
end
--- Is LL DMS
-- @param #SETTINGS self
-- @return #boolean true if LL DMS
function SETTINGS:IsA2A_LL_DMS()
return ( self.A2ASystem and self.A2ASystem == "LL DMS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS() )
end
--- Is LL DDM
-- @param #SETTINGS self
-- @return #boolean true if LL DDM
function SETTINGS:IsA2A_LL_DDM()
return ( self.A2ASystem and self.A2ASystem == "LL DDM" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM() )
end
--- Sets A2A MGRS
-- @param #SETTINGS self
-- @return #SETTINGS
function SETTINGS:SetA2A_MGRS()
self.A2ASystem = "MGRS"
end
--- Is MGRS
-- @param #SETTINGS self
-- @return #boolean true if MGRS
function SETTINGS:IsA2A_MGRS()
return ( self.A2ASystem and self.A2ASystem == "MGRS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_MGRS() )
end
--- @param #SETTINGS self
-- @return #SETTINGS
function SETTINGS:SetSystemMenu( RootMenu, MenuText )
function SETTINGS:SetSystemMenu( MenuGroup, RootMenu )
MenuText = MenuText or "System Settings"
if not self.SettingsMenu then
self.SettingsMenu = MENU_MISSION:New( MenuText, RootMenu )
end
if self.DefaultMenu then
self.DefaultMenu:Remove()
self.DefaultMenu = nil
end
self.DefaultMenu = MENU_MISSION:New( "Default Settings", self.SettingsMenu )
local MenuText = "System Settings"
local A2GCoordinateMenu = MENU_MISSION:New( "A2G Coordinate System", self.DefaultMenu )
local MenuTime = timer.getTime()
local SettingsMenu = MENU_GROUP:New( MenuGroup, MenuText, RootMenu ):SetTime( MenuTime )
local A2GCoordinateMenu = MENU_GROUP:New( MenuGroup, "A2G Coordinate System", SettingsMenu ):SetTime( MenuTime )
if self:IsA2G_LL() then
MENU_MISSION_COMMAND:New( "Activate BRA", A2GCoordinateMenu, self.A2GMenuSystem, self, "BRA" )
MENU_MISSION_COMMAND:New( "Activate MGRS", A2GCoordinateMenu, self.A2GMenuSystem, self, "MGRS" )
MENU_MISSION_COMMAND:New( "LL Accuracy 1", A2GCoordinateMenu, self.MenuLL_Accuracy, self, 1 )
MENU_MISSION_COMMAND:New( "LL Accuracy 2", A2GCoordinateMenu, self.MenuLL_Accuracy, self, 2 )
MENU_MISSION_COMMAND:New( "LL Accuracy 3", A2GCoordinateMenu, self.MenuLL_Accuracy, self, 3 )
MENU_MISSION_COMMAND:New( "LL Decimal On", A2GCoordinateMenu, self.MenuLL_DMS, self, true )
MENU_MISSION_COMMAND:New( "LL Decimal Off", A2GCoordinateMenu, self.MenuLL_DMS, self, false )
if not self:IsA2G_LL_DMS() then
MENU_GROUP_COMMAND:New( MenuGroup, "Lat/Lon Degree Min Sec (LL DMS)", A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime )
end
if not self:IsA2G_LL_DDM() then
MENU_GROUP_COMMAND:New( MenuGroup, "Lat/Lon Degree Dec Min (LL DDM)", A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime )
end
if self:IsA2G_LL_DDM() then
MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 1", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 1 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 2", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 2 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 3", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 3 ):SetTime( MenuTime )
end
if not self:IsA2G_BR() then
MENU_GROUP_COMMAND:New( MenuGroup, "Bearing, Range (BR)", A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "BR" ):SetTime( MenuTime )
end
if not self:IsA2G_MGRS() then
MENU_GROUP_COMMAND:New( MenuGroup, "Military Grid (MGRS)", A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime )
end
if self:IsA2G_MGRS() then
MENU_MISSION_COMMAND:New( "Activate BRA", A2GCoordinateMenu, self.A2GMenuSystem, self, "BRA" )
MENU_MISSION_COMMAND:New( "Activate LL", A2GCoordinateMenu, self.A2GMenuSystem, self, "LL" )
MENU_MISSION_COMMAND:New( "MGRS Accuracy 1", A2GCoordinateMenu, self.MenuMGRS_Accuracy, self, 1 )
MENU_MISSION_COMMAND:New( "MGRS Accuracy 2", A2GCoordinateMenu, self.MenuMGRS_Accuracy, self, 2 )
MENU_MISSION_COMMAND:New( "MGRS Accuracy 3", A2GCoordinateMenu, self.MenuMGRS_Accuracy, self, 3 )
MENU_MISSION_COMMAND:New( "MGRS Accuracy 4", A2GCoordinateMenu, self.MenuMGRS_Accuracy, self, 4 )
MENU_MISSION_COMMAND:New( "MGRS Accuracy 5", A2GCoordinateMenu, self.MenuMGRS_Accuracy, self, 5 )
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 1", A2GCoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 1 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 2", A2GCoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 2 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 3", A2GCoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 3 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 4", A2GCoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 4 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 5", A2GCoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 5 ):SetTime( MenuTime )
end
if self:IsA2G_BRA() then
MENU_MISSION_COMMAND:New( "Activate MGRS", A2GCoordinateMenu, self.A2GMenuSystem, self, "MGRS" )
MENU_MISSION_COMMAND:New( "Activate LL", A2GCoordinateMenu, self.A2GMenuSystem, self, "LL" )
local A2ACoordinateMenu = MENU_GROUP:New( MenuGroup, "A2A Coordinate System", SettingsMenu ):SetTime( MenuTime )
if not self:IsA2A_LL_DMS() then
MENU_GROUP_COMMAND:New( MenuGroup, "Lat/Lon Degree Min Sec (LL DMS)", A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime )
end
local A2ACoordinateMenu = MENU_MISSION:New( "A2A Coordinate System", self.DefaultMenu )
if self:IsA2A_BULLS() then
MENU_MISSION_COMMAND:New( "Activate BRA", A2ACoordinateMenu, self.A2AMenuSystem, self, "BRA" )
if not self:IsA2A_LL_DDM() then
MENU_GROUP_COMMAND:New( MenuGroup, "Lat/Lon Degree Dec Min (LL DDM)", A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime )
end
if self:IsA2A_BRA() then
MENU_MISSION_COMMAND:New( "Activate BULLS", A2ACoordinateMenu, self.A2AMenuSystem, self, "BULLS" )
if self:IsA2A_LL_DDM() then
MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 1", A2ACoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 1 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 2", A2ACoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 2 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 3", A2ACoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 3 ):SetTime( MenuTime )
end
if not self:IsA2A_BULLS() then
MENU_GROUP_COMMAND:New( MenuGroup, "Bullseye (BULLS)", A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BULLS" ):SetTime( MenuTime )
end
local MetricsMenu = MENU_MISSION:New( "Measures and Weights System", self.DefaultMenu )
if not self:IsA2A_BRAA() then
MENU_GROUP_COMMAND:New( MenuGroup, "Bearing Range Altitude Aspect (BRAA)", A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BRAA" ):SetTime( MenuTime )
end
if not self:IsA2A_MGRS() then
MENU_GROUP_COMMAND:New( MenuGroup, "Military Grid (MGRS)", A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime )
end
if self:IsA2A_MGRS() then
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 1", A2ACoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 1 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 2", A2ACoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 2 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 3", A2ACoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 3 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 4", A2ACoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 4 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 5", A2ACoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 5 ):SetTime( MenuTime )
end
local MetricsMenu = MENU_GROUP:New( MenuGroup, "Measures and Weights System", SettingsMenu ):SetTime( MenuTime )
if self:IsMetric() then
MENU_MISSION_COMMAND:New( "Activate Imperial", MetricsMenu, self.MenuMWSystem, self, false )
MENU_GROUP_COMMAND:New( MenuGroup, "Imperial (Miles,Feet)", MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, false ):SetTime( MenuTime )
end
if self:IsImperial() then
MENU_MISSION_COMMAND:New( "Activate Metric", MetricsMenu, self.MenuMWSystem, self, true )
MENU_GROUP_COMMAND:New( MenuGroup, "Metric (Kilometers,Meters)", MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, true ):SetTime( MenuTime )
end
local MessagesMenu = MENU_GROUP:New( MenuGroup, "Messages and Reports", SettingsMenu ):SetTime( MenuTime )
local UpdateMessagesMenu = MENU_GROUP:New( MenuGroup, "Update Messages", MessagesMenu ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "Off", UpdateMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Update, 0 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "5 seconds", UpdateMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Update, 5 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "10 seconds", UpdateMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Update, 10 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "15 seconds", UpdateMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Update, 15 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "30 seconds", UpdateMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Update, 30 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "1 minute", UpdateMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Update, 60 ):SetTime( MenuTime )
local InformationMessagesMenu = MENU_GROUP:New( MenuGroup, "Information Messages", MessagesMenu ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "5 seconds", InformationMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Information, 5 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "10 seconds", InformationMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Information, 10 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "15 seconds", InformationMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Information, 15 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "30 seconds", InformationMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Information, 30 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "1 minute", InformationMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Information, 60 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "2 minutes", InformationMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Information, 120 ):SetTime( MenuTime )
local BriefingReportsMenu = MENU_GROUP:New( MenuGroup, "Briefing Reports", MessagesMenu ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "15 seconds", BriefingReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Briefing, 15 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "30 seconds", BriefingReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Briefing, 30 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "1 minute", BriefingReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Briefing, 60 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "2 minutes", BriefingReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Briefing, 120 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "3 minutes", BriefingReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Briefing, 180 ):SetTime( MenuTime )
local OverviewReportsMenu = MENU_GROUP:New( MenuGroup, "Overview Reports", MessagesMenu ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "15 seconds", OverviewReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Overview, 15 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "30 seconds", OverviewReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Overview, 30 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "1 minute", OverviewReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Overview, 60 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "2 minutes", OverviewReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Overview, 120 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "3 minutes", OverviewReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Overview, 180 ):SetTime( MenuTime )
local DetailedReportsMenu = MENU_GROUP:New( MenuGroup, "Detailed Reports", MessagesMenu ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "15 seconds", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 15 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "30 seconds", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 30 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "1 minute", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 60 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "2 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 120 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "3 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 180 ):SetTime( MenuTime )
SettingsMenu:Remove( MenuTime )
return self
end
@ -293,35 +418,39 @@ do -- SETTINGS
-- @return #SETTINGS
function SETTINGS:SetPlayerMenu( PlayerUnit )
local MenuText = "Player Settings"
self.MenuText = MenuText
local SettingsMenu = _SETTINGS.SettingsMenu
local PlayerGroup = PlayerUnit:GetGroup()
local PlayerName = PlayerUnit:GetPlayerName()
local PlayerNames = PlayerGroup:GetPlayerNames()
local GroupMenu = MENU_GROUP:New( PlayerGroup, MenuText, SettingsMenu )
local PlayerMenu = MENU_GROUP:New( PlayerGroup, 'Settings "' .. PlayerName .. '"', GroupMenu )
local PlayerMenu = MENU_GROUP:New( PlayerGroup, 'Settings "' .. PlayerName .. '"' )
self.PlayerMenu = PlayerMenu
local A2GCoordinateMenu = MENU_GROUP:New( PlayerGroup, "A2G Coordinate System", PlayerMenu )
if self:IsA2G_LL() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Activate BRA", A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "BRA" )
MENU_GROUP_COMMAND:New( PlayerGroup, "Activate MGRS", A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" )
MENU_GROUP_COMMAND:New( PlayerGroup, "LL Accuracy 1", A2GCoordinateMenu, self.MenuGroupLL_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 )
MENU_GROUP_COMMAND:New( PlayerGroup, "LL Accuracy 2", A2GCoordinateMenu, self.MenuGroupLL_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 2 )
MENU_GROUP_COMMAND:New( PlayerGroup, "LL Accuracy 3", A2GCoordinateMenu, self.MenuGroupLL_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 3 )
MENU_GROUP_COMMAND:New( PlayerGroup, "LL Decimal On", A2GCoordinateMenu, self.MenuGroupLL_DMSSystem, self, PlayerUnit, PlayerGroup, PlayerName, true )
MENU_GROUP_COMMAND:New( PlayerGroup, "LL Decimal Off", A2GCoordinateMenu, self.MenuGroupLL_DMSSystem, self, PlayerUnit, PlayerGroup, PlayerName, false )
if not self:IsA2G_LL_DMS() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Lat/Lon Degree Min Sec (LL DMS)", A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" )
end
if not self:IsA2G_LL_DDM() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Lat/Lon Degree Dec Min (LL DDM)", A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" )
end
if self:IsA2G_LL_DDM() then
MENU_GROUP_COMMAND:New( PlayerGroup, "LL DDM Accuracy 1", A2GCoordinateMenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 )
MENU_GROUP_COMMAND:New( PlayerGroup, "LL DDM Accuracy 2", A2GCoordinateMenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 2 )
MENU_GROUP_COMMAND:New( PlayerGroup, "LL DDM Accuracy 3", A2GCoordinateMenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 3 )
end
if not self:IsA2G_BR() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Bearing, Range (BR)", A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "BR" )
end
if not self:IsA2G_MGRS() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Military Grid (MGRS)", A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" )
end
if self:IsA2G_MGRS() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Activate BRA", A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "BRA" )
MENU_GROUP_COMMAND:New( PlayerGroup, "Activate LL", A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL" )
MENU_GROUP_COMMAND:New( PlayerGroup, "MGRS Accuracy 1", A2GCoordinateMenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 )
MENU_GROUP_COMMAND:New( PlayerGroup, "MGRS Accuracy 2", A2GCoordinateMenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 2 )
MENU_GROUP_COMMAND:New( PlayerGroup, "MGRS Accuracy 3", A2GCoordinateMenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 3 )
@ -329,30 +458,93 @@ do -- SETTINGS
MENU_GROUP_COMMAND:New( PlayerGroup, "MGRS Accuracy 5", A2GCoordinateMenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 5 )
end
if self:IsA2G_BRA() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Activate MGRS", A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" )
MENU_GROUP_COMMAND:New( PlayerGroup, "Activate LL", A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL" )
end
local A2ACoordinateMenu = MENU_GROUP:New( PlayerGroup, "A2A Coordinate System", PlayerMenu )
if self:IsA2A_BULLS() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Activate BRA", A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BRA" )
if not self:IsA2A_LL_DMS() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Lat/Lon Degree Min Sec (LL DMS)", A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" )
end
if self:IsA2A_BRA() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Activate BULLS", A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BULLS" )
if not self:IsA2A_LL_DDM() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Lat/Lon Degree Dec Min (LL DDM)", A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" )
end
if self:IsA2A_LL_DDM() then
MENU_GROUP_COMMAND:New( PlayerGroup, "LL DDM Accuracy 1", A2GCoordinateMenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 )
MENU_GROUP_COMMAND:New( PlayerGroup, "LL DDM Accuracy 2", A2GCoordinateMenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 2 )
MENU_GROUP_COMMAND:New( PlayerGroup, "LL DDM Accuracy 3", A2GCoordinateMenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 3 )
end
if not self:IsA2A_BULLS() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Bullseye (BULLS)", A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BULLS" )
end
if not self:IsA2A_BRAA() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Bearing Range Altitude Aspect (BRAA)", A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BRAA" )
end
if not self:IsA2A_MGRS() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Military Grid (MGRS)", A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" )
end
if self:IsA2A_MGRS() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Military Grid (MGRS) Accuracy 1", A2ACoordinateMenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 )
MENU_GROUP_COMMAND:New( PlayerGroup, "Military Grid (MGRS) Accuracy 2", A2ACoordinateMenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 2 )
MENU_GROUP_COMMAND:New( PlayerGroup, "Military Grid (MGRS) Accuracy 3", A2ACoordinateMenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 3 )
MENU_GROUP_COMMAND:New( PlayerGroup, "Military Grid (MGRS) Accuracy 4", A2ACoordinateMenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 4 )
MENU_GROUP_COMMAND:New( PlayerGroup, "Military Grid (MGRS) Accuracy 5", A2ACoordinateMenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 5 )
end
local MetricsMenu = MENU_GROUP:New( PlayerGroup, "Measures and Weights System", PlayerMenu )
if self:IsMetric() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Activate Imperial", MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, false )
MENU_GROUP_COMMAND:New( PlayerGroup, "Imperial (Miles,Feet)", MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, false )
end
if self:IsImperial() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Activate Metric", MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, true )
MENU_GROUP_COMMAND:New( PlayerGroup, "Metric (Kilometers,Meters)", MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, true )
end
local MessagesMenu = MENU_GROUP:New( PlayerGroup, "Messages and Reports", PlayerMenu )
local UpdateMessagesMenu = MENU_GROUP:New( PlayerGroup, "Update Messages", MessagesMenu )
MENU_GROUP_COMMAND:New( PlayerGroup, "Off", UpdateMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Update, 0 )
MENU_GROUP_COMMAND:New( PlayerGroup, "5 seconds", UpdateMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Update, 5 )
MENU_GROUP_COMMAND:New( PlayerGroup, "10 seconds", UpdateMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Update, 10 )
MENU_GROUP_COMMAND:New( PlayerGroup, "15 seconds", UpdateMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Update, 15 )
MENU_GROUP_COMMAND:New( PlayerGroup, "30 seconds", UpdateMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Update, 30 )
MENU_GROUP_COMMAND:New( PlayerGroup, "1 minute", UpdateMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Update, 60 )
local InformationMessagesMenu = MENU_GROUP:New( PlayerGroup, "Information Messages", MessagesMenu )
MENU_GROUP_COMMAND:New( PlayerGroup, "5 seconds", InformationMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Information, 5 )
MENU_GROUP_COMMAND:New( PlayerGroup, "10 seconds", InformationMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Information, 10 )
MENU_GROUP_COMMAND:New( PlayerGroup, "15 seconds", InformationMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Information, 15 )
MENU_GROUP_COMMAND:New( PlayerGroup, "30 seconds", InformationMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Information, 30 )
MENU_GROUP_COMMAND:New( PlayerGroup, "1 minute", InformationMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Information, 60 )
MENU_GROUP_COMMAND:New( PlayerGroup, "2 minutes", InformationMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Information, 120 )
local BriefingReportsMenu = MENU_GROUP:New( PlayerGroup, "Briefing Reports", MessagesMenu )
MENU_GROUP_COMMAND:New( PlayerGroup, "15 seconds", BriefingReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Briefing, 15 )
MENU_GROUP_COMMAND:New( PlayerGroup, "30 seconds", BriefingReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Briefing, 30 )
MENU_GROUP_COMMAND:New( PlayerGroup, "1 minute", BriefingReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Briefing, 60 )
MENU_GROUP_COMMAND:New( PlayerGroup, "2 minutes", BriefingReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Briefing, 120 )
MENU_GROUP_COMMAND:New( PlayerGroup, "3 minutes", BriefingReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Briefing, 180 )
local OverviewReportsMenu = MENU_GROUP:New( PlayerGroup, "Overview Reports", MessagesMenu )
MENU_GROUP_COMMAND:New( PlayerGroup, "15 seconds", OverviewReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Overview, 15 )
MENU_GROUP_COMMAND:New( PlayerGroup, "30 seconds", OverviewReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Overview, 30 )
MENU_GROUP_COMMAND:New( PlayerGroup, "1 minute", OverviewReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Overview, 60 )
MENU_GROUP_COMMAND:New( PlayerGroup, "2 minutes", OverviewReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Overview, 120 )
MENU_GROUP_COMMAND:New( PlayerGroup, "3 minutes", OverviewReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Overview, 180 )
local DetailedReportsMenu = MENU_GROUP:New( PlayerGroup, "Detailed Reports", MessagesMenu )
MENU_GROUP_COMMAND:New( PlayerGroup, "15 seconds", DetailedReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.DetailedReportsMenu, 15 )
MENU_GROUP_COMMAND:New( PlayerGroup, "30 seconds", DetailedReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.DetailedReportsMenu, 30 )
MENU_GROUP_COMMAND:New( PlayerGroup, "1 minute", DetailedReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.DetailedReportsMenu, 60 )
MENU_GROUP_COMMAND:New( PlayerGroup, "2 minutes", DetailedReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.DetailedReportsMenu, 120 )
MENU_GROUP_COMMAND:New( PlayerGroup, "3 minutes", DetailedReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.DetailedReportsMenu, 180 )
return self
end
@ -372,40 +564,44 @@ do -- SETTINGS
--- @param #SETTINGS self
function SETTINGS:A2GMenuSystem( A2GSystem )
function SETTINGS:A2GMenuSystem( MenuGroup, RootMenu, A2GSystem )
self.A2GSystem = A2GSystem
self:SetSystemMenu()
MESSAGE:New( string.format("Settings: Default A2G coordinate system set to %s for all players!", A2GSystem ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu )
end
--- @param #SETTINGS self
function SETTINGS:A2AMenuSystem( A2ASystem )
function SETTINGS:A2AMenuSystem( MenuGroup, RootMenu, A2ASystem )
self.A2ASystem = A2ASystem
self:SetSystemMenu()
MESSAGE:New( string.format("Settings: Default A2A coordinate system set to %s for all players!", A2ASystem ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu )
end
--- @param #SETTINGS self
function SETTINGS:MenuLL_Accuracy( LL_Accuracy )
function SETTINGS:MenuLL_DDM_Accuracy( MenuGroup, RootMenu, LL_Accuracy )
self.LL_Accuracy = LL_Accuracy
self:SetSystemMenu()
MESSAGE:New( string.format("Settings: Default LL accuracy set to %s for all players!", LL_Accuracy ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu )
end
--- @param #SETTINGS self
function SETTINGS:MenuLL_DMS( LL_DMS )
self.LL_DMS = LL_DMS
self:SetSystemMenu()
end
--- @param #SETTINGS self
function SETTINGS:MenuMGRS_Accuracy( MGRS_Accuracy )
function SETTINGS:MenuMGRS_Accuracy( MenuGroup, RootMenu, MGRS_Accuracy )
self.MGRS_Accuracy = MGRS_Accuracy
self:SetSystemMenu()
MESSAGE:New( string.format("Settings: Default MGRS accuracy set to %s for all players!", MGRS_Accuracy ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu )
end
--- @param #SETTINGS self
function SETTINGS:MenuMWSystem( MW )
function SETTINGS:MenuMWSystem( MenuGroup, RootMenu, MW )
self.Metric = MW
MESSAGE:New( string.format("Settings: Default measurement format set to %s for all players!.", MW and "Metric" or "Imperial" ), 5 ):ToAll()
self:SetSystemMenu()
MESSAGE:New( string.format("Settings: Default measurement format set to %s for all players!", MW and "Metric" or "Imperial" ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu )
end
--- @param #SETTINGS self
function SETTINGS:MenuMessageTimingsSystem( MenuGroup, RootMenu, MessageType, MessageTime )
self:SetMessageTime( MessageType, MessageTime )
MESSAGE:New( string.format( "Settings: Default message time set for %s to %d.", MessageType, MessageTime ), 5 ):ToAll()
end
do
@ -413,7 +609,7 @@ do -- SETTINGS
function SETTINGS:MenuGroupA2GSystem( PlayerUnit, PlayerGroup, PlayerName, A2GSystem )
BASE:E( {self, PlayerUnit:GetName(), A2GSystem} )
self.A2GSystem = A2GSystem
MESSAGE:New( string.format("Settings: A2G format set to %s for player %s.", A2GSystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
MESSAGE:New( string.format( "Settings: A2G format set to %s for player %s.", A2GSystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
@ -421,42 +617,40 @@ do -- SETTINGS
--- @param #SETTINGS self
function SETTINGS:MenuGroupA2ASystem( PlayerUnit, PlayerGroup, PlayerName, A2ASystem )
self.A2ASystem = A2ASystem
MESSAGE:New( string.format("Settings: A2A format set to %s for player %s.", A2ASystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
MESSAGE:New( string.format( "Settings: A2A format set to %s for player %s.", A2ASystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
--- @param #SETTINGS self
function SETTINGS:MenuGroupLL_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, LL_Accuracy )
function SETTINGS:MenuGroupLL_DDM_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, LL_Accuracy )
self.LL_Accuracy = LL_Accuracy
MESSAGE:New( string.format("Settings: A2G LL format accuracy set to %d for player %s.", LL_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup )
MESSAGE:New( string.format( "Settings: A2G LL format accuracy set to %d for player %s.", LL_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup )
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
--- @param #SETTINGS self
function SETTINGS:MenuGroupLL_DMSSystem( PlayerUnit, PlayerGroup, PlayerName, LL_DMS )
self.LL_DMS = LL_DMS
MESSAGE:New( string.format("Settings: A2G LL format mode set to %s for player %s.", LL_DMS and "DMS" or "HMS", PlayerName ), 5 ):ToGroup( PlayerGroup )
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
--- @param #SETTINGS self
function SETTINGS:MenuGroupMGRS_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, MGRS_Accuracy )
self.MGRS_Accuracy = MGRS_Accuracy
MESSAGE:New( string.format("Settings: A2G MGRS format accuracy set to %d for player %s.", MGRS_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup )
MESSAGE:New( string.format( "Settings: A2G MGRS format accuracy set to %d for player %s.", MGRS_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup )
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
--- @param #SETTINGS self
function SETTINGS:MenuGroupMWSystem( PlayerUnit, PlayerGroup, PlayerName, MW )
self.Metrics = MW
MESSAGE:New( string.format("Settings: Measurement format set to %s for player %s.", MW and "Metric" or "Imperial", PlayerName ), 5 ):ToGroup( PlayerGroup )
self.Metric = MW
MESSAGE:New( string.format( "Settings: Measurement format set to %s for player %s.", MW and "Metric" or "Imperial", PlayerName ), 5 ):ToGroup( PlayerGroup )
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
--- @param #SETTINGS self
function SETTINGS:MenuGroupMessageTimingsSystem( PlayerUnit, PlayerGroup, PlayerName, MessageType, MessageTime )
self:SetMessageTime( MessageType, MessageTime )
MESSAGE:New( string.format( "Settings: Default message time set for %s to %d.", MessageType, MessageTime ), 5 ):ToGroup( PlayerGroup )
end
end

View File

@ -1,4 +1,4 @@
--- **Functional** -- Spawn dynamically new GROUPs in your missions.
--- **Core** -- SPAWN class dynamically spawns new groups of units in your missions.
--
-- ![Banner Image](..\Presentations\SPAWN\SPAWN.JPG)
--
@ -55,8 +55,12 @@
--- # SPAWN class, extends @{Base#BASE}
--
-- -- ![Banner Image](..\Presentations\SPAWN\SPAWN.JPG)
--
-- ===
--
-- The SPAWN class allows to spawn dynamically new groups.
-- Each SPAWN object needs to be have a related **template group** setup in the Mission Editor (ME),
-- Each SPAWN object needs to be have related **template groups** setup in the Mission Editor (ME),
-- which is a normal group with the **Late Activation** flag set.
-- This template group will never be activated in your mission.
-- SPAWN uses that **template group** to reference to all the characteristics
@ -200,6 +204,7 @@
-- * @{#SPAWN.SpawnFromStatic}(): Spawn a new group from a structure, taking the position of a @{Static}.
-- * @{#SPAWN.SpawnFromUnit}(): Spawn a new group taking the position of a @{Unit}.
-- * @{#SPAWN.SpawnInZone}(): Spawn a new group in a @{Zone}.
-- * @{#SPAWN.SpawnAtAirbase}(): Spawn a new group at an @{Airbase}, which can be an airdrome, ship or helipad.
--
-- Note that @{#SPAWN.Spawn} and @{#SPAWN.ReSpawn} return a @{GROUP#GROUP.New} object, that contains a reference to the DCSGroup object.
-- You can use the @{GROUP} object to do further actions with the DCSGroup.
@ -270,8 +275,12 @@ SPAWN = {
-- @extends Wrapper.Group#GROUP.Takeoff
--- @field #SPAWN.Takeoff Takeoff
SPAWN.Takeoff = GROUP.Takeoff
SPAWN.Takeoff = {
Air = 1,
Runway = 2,
Hot = 3,
Cold = 4,
}
--- @type SPAWN.SpawnZoneTable
-- @list <Core.Zone#ZONE_BASE> SpawnZone
@ -289,7 +298,7 @@ function SPAWN:New( SpawnTemplatePrefix )
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWN
self:F( { SpawnTemplatePrefix } )
local TemplateGroup = Group.getByName( SpawnTemplatePrefix )
local TemplateGroup = GROUP:FindByName( SpawnTemplatePrefix )
if TemplateGroup then
self.SpawnTemplatePrefix = SpawnTemplatePrefix
self.SpawnIndex = 0
@ -333,7 +342,7 @@ function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix )
local self = BASE:Inherit( self, BASE:New() )
self:F( { SpawnTemplatePrefix, SpawnAliasPrefix } )
local TemplateGroup = Group.getByName( SpawnTemplatePrefix )
local TemplateGroup = GROUP:FindByName( SpawnTemplatePrefix )
if TemplateGroup then
self.SpawnTemplatePrefix = SpawnTemplatePrefix
self.SpawnAliasPrefix = SpawnAliasPrefix
@ -520,6 +529,73 @@ function SPAWN:InitRandomizeTemplate( SpawnTemplatePrefixTable )
return self
end
--- Randomize templates to be used as the unit representatives for the Spawned group, defined using a SET_GROUP object.
-- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor,
-- but they will all follow the same Template route and have the same prefix name.
-- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group.
-- @param #SPAWN self
-- @param Core.Set#SET_GROUP SpawnTemplateSet A SET_GROUP object set, that contains the groups that are possible unit representatives of the group to be spawned.
-- @return #SPAWN
-- @usage
-- -- NATO Tank Platoons invading Gori.
--
-- -- Choose between different 'US Tank Platoon Template' configurations to be spawned for the
-- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SPAWN objects.
--
-- -- Each new SPAWN will randomize the route, with a defined time interval of 200 seconds with 40% time variation (randomization) and
-- -- with a limit set of maximum 12 Units alive simulteneously and 150 Groups to be spawned during the whole mission.
--
-- Spawn_US_PlatoonSet = SET_GROUP:New():FilterPrefixes( "US Tank Platoon Templates" ):FilterOnce()
--
-- --- Now use the Spawn_US_PlatoonSet to define the templates using InitRandomizeTemplateSet.
-- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 )
-- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 )
-- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 )
function SPAWN:InitRandomizeTemplateSet( SpawnTemplateSet ) -- R2.3
self:F( { self.SpawnTemplatePrefix } )
self.SpawnTemplatePrefixTable = SpawnTemplateSet:GetSetNames()
self.SpawnRandomizeTemplate = true
for SpawnGroupID = 1, self.SpawnMaxGroups do
self:_RandomizeTemplate( SpawnGroupID )
end
return self
end
--- Randomize templates to be used as the unit representatives for the Spawned group, defined by specifying the prefix names.
-- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor,
-- but they will all follow the same Template route and have the same prefix name.
-- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group.
-- @param #SPAWN self
-- @param #string SpawnTemplatePrefixes A string or a list of string that contains the prefixes of the groups that are possible unit representatives of the group to be spawned.
-- @return #SPAWN
-- @usage
-- -- NATO Tank Platoons invading Gori.
--
-- -- Choose between different 'US Tank Platoon Templates' configurations to be spawned for the
-- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SPAWN objects.
--
-- -- Each new SPAWN will randomize the route, with a defined time interval of 200 seconds with 40% time variation (randomization) and
-- -- with a limit set of maximum 12 Units alive simulteneously and 150 Groups to be spawned during the whole mission.
--
-- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 )
-- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 )
-- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 )
function SPAWN:InitRandomizeTemplatePrefixes( SpawnTemplatePrefixes ) --R2.3
self:F( { self.SpawnTemplatePrefix } )
local SpawnTemplateSet = SET_GROUP:New():FilterPrefixes( SpawnTemplatePrefixes ):FilterOnce()
self:InitRandomizeTemplateSet( SpawnTemplateSet )
return self
end
--- When spawning a new group, make the grouping of the units according the InitGrouping setting.
-- @param #SPAWN self
-- @param #number Grouping Indicates the maximum amount of units in the group.
@ -982,19 +1058,53 @@ function SPAWN:OnSpawnGroup( SpawnCallBackFunction, ... )
return self
end
--- Will spawn a group at an airbase.
--- Will spawn a group at an @{Airbase}.
-- This method is mostly advisable to be used if you want to simulate spawning units at an airbase.
-- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
-- You can use the returned group to further define the route to be followed.
--
-- The @{Airbase#AIRBASE} object must refer to a valid airbase known in the sim.
-- You can use the following enumerations to search for the pre-defined airbases on the current known maps of DCS:
--
-- * @{Airbase#AIRBASE.Caucasus}: The airbases on the Caucasus map.
-- * @{Airbase#AIRBASE.Nevada}: The airbases on the Nevada (NTTR) map.
-- * @{Airbase#AIRBASE.Normandy}: The airbases on the Normandy map.
--
-- Use the method @{Airbase#AIRBASE.FindByName}() to retrieve the airbase object.
-- The known AIRBASE objects are automatically imported at mission start by MOOSE.
-- Therefore, there isn't any New() constructor defined for AIRBASE objects.
--
-- Ships and Farps are added within the mission, and are therefore not known.
-- For these AIRBASE objects, there isn't an @{Airbase#AIRBASE} enumeration defined.
-- You need to provide the **exact name** of the airbase as the parameter to the @{Airbase#AIRBASE.FindByName}() method!
--
-- @param #SPAWN self
-- @param Wrapper.Airbase#AIRBASE Airbase The @{Airbase} where to spawn the group.
-- @param Wrapper.Airbase#AIRBASE SpawnAirbase The @{Airbase} where to spawn the group.
-- @param #SPAWN.Takeoff Takeoff (optional) The location and takeoff method. Default is Hot.
-- @param #number TakeoffAltitude (optional) The altitude above the ground.
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil Nothing was spawned.
function SPAWN:SpawnAtAirbase( Airbase, Takeoff ) -- R2.2
self:F( { self.SpawnTemplatePrefix, Airbase } )
-- @usage
-- Spawn_Plane = SPAWN:New( "Plane" )
-- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Cold )
-- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Hot )
-- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Runway )
--
-- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( "Carrier" ), SPAWN.Takeoff.Cold )
--
-- Spawn_Heli = SPAWN:New( "Heli")
--
-- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "FARP Cold" ), SPAWN.Takeoff.Cold )
-- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "FARP Hot" ), SPAWN.Takeoff.Hot )
-- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "FARP Runway" ), SPAWN.Takeoff.Runway )
-- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "FARP Air" ), SPAWN.Takeoff.Air )
--
-- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "Carrier" ), SPAWN.Takeoff.Cold )
--
function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude ) -- R2.2
self:E( { self.SpawnTemplatePrefix, SpawnAirbase, Takeoff, TakeoffAltitude } )
local PointVec3 = Airbase:GetPointVec3()
local PointVec3 = SpawnAirbase:GetPointVec3()
self:T2(PointVec3)
Takeoff = Takeoff or SPAWN.Takeoff.Hot
@ -1005,34 +1115,87 @@ function SPAWN:SpawnAtAirbase( Airbase, Takeoff ) -- R2.2
if SpawnTemplate then
self:T( { "Current point of ", self.SpawnTemplatePrefix, Airbase } )
self:T( { "Current point of ", self.SpawnTemplatePrefix, SpawnAirbase } )
local SpawnPoint = SpawnTemplate.route.points[1]
-- These are only for ships.
SpawnPoint.linkUnit = nil
SpawnPoint.helipadId = nil
SpawnPoint.airdromeId = nil
local AirbaseID = SpawnAirbase:GetID()
local AirbaseCategory = SpawnAirbase:GetDesc().category
self:F( { AirbaseCategory = AirbaseCategory } )
if AirbaseCategory == Airbase.Category.SHIP then
SpawnPoint.linkUnit = AirbaseID
SpawnPoint.helipadId = AirbaseID
elseif AirbaseCategory == Airbase.Category.HELIPAD then
SpawnPoint.linkUnit = AirbaseID
SpawnPoint.helipadId = AirbaseID
elseif AirbaseCategory == Airbase.Category.AIRDROME then
SpawnPoint.airdromeId = AirbaseID
end
SpawnPoint.alt = 0
SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type
SpawnPoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action
-- Translate the position of the Group Template to the Vec3.
for UnitID = 1, #SpawnTemplate.units do
self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
-- These cause a lot of confusion.
local UnitTemplate = SpawnTemplate.units[UnitID]
UnitTemplate.parking = nil
UnitTemplate.parking_id = nil
UnitTemplate.alt = 0
local SX = UnitTemplate.x
local SY = UnitTemplate.y
local BX = SpawnTemplate.route.points[1].x
local BY = SpawnTemplate.route.points[1].y
local BX = SpawnPoint.x
local BY = SpawnPoint.y
local TX = PointVec3.x + ( SX - BX )
local TY = PointVec3.z + ( SY - BY )
SpawnTemplate.units[UnitID].x = TX
SpawnTemplate.units[UnitID].y = TY
SpawnTemplate.units[UnitID].alt = PointVec3.y
self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
UnitTemplate.x = TX
UnitTemplate.y = TY
if Takeoff == GROUP.Takeoff.Air then
UnitTemplate.alt = PointVec3.y + ( TakeoffAltitude or 200 )
--else
-- UnitTemplate.alt = PointVec3.y + 10
end
self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y )
end
SpawnTemplate.route.points[1].x = PointVec3.x
SpawnTemplate.route.points[1].y = PointVec3.z
SpawnTemplate.route.points[1].alt = Airbase.y
SpawnTemplate.route.points[1].type = GROUPTEMPLATE.Takeoff[Takeoff]
SpawnTemplate.route.points[1].airdromeId = Airbase:GetID()
SpawnPoint.x = PointVec3.x
SpawnPoint.y = PointVec3.z
if Takeoff == GROUP.Takeoff.Air then
SpawnPoint.alt = PointVec3.y + ( TakeoffAltitude or 200 )
--else
-- SpawnPoint.alt = PointVec3.y + 10
end
SpawnTemplate.x = PointVec3.x
SpawnTemplate.y = PointVec3.z
return self:SpawnWithIndex( self.SpawnIndex )
local GroupSpawned = self:SpawnWithIndex( self.SpawnIndex )
-- When spawned in the air, we need to generate a Takeoff Event
if Takeoff == GROUP.Takeoff.Air then
for UnitID, UnitSpawned in pairs( GroupSpawned:GetUnits() ) do
SCHEDULER:New( nil, BASE.CreateEventTakeoff, { timer.getTime(), UnitSpawned:GetDCSObject() } , 1 )
end
end
return GroupSpawned
end
end
@ -1068,6 +1231,8 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex )
if SpawnTemplate then
self:T( { "Current point of ", self.SpawnTemplatePrefix, Vec3 } )
local TemplateHeight = SpawnTemplate.route.points[1].alt
-- Translate the position of the Group Template to the Vec3.
for UnitID = 1, #SpawnTemplate.units do
@ -1081,16 +1246,20 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex )
local TY = Vec3.z + ( SY - BY )
SpawnTemplate.units[UnitID].x = TX
SpawnTemplate.units[UnitID].y = TY
SpawnTemplate.units[UnitID].alt = Vec3.y
if SpawnTemplate.CategoryID ~= Group.Category.SHIP then
SpawnTemplate.units[UnitID].alt = Vec3.y or TemplateHeight
end
self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
end
SpawnTemplate.route.points[1].x = Vec3.x
SpawnTemplate.route.points[1].y = Vec3.z
SpawnTemplate.route.points[1].alt = Vec3.y
if SpawnTemplate.CategoryID ~= Group.Category.SHIP then
SpawnTemplate.route.points[1].alt = Vec3.y or TemplateHeight
end
SpawnTemplate.x = Vec3.x
SpawnTemplate.y = Vec3.z
SpawnTemplate.alt = Vec3.y or TemplateHeight
return self:SpawnWithIndex( self.SpawnIndex )
end
@ -1105,14 +1274,31 @@ end
-- You can use the returned group to further define the route to be followed.
-- @param #SPAWN self
-- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 coordinates where to spawn the group.
-- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone.
-- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone.
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil Nothing was spawned.
function SPAWN:SpawnFromVec2( Vec2, SpawnIndex )
self:F( { self.SpawnTemplatePrefix, Vec2, SpawnIndex } )
-- @usage
--
-- local SpawnVec2 = ZONE:New( ZoneName ):GetVec2()
--
-- -- Spawn at the zone center position at the height specified in the ME of the group template!
-- SpawnAirplanes:SpawnFromVec2( SpawnVec2 )
--
-- -- Spawn from the static position at the height randomized between 2000 and 4000 meters.
-- SpawnAirplanes:SpawnFromVec2( SpawnVec2, 2000, 4000 )
--
function SPAWN:SpawnFromVec2( Vec2, MinHeight, MaxHeight, SpawnIndex )
self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, Vec2, MinHeight, MaxHeight, SpawnIndex } )
local PointVec2 = POINT_VEC2:NewFromVec2( Vec2 )
return self:SpawnFromVec3( PointVec2:GetVec3(), SpawnIndex )
local Height = nil
if MinHeight and MaxHeight then
Height = math.random( MinHeight, MaxHeight)
end
return self:SpawnFromVec3( { x = Vec2.x, y = Height, z = Vec2.y }, SpawnIndex ) -- y can be nil. In this case, spawn on the ground for vehicles, and in the template altitude for air.
end
@ -1121,14 +1307,26 @@ end
-- You can use the returned group to further define the route to be followed.
-- @param #SPAWN self
-- @param Wrapper.Unit#UNIT HostUnit The air or ground unit dropping or unloading the group.
-- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone.
-- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone.
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil Nothing was spawned.
function SPAWN:SpawnFromUnit( HostUnit, SpawnIndex )
self:F( { self.SpawnTemplatePrefix, HostUnit, SpawnIndex } )
-- @usage
--
-- local SpawnStatic = STATIC:FindByName( StaticName )
--
-- -- Spawn from the static position at the height specified in the ME of the group template!
-- SpawnAirplanes:SpawnFromUnit( SpawnStatic )
--
-- -- Spawn from the static position at the height randomized between 2000 and 4000 meters.
-- SpawnAirplanes:SpawnFromUnit( SpawnStatic, 2000, 4000 )
--
function SPAWN:SpawnFromUnit( HostUnit, MinHeight, MaxHeight, SpawnIndex )
self:F( { self.SpawnTemplatePrefix, HostUnit, MinHeight, MaxHeight, SpawnIndex } )
if HostUnit and HostUnit:IsAlive() ~= nil then -- and HostUnit:getUnit(1):inAir() == false then
return self:SpawnFromVec3( HostUnit:GetVec3(), SpawnIndex )
return self:SpawnFromVec2( HostUnit:GetVec2(), MinHeight, MaxHeight, SpawnIndex )
end
return nil
@ -1138,14 +1336,26 @@ end
-- You can use the returned group to further define the route to be followed.
-- @param #SPAWN self
-- @param Wrapper.Static#STATIC HostStatic The static dropping or unloading the group.
-- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone.
-- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone.
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil Nothing was spawned.
function SPAWN:SpawnFromStatic( HostStatic, SpawnIndex )
self:F( { self.SpawnTemplatePrefix, HostStatic, SpawnIndex } )
-- @usage
--
-- local SpawnStatic = STATIC:FindByName( StaticName )
--
-- -- Spawn from the static position at the height specified in the ME of the group template!
-- SpawnAirplanes:SpawnFromStatic( SpawnStatic )
--
-- -- Spawn from the static position at the height randomized between 2000 and 4000 meters.
-- SpawnAirplanes:SpawnFromStatic( SpawnStatic, 2000, 4000 )
--
function SPAWN:SpawnFromStatic( HostStatic, MinHeight, MaxHeight, SpawnIndex )
self:F( { self.SpawnTemplatePrefix, HostStatic, MinHeight, MaxHeight, SpawnIndex } )
if HostStatic and HostStatic:IsAlive() then
return self:SpawnFromVec3( HostStatic:GetVec3(), SpawnIndex )
return self:SpawnFromVec2( HostStatic:GetVec2(), MinHeight, MaxHeight, SpawnIndex )
end
return nil
@ -1158,17 +1368,38 @@ end
-- @param #SPAWN self
-- @param Core.Zone#ZONE Zone The zone where the group is to be spawned.
-- @param #boolean RandomizeGroup (optional) Randomization of the @{Group} position in the zone.
-- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone.
-- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone.
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil when nothing was spawned.
function SPAWN:SpawnInZone( Zone, RandomizeGroup, SpawnIndex )
self:F( { self.SpawnTemplatePrefix, Zone, RandomizeGroup, SpawnIndex } )
-- @usage
--
-- local SpawnZone = ZONE:New( ZoneName )
--
-- -- Spawn at the zone center position at the height specified in the ME of the group template!
-- SpawnAirplanes:SpawnInZone( SpawnZone )
--
-- -- Spawn in the zone at a random position at the height specified in the Me of the group template.
-- SpawnAirplanes:SpawnInZone( SpawnZone, true )
--
-- -- Spawn in the zone at a random position at the height randomized between 2000 and 4000 meters.
-- SpawnAirplanes:SpawnInZone( SpawnZone, true, 2000, 4000 )
--
-- -- Spawn at the zone center position at the height randomized between 2000 and 4000 meters.
-- SpawnAirplanes:SpawnInZone( SpawnZone, false, 2000, 4000 )
--
-- -- Spawn at the zone center position at the height randomized between 2000 and 4000 meters.
-- SpawnAirplanes:SpawnInZone( SpawnZone, nil, 2000, 4000 )
--
function SPAWN:SpawnInZone( Zone, RandomizeGroup, MinHeight, MaxHeight, SpawnIndex )
self:F( { self.SpawnTemplatePrefix, Zone, RandomizeGroup, MinHeight, MaxHeight, SpawnIndex } )
if Zone then
if RandomizeGroup then
return self:SpawnFromVec2( Zone:GetRandomVec2(), SpawnIndex )
return self:SpawnFromVec2( Zone:GetRandomVec2(), MinHeight, MaxHeight, SpawnIndex )
else
return self:SpawnFromVec2( Zone:GetVec2(), SpawnIndex )
return self:SpawnFromVec2( Zone:GetVec2(), MinHeight, MaxHeight, SpawnIndex )
end
end

View File

@ -116,6 +116,33 @@ function SPAWNSTATIC:NewFromType( SpawnTypeName, SpawnShapeName, SpawnCategory,
return self
end
--- Creates a new @{Static} at the original position.
-- @param #SPAWNSTATIC self
-- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360.
-- @param #string (optional) The name of the new static.
-- @return #SPAWNSTATIC
function SPAWNSTATIC:Spawn( Heading, NewName ) --R2.3
self:F( { Heading, NewName } )
local CountryName = _DATABASE.COUNTRY_NAME[self.CountryID]
local StaticTemplate = _DATABASE:GetStaticUnitTemplate( self.SpawnTemplatePrefix )
StaticTemplate.name = NewName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex )
StaticTemplate.heading = ( Heading / 180 ) * math.pi
StaticTemplate.CountryID = nil
StaticTemplate.CoalitionID = nil
StaticTemplate.CategoryID = nil
local Static = coalition.addStaticObject( self.CountryID, StaticTemplate )
self.SpawnIndex = self.SpawnIndex + 1
return Static
end
--- Creates a new @{Static} from a POINT_VEC2.
-- @param #SPAWNSTATIC self
@ -130,8 +157,13 @@ function SPAWNSTATIC:SpawnFromPointVec2( PointVec2, Heading, NewName ) --R2.1
local StaticTemplate = _DATABASE:GetStaticUnitTemplate( self.SpawnTemplatePrefix )
StaticTemplate.x = PointVec2:GetLat()
StaticTemplate.y = PointVec2:GetLon()
StaticTemplate.x = PointVec2.x
StaticTemplate.y = PointVec2.z
StaticTemplate.units = nil
StaticTemplate.route = nil
StaticTemplate.groupId = nil
StaticTemplate.name = NewName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex )
StaticTemplate.heading = ( Heading / 180 ) * math.pi

View File

@ -222,7 +222,7 @@ do
self:HandleEvent( EVENTS.Dead )
self:__Lasing( -0.2 )
self:__Lasing( -1 )
end
--- @param #SPOT self

View File

@ -0,0 +1,95 @@
--- **Core (WIP)** -- Manage user flags.
--
-- ====
--
-- Management of DCS User Flags.
--
-- ====
--
-- ### Author: **Sven Van de Velde (FlightControl)**
--
-- ====
--
-- @module UserFlag
do -- UserFlag
--- @type USERFLAG
-- @extends Core.Base#BASE
--- # USERFLAG class, extends @{Base#BASE}
--
-- Management of DCS User Flags.
--
-- ## 1. USERFLAG constructor
--
-- * @{#USERFLAG.New}(): Creates a new USERFLAG object.
--
-- @field #USERFLAG
USERFLAG = {
ClassName = "USERFLAG",
}
--- USERFLAG Constructor.
-- @param #USERFLAG self
-- @param #string UserFlagName The name of the userflag, which is a free text string.
-- @return #USERFLAG
function USERFLAG:New( UserFlagName ) --R2.3
local self = BASE:Inherit( self, BASE:New() ) -- #USERFLAG
self.UserFlagName = UserFlagName
return self
end
--- Set the userflag to a given Number.
-- @param #USERFLAG self
-- @param #number Number The number value to be checked if it is the same as the userflag.
-- @return #USERFLAG The userflag instance.
-- @usage
-- local BlueVictory = USERFLAG:New( "VictoryBlue" )
-- BlueVictory:Set( 100 ) -- Set the UserFlag VictoryBlue to 100.
--
function USERFLAG:Set( Number ) --R2.3
self:F( { Number = Number } )
trigger.action.setUserFlag( self.UserFlagName, Number )
return self
end
--- Get the userflag Number.
-- @param #USERFLAG self
-- @return #number Number The number value to be checked if it is the same as the userflag.
-- @usage
-- local BlueVictory = USERFLAG:New( "VictoryBlue" )
-- local BlueVictoryValue = BlueVictory:Get() -- Get the UserFlag VictoryBlue value.
--
function USERFLAG:Get( Number ) --R2.3
return trigger.misc.getUserFlag( self.UserFlagName )
end
--- Check if the userflag has a value of Number.
-- @param #USERFLAG self
-- @param #number Number The number value to be checked if it is the same as the userflag.
-- @return #boolean true if the Number is the value of the userflag.
-- @usage
-- local BlueVictory = USERFLAG:New( "VictoryBlue" )
-- if BlueVictory:Is( 1 ) then
-- return "Blue has won"
-- end
function USERFLAG:Is( Number ) --R2.3
return trigger.misc.getUserFlag( self.UserFlagName ) == Number
end
end

View File

@ -0,0 +1,129 @@
--- **Core (WIP)** -- Manage user sound.
--
-- ====
--
-- Management of DCS User Sound.
--
-- ====
--
-- ### Author: **Sven Van de Velde (FlightControl)**
--
-- ====
--
-- @module UserSound
do -- UserSound
--- @type USERSOUND
-- @extends Core.Base#BASE
--- # USERSOUND class, extends @{Base#BASE}
--
-- Management of DCS User Sound.
--
-- ## 1. USERSOUND constructor
--
-- * @{#USERSOUND.New}(): Creates a new USERSOUND object.
--
-- @field #USERSOUND
USERSOUND = {
ClassName = "USERSOUND",
}
--- USERSOUND Constructor.
-- @param #USERSOUND self
-- @param #string UserSoundFileName The filename of the usersound.
-- @return #USERSOUND
function USERSOUND:New( UserSoundFileName ) --R2.3
local self = BASE:Inherit( self, BASE:New() ) -- #USERSOUND
self.UserSoundFileName = UserSoundFileName
return self
end
--- Set usersound filename.
-- @param #USERSOUND self
-- @param #string UserSoundFileName The filename of the usersound.
-- @return #USERSOUND The usersound instance.
-- @usage
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- BlueVictory:SetFileName( "BlueVictoryLoud.ogg" ) -- Set the BlueVictory to change the file name to play a louder sound.
--
function USERSOUND:SetFileName( UserSoundFileName ) --R2.3
self.UserSoundFileName = UserSoundFileName
return self
end
--- Play the usersound to all players.
-- @param #USERSOUND self
-- @return #USERSOUND The usersound instance.
-- @usage
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- BlueVictory:ToAll() -- Play the sound that Blue has won.
--
function USERSOUND:ToAll() --R2.3
trigger.action.outSound( self.UserSoundFileName )
return self
end
--- Play the usersound to the given coalition.
-- @param #USERSOUND self
-- @param Dcs.DCScoalition#coalition Coalition The coalition to play the usersound to.
-- @return #USERSOUND The usersound instance.
-- @usage
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- BlueVictory:ToCoalition( coalition.side.BLUE ) -- Play the sound that Blue has won to the blue coalition.
--
function USERSOUND:ToCoalition( Coalition ) --R2.3
trigger.action.outSoundForCoalition(Coalition, self.UserSoundFileName )
return self
end
--- Play the usersound to the given country.
-- @param #USERSOUND self
-- @param Dcs.DCScountry#country Country The country to play the usersound to.
-- @return #USERSOUND The usersound instance.
-- @usage
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- BlueVictory:ToCountry( country.id.USA ) -- Play the sound that Blue has won to the USA country.
--
function USERSOUND:ToCountry( Country ) --R2.3
trigger.action.outSoundForCountry( Country, self.UserSoundFileName )
return self
end
--- Play the usersound to the given @{Group}.
-- @param #USERSOUND self
-- @param Wrapper.Group#GROUP Group The @{Group} to play the usersound to.
-- @return #USERSOUND The usersound instance.
-- @usage
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- local PlayerGroup = GROUP:FindByName( "PlayerGroup" ) -- Search for the active group named "PlayerGroup", that contains a human player.
-- BlueVictory:ToGroup( PlayerGroup ) -- Play the sound that Blue has won to the player group.
--
function USERSOUND:ToGroup( Group ) --R2.3
trigger.action.outSoundForGroup( Group:GetID(), self.UserSoundFileName )
return self
end
end

View File

@ -0,0 +1,184 @@
--- **Core** -- VELOCITY models a speed, which can be expressed in various formats according the Settings.
--
-- ===
--
-- ### Author: **Sven Van de Velde (FlightControl)**
-- ### Contributions:
--
-- ====
--
-- @module Velocity
do -- Velocity
--- @type VELOCITY
-- @extends Core.Base#BASE
--- # VELOCITY class, extends @{Base#BASE}
--
-- VELOCITY models a speed, which can be expressed in various formats according the Settings.
--
-- ## 1. VELOCITY constructor
--
-- * @{#VELOCITY.New}(): Creates a new VELOCITY object.
--
-- @field #VELOCITY
VELOCITY = {
ClassName = "VELOCITY",
}
--- VELOCITY Constructor.
-- @param #VELOCITY self
-- @param #number VelocityMps The velocity in meters per second.
-- @return #VELOCITY
function VELOCITY:New( VelocityMps )
local self = BASE:Inherit( self, BASE:New() ) -- #VELOCITY
self:F( {} )
self.Velocity = VelocityMps
return self
end
--- Set the velocity in Mps (meters per second).
-- @param #VELOCITY self
-- @param #number VelocityMps The velocity in meters per second.
-- @return #VELOCITY
function VELOCITY:Set( VelocityMps )
self.Velocity = VelocityMps
return self
end
--- Get the velocity in Mps (meters per second).
-- @param #VELOCITY self
-- @return #number The velocity in meters per second.
function VELOCITY:Get()
return self.Velocity
end
--- Set the velocity in Kmph (kilometers per hour).
-- @param #VELOCITY self
-- @param #number VelocityKmph The velocity in kilometers per hour.
-- @return #VELOCITY
function VELOCITY:SetKmph( VelocityKmph )
self.Velocity = UTILS.KmphToMps( VelocityKmph )
return self
end
--- Get the velocity in Kmph (kilometers per hour).
-- @param #VELOCITY self
-- @return #number The velocity in kilometers per hour.
function VELOCITY:GetKmph()
return UTILS.MpsToKmph( self.Velocity )
end
--- Set the velocity in Miph (miles per hour).
-- @param #VELOCITY self
-- @param #number VelocityMiph The velocity in miles per hour.
-- @return #VELOCITY
function VELOCITY:SetMiph( VelocityMiph )
self.Velocity = UTILS.MiphToMps( VelocityMiph )
return self
end
--- Get the velocity in Miph (miles per hour).
-- @param #VELOCITY self
-- @return #number The velocity in miles per hour.
function VELOCITY:GetMiph()
return UTILS.MpsToMiph( self.Velocity )
end
--- Get the velocity in text, according the player @{Settings}.
-- @param #VELOCITY self
-- @param Core.Settings#SETTINGS Settings
-- @return #string The velocity in text.
function VELOCITY:GetText( Settings )
local Settings = Settings or _SETTINGS
if self.Velocity ~= 0 then
if Settings:IsMetric() then
return string.format( "%d km/h", UTILS.MpsToKmph( self.Velocity ) )
else
return string.format( "%d mi/h", UTILS.MpsToMiph( self.Velocity ) )
end
else
return "stationary"
end
end
--- Get the velocity in text, according the player or default @{Settings}.
-- @param #VELOCITY self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable
-- @param Core.Settings#SETTINGS Settings
-- @return #string The velocity in text according the player or default @{Settings}
function VELOCITY:ToString( VelocityGroup, Settings ) -- R2.3
self:F( { Group = VelocityGroup and VelocityGroup:GetName() } )
local Settings = Settings or ( VelocityGroup and _DATABASE:GetPlayerSettings( VelocityGroup:GetPlayerName() ) ) or _SETTINGS
return self:GetText( Settings )
end
end
do -- VELOCITY_POSITIONABLE
--- @type VELOCITY_POSITIONABLE
-- @extends Core.Base#BASE
--- # VELOCITY_POSITIONABLE class, extends @{Base#BASE}
--
-- VELOCITY_POSITIONABLE monitors the speed of an @{Positionable} in the simulation, which can be expressed in various formats according the Settings.
--
-- ## 1. VELOCITY_POSITIONABLE constructor
--
-- * @{#VELOCITY_POSITIONABLE.New}(): Creates a new VELOCITY_POSITIONABLE object.
--
-- @field #VELOCITY_POSITIONABLE
VELOCITY_POSITIONABLE = {
ClassName = "VELOCITY_POSITIONABLE",
}
--- VELOCITY_POSITIONABLE Constructor.
-- @param #VELOCITY_POSITIONABLE self
-- @param Wrapper.Positionable#POSITIONABLE Positionable The Positionable to monitor the speed.
-- @return #VELOCITY_POSITIONABLE
function VELOCITY_POSITIONABLE:New( Positionable )
local self = BASE:Inherit( self, VELOCITY:New() ) -- #VELOCITY_POSITIONABLE
self:F( {} )
self.Positionable = Positionable
return self
end
--- Get the velocity in Mps (meters per second).
-- @param #VELOCITY_POSITIONABLE self
-- @return #number The velocity in meters per second.
function VELOCITY_POSITIONABLE:Get()
return self.Positionable:GetVelocityMPS() or 0
end
--- Get the velocity in Kmph (kilometers per hour).
-- @param #VELOCITY_POSITIONABLE self
-- @return #number The velocity in kilometers per hour.
function VELOCITY_POSITIONABLE:GetKmph()
return UTILS.MpsToKmph( self.Positionable:GetVelocityMPS() or 0)
end
--- Get the velocity in Miph (miles per hour).
-- @param #VELOCITY_POSITIONABLE self
-- @return #number The velocity in miles per hour.
function VELOCITY_POSITIONABLE:GetMiph()
return UTILS.MpsToMiph( self.Positionable:GetVelocityMPS() or 0 )
end
--- Get the velocity in text, according the player or default @{Settings}.
-- @param #VELOCITY_POSITIONABLE self
-- @return #string The velocity in text according the player or default @{Settings}
function VELOCITY_POSITIONABLE:ToString() -- R2.3
self:F( { Group = self.Positionable and self.Positionable:GetName() } )
local Settings = Settings or ( self.Positionable and _DATABASE:GetPlayerSettings( self.Positionable:GetPlayerName() ) ) or _SETTINGS
self.Velocity = self.Positionable:GetVelocityMPS()
return self:GetText( Settings )
end
end

View File

@ -50,6 +50,8 @@
-- ## Each zone has a name:
--
-- * @{#ZONE_BASE.GetName}(): Returns the name of the zone.
-- * @{#ZONE_BASE.SetName}(): Sets the name of the zone.
--
--
-- ## Each zone implements two polymorphic functions defined in @{Zone#ZONE_BASE}:
--
@ -121,6 +123,17 @@ function ZONE_BASE:GetName()
return self.ZoneName
end
--- Sets the name of the zone.
-- @param #ZONE_BASE self
-- @param #string ZoneName The name of the zone.
-- @return #ZONE_BASE
function ZONE_BASE:SetName( ZoneName )
self:F2()
self.ZoneName = ZoneName
end
--- Returns if a Vec2 is within the zone.
-- @param #ZONE_BASE self
-- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 to test.
@ -445,12 +458,17 @@ end
-- @param #ZONE_RADIUS self
-- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color.
-- @param #number Points (optional) The amount of points in the circle.
-- @param #number AddHeight (optional) The height to be added for the smoke.
-- @param #number AddOffSet (optional) The angle to be added for the smoking start position.
-- @return #ZONE_RADIUS self
function ZONE_RADIUS:SmokeZone( SmokeColor, Points )
function ZONE_RADIUS:SmokeZone( SmokeColor, Points, AddHeight, AngleOffset )
self:F2( SmokeColor )
local Point = {}
local Vec2 = self:GetVec2()
AddHeight = AddHeight or 0
AngleOffset = AngleOffset or 0
Points = Points and Points or 360
@ -458,10 +476,10 @@ function ZONE_RADIUS:SmokeZone( SmokeColor, Points )
local RadialBase = math.pi*2
for Angle = 0, 360, 360 / Points do
local Radial = Angle * RadialBase / 360
local Radial = ( Angle + AngleOffset ) * RadialBase / 360
Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius()
Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius()
POINT_VEC2:New( Point.x, Point.y ):Smoke( SmokeColor )
POINT_VEC2:New( Point.x, Point.y, AddHeight ):Smoke( SmokeColor )
end
return self
@ -473,13 +491,16 @@ end
-- @param Utilities.Utils#FLARECOLOR FlareColor The flare color.
-- @param #number Points (optional) The amount of points in the circle.
-- @param Dcs.DCSTypes#Azimuth Azimuth (optional) Azimuth The azimuth of the flare.
-- @param #number AddHeight (optional) The height to be added for the smoke.
-- @return #ZONE_RADIUS self
function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth )
function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth, AddHeight )
self:F2( { FlareColor, Azimuth } )
local Point = {}
local Vec2 = self:GetVec2()
AddHeight = AddHeight or 0
Points = Points and Points or 360
local Angle
@ -489,7 +510,7 @@ function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth )
local Radial = Angle * RadialBase / 360
Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius()
Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius()
POINT_VEC2:New( Point.x, Point.y ):Flare( FlareColor, Azimuth )
POINT_VEC2:New( Point.x, Point.y, AddHeight ):Flare( FlareColor, Azimuth )
end
return self
@ -562,6 +583,189 @@ function ZONE_RADIUS:GetVec3( Height )
end
--- Scan the zone
-- @param #ZONE_RADIUS self
-- @param ObjectCategories
-- @param Coalition
function ZONE_RADIUS:Scan( ObjectCategories )
self.ScanData = {}
self.ScanData.Coalitions = {}
self.ScanData.Scenery = {}
local ZoneCoord = self:GetCoordinate()
local ZoneRadius = self:GetRadius()
self:E({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()})
local SphereSearch = {
id = world.VolumeType.SPHERE,
params = {
point = ZoneCoord:GetVec3(),
radius = ZoneRadius,
}
}
local function EvaluateZone( ZoneObject )
if ZoneObject:isExist() then
local ObjectCategory = ZoneObject:getCategory()
if ( ObjectCategory == Object.Category.UNIT and ZoneObject:isActive() ) or
ObjectCategory == Object.Category.STATIC then
local CoalitionDCSUnit = ZoneObject:getCoalition()
self.ScanData.Coalitions[CoalitionDCSUnit] = true
self:E( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } )
end
if ObjectCategory == Object.Category.SCENERY then
local SceneryType = ZoneObject:getTypeName()
local SceneryName = ZoneObject:getName()
self.ScanData.Scenery[SceneryType] = self.ScanData.Scenery[SceneryType] or {}
self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( SceneryName, ZoneObject )
self:E( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } )
end
end
return true
end
world.searchObjects( ObjectCategories, SphereSearch, EvaluateZone )
end
function ZONE_RADIUS:CountScannedCoalitions()
local Count = 0
for CoalitionID, Coalition in pairs( self.ScanData.Coalitions ) do
Count = Count + 1
end
return Count
end
--- Get Coalitions of the units in the Zone, or Check if there are units of the given Coalition in the Zone.
-- Returns nil if there are none ot two Coalitions in the zone!
-- Returns one Coalition if there are only Units of one Coalition in the Zone.
-- Returns the Coalition for the given Coalition if there are units of the Coalition in the Zone
-- @param #ZONE_RADIUS self
-- @return #table
function ZONE_RADIUS:GetScannedCoalition( Coalition )
if Coalition then
return self.ScanData.Coalitions[Coalition]
else
local Count = 0
local ReturnCoalition = nil
for CoalitionID, Coalition in pairs( self.ScanData.Coalitions ) do
Count = Count + 1
ReturnCoalition = CoalitionID
end
if Count ~= 1 then
ReturnCoalition = nil
end
return ReturnCoalition
end
end
function ZONE_RADIUS:GetScannedSceneryType( SceneryType )
return self.ScanData.Scenery[SceneryType]
end
function ZONE_RADIUS:GetScannedScenery()
return self.ScanData.Scenery
end
--- Is All in Zone of Coalition?
-- @param #ZONE_RADIUS self
-- @param Coalition
-- @return #boolean
function ZONE_RADIUS:IsAllInZoneOfCoalition( Coalition )
return self:CountScannedCoalitions() == 1 and self:GetScannedCoalition( Coalition ) == true
end
--- Is All in Zone of Other Coalition?
-- @param #ZONE_RADIUS self
-- @param Coalition
-- @return #boolean
function ZONE_RADIUS:IsAllInZoneOfOtherCoalition( Coalition )
self:E( { Coalitions = self.Coalitions, Count = self:CountScannedCoalitions() } )
return self:CountScannedCoalitions() == 1 and self:GetScannedCoalition( Coalition ) == nil
end
--- Is Some in Zone of Coalition?
-- @param #ZONE_RADIUS self
-- @param Coalition
-- @return #boolean
function ZONE_RADIUS:IsSomeInZoneOfCoalition( Coalition )
return self:CountScannedCoalitions() > 1 and self:GetScannedCoalition( Coalition ) == true
end
--- Is None in Zone of Coalition?
-- @param #ZONE_RADIUS self
-- @param Coalition
-- @return #boolean
function ZONE_RADIUS:IsNoneInZoneOfCoalition( Coalition )
return self:GetScannedCoalition( Coalition ) == nil
end
--- Is None in Zone?
-- @param #ZONE_RADIUS self
-- @return #boolean
function ZONE_RADIUS:IsNoneInZone()
return self:CountScannedCoalitions() == 0
end
--- Searches the zone
-- @param #ZONE_RADIUS self
-- @param ObjectCategories A list of categories, which are members of Object.Category
-- @param EvaluateFunction
function ZONE_RADIUS:SearchZone( EvaluateFunction, ObjectCategories )
local SearchZoneResult = true
local ZoneCoord = self:GetCoordinate()
local ZoneRadius = self:GetRadius()
self:E({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()})
local SphereSearch = {
id = world.VolumeType.SPHERE,
params = {
point = ZoneCoord:GetVec3(),
radius = ZoneRadius / 2,
}
}
local function EvaluateZone( ZoneDCSUnit )
env.info( ZoneDCSUnit:getName() )
local ZoneUnit = UNIT:Find( ZoneDCSUnit )
return EvaluateFunction( ZoneUnit )
end
world.searchObjects( Object.Category.UNIT, SphereSearch, EvaluateZone )
end
--- Returns if a location is within the zone.
-- @param #ZONE_RADIUS self
-- @param Dcs.DCSTypes#Vec2 Vec2 The location to test.
@ -645,6 +849,22 @@ function ZONE_RADIUS:GetRandomPointVec3( inner, outer )
end
--- Returns a @{Point#COORDINATE} object reflecting a random 3D location within the zone.
-- @param #ZONE_RADIUS self
-- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0.
-- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.
-- @return Core.Point#COORDINATE
function ZONE_RADIUS:GetRandomCoordinate( inner, outer )
self:F( self.ZoneName, inner, outer )
local Coordinate = COORDINATE:NewFromVec2( self:GetRandomVec2() )
self:T3( { Coordinate = Coordinate } )
return Coordinate
end
--- @type ZONE
-- @extends #ZONE_RADIUS
@ -834,6 +1054,20 @@ function ZONE_GROUP:GetRandomVec2()
return Point
end
--- Returns a @{Point#POINT_VEC2} object reflecting a random 2D location within the zone.
-- @param #ZONE_GROUP self
-- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0.
-- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.
-- @return Core.Point#POINT_VEC2 The @{Point#POINT_VEC2} object reflecting the random 3D location within the zone.
function ZONE_GROUP:GetRandomPointVec2( inner, outer )
self:F( self.ZoneName, inner, outer )
local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() )
self:T3( { PointVec2 } )
return PointVec2
end
--- @type ZONE_POLYGON_BASE
@ -1077,6 +1311,20 @@ function ZONE_POLYGON_BASE:GetRandomPointVec3()
end
--- Return a @{Point#COORDINATE} object representing a random 3D point at landheight within the zone.
-- @param #ZONE_POLYGON_BASE self
-- @return Core.Point#COORDINATE
function ZONE_POLYGON_BASE:GetRandomCoordinate()
self:F2()
local Coordinate = COORDINATE:NewFromVec2( self:GetRandomVec2() )
self:T2( Coordinate )
return Coordinate
end
--- Get the bounding square the zone.
-- @param #ZONE_POLYGON_BASE self
-- @return #ZONE_POLYGON_BASE.BoundingSquare The bounding square.
@ -1114,7 +1362,7 @@ ZONE_POLYGON = {
ClassName="ZONE_POLYGON",
}
--- Constructor to create a ZONE_POLYGON instance, taking the zone name and the name of the @{Group#GROUP} defined within the Mission Editor.
--- Constructor to create a ZONE_POLYGON instance, taking the zone name and the @{Group#GROUP} defined within the Mission Editor.
-- The @{Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected by ZONE_POLYGON.
-- @param #ZONE_POLYGON self
-- @param #string ZoneName Name of the zone.
@ -1130,3 +1378,22 @@ function ZONE_POLYGON:New( ZoneName, ZoneGroup )
return self
end
--- Constructor to create a ZONE_POLYGON instance, taking the zone name and the **name** of the @{Group#GROUP} defined within the Mission Editor.
-- The @{Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected by ZONE_POLYGON.
-- @param #ZONE_POLYGON self
-- @param #string ZoneName Name of the zone.
-- @param #string GroupName The group name of the GROUP defining the waypoints within the Mission Editor to define the polygon shape.
-- @return #ZONE_POLYGON self
function ZONE_POLYGON:NewFromGroupName( ZoneName, GroupName )
local ZoneGroup = GROUP:FindByName( GroupName )
local GroupPoints = ZoneGroup:GetTaskRoute()
local self = BASE:Inherit( self, ZONE_POLYGON_BASE:New( ZoneName, GroupPoints ) )
self:F( { ZoneName, ZoneGroup, self._.Polygon } )
return self
end

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,136 +1,234 @@
--- **Functional** -- The CLEANUP class keeps an area clean of crashing or colliding airplanes. It also prevents airplanes from firing within this area.
--- **Functional** -- The CLEANUP_AIRBASE class keeps an area clean of crashing or colliding airplanes. It also prevents airplanes from firing within this area.
--
-- ===
--
-- ### Author: **Sven Van de Velde (FlightControl)**
-- ### Contributions:
--
-- ====
--
-- @module CleanUp
--- The CLEANUP class.
-- @type CLEANUP
--- @type CLEANUP_AIRBASE.__ Methods which are not intended for mission designers, but which are used interally by the moose designer :-)
-- @field #map<#string,Wrapper.Airbase#AIRBASE> Airbases Map of Airbases.
-- @extends Core.Base#BASE
CLEANUP = {
ClassName = "CLEANUP",
ZoneNames = {},
TimeInterval = 300,
--- @type CLEANUP_AIRBASE
-- @extends #CLEANUP_AIRBASE.__
--- # CLEANUP_AIRBASE, extends @{Base#BASE}
--
-- ![Banner Image](..\Presentations\CLEANUP_AIRBASE\Dia1.JPG)
--
-- The CLEANUP_AIRBASE class keeps airbases clean, and tries to guarantee continuous airbase operations, even under combat.
-- Specific airbases need to be provided that need to be guarded. Each airbase registered, will be guarded within a zone of 8 km around the airbase.
-- Any unit that fires a missile, or shoots within the zone of an airbase, will be monitored by CLEANUP_AIRBASE.
-- Within the 8km zone, units cannot fire any missile, which prevents the airbase runway to receive missile or bomb hits.
-- Any airborne or ground unit that is on the runway below 30 meters (default value) will be automatically removed if it is damaged.
--
-- This is not a full 100% secure implementation. It is still possible that CLEANUP_AIRBASE cannot prevent (in-time) to keep the airbase clean.
-- The following situations may happen that will still stop the runway of an airbase:
--
-- * A damaged unit is not removed on time when above the runway, and crashes on the runway.
-- * A bomb or missile is still able to dropped on the runway.
-- * Units collide on the airbase, and could not be removed on time.
--
-- When a unit is within the airbase zone and needs to be monitored,
-- its status will be checked every 0.25 seconds! This is required to ensure that the airbase is kept clean.
-- But as a result, there is more CPU overload.
--
-- So as an advise, I suggest you use the CLEANUP_AIRBASE class with care:
--
-- * Only monitor airbases that really need to be monitored!
-- * Try not to monitor airbases that are likely to be invaded by enemy troops.
-- For these airbases, there is little use to keep them clean, as they will be invaded anyway...
--
-- By following the above guidelines, you can add airbase cleanup with acceptable CPU overhead.
--
-- ## 1. CLEANUP_AIRBASE Constructor
--
-- Creates the main object which is preventing the airbase to get polluted with debris on the runway, which halts the airbase.
--
-- -- Clean these Zones.
-- CleanUpAirports = CLEANUP_AIRBASE:New( { AIRBASE.Caucasus.Tbilisi, AIRBASE.Caucasus.Kutaisi )
--
-- -- or
-- CleanUpTbilisi = CLEANUP_AIRBASE:New( AIRBASE.Caucasus.Tbilisi )
-- CleanUpKutaisi = CLEANUP_AIRBASE:New( AIRBASE.Caucasus.Kutaisi )
--
-- ## 2. Add or Remove airbases
--
-- The method @{#CLEANUP_AIRBASE.AddAirbase}() to add an airbase to the cleanup validation process.
-- The method @{#CLEANUP_AIRBASE.RemoveAirbase}() removes an airbase from the cleanup validation process.
--
-- ## 3. Clean missiles and bombs within the airbase zone.
--
-- When missiles or bombs hit the runway, the airbase operations stop.
-- Use the method @{#CLEANUP_AIRBASE.SetCleanMissiles}() to control the cleaning of missiles, which will prevent airbases to stop.
-- Note that this method will not allow anymore airbases to be attacked, so there is a trade-off here to do.
--
-- @field #CLEANUP_AIRBASE
CLEANUP_AIRBASE = {
ClassName = "CLEANUP_AIRBASE",
TimeInterval = 0.2,
CleanUpList = {},
}
-- @field #CLEANUP_AIRBASE.__
CLEANUP_AIRBASE.__ = {}
--- @field #CLEANUP_AIRBASE.__.Airbases
CLEANUP_AIRBASE.__.Airbases = {}
--- Creates the main object which is handling the cleaning of the debris within the given Zone Names.
-- @param #CLEANUP self
-- @param #table ZoneNames Is a table of zone names where the debris should be cleaned. Also a single string can be passed with one zone name.
-- @param #number TimeInterval The interval in seconds when the clean activity takes place. The default is 300 seconds, thus every 5 minutes.
-- @return #CLEANUP
-- @param #CLEANUP_AIRBASE self
-- @param #list<#string> AirbaseNames Is a table of airbase names where the debris should be cleaned. Also a single string can be passed with one airbase name.
-- @return #CLEANUP_AIRBASE
-- @usage
-- -- Clean these Zones.
-- CleanUpAirports = CLEANUP:New( { 'CLEAN Tbilisi', 'CLEAN Kutaisi' }, 150 )
-- CleanUpAirports = CLEANUP_AIRBASE:New( { AIRBASE.Caucasus.Tbilisi, AIRBASE.Caucasus.Kutaisi )
-- or
-- CleanUpTbilisi = CLEANUP:New( 'CLEAN Tbilisi', 150 )
-- CleanUpKutaisi = CLEANUP:New( 'CLEAN Kutaisi', 600 )
function CLEANUP:New( ZoneNames, TimeInterval )
-- CleanUpTbilisi = CLEANUP_AIRBASE:New( AIRBASE.Caucasus.Tbilisi )
-- CleanUpKutaisi = CLEANUP_AIRBASE:New( AIRBASE.Caucasus.Kutaisi )
function CLEANUP_AIRBASE:New( AirbaseNames )
local self = BASE:Inherit( self, BASE:New() ) -- #CLEANUP
self:F( { ZoneNames, TimeInterval } )
local self = BASE:Inherit( self, BASE:New() ) -- #CLEANUP_AIRBASE
self:F( { AirbaseNames } )
if type( ZoneNames ) == 'table' then
self.ZoneNames = ZoneNames
if type( AirbaseNames ) == 'table' then
for AirbaseID, AirbaseName in pairs( AirbaseNames ) do
self:AddAirbase( AirbaseName )
end
else
self.ZoneNames = { ZoneNames }
end
if TimeInterval then
self.TimeInterval = TimeInterval
local AirbaseName = AirbaseNames
self:AddAirbase( AirbaseName )
end
self:HandleEvent( EVENTS.Birth )
self:HandleEvent( EVENTS.Birth, self.__.OnEventBirth )
self.CleanUpScheduler = SCHEDULER:New( self, self._CleanUpScheduler, {}, 1, TimeInterval )
self.__.CleanUpScheduler = SCHEDULER:New( self, self.__.CleanUpSchedule, {}, 1, self.TimeInterval )
self:HandleEvent( EVENTS.EngineShutdown , self.__.EventAddForCleanUp )
self:HandleEvent( EVENTS.EngineStartup, self.__.EventAddForCleanUp )
self:HandleEvent( EVENTS.Hit, self.__.EventAddForCleanUp )
self:HandleEvent( EVENTS.PilotDead, self.__.OnEventCrash )
self:HandleEvent( EVENTS.Dead, self.__.OnEventCrash )
self:HandleEvent( EVENTS.Crash, self.__.OnEventCrash )
return self
end
--- Destroys a group from the simulator, but checks first if it is still existing!
-- @param #CLEANUP self
-- @param Dcs.DCSWrapper.Group#Group GroupObject The object to be destroyed.
-- @param #string CleanUpGroupName The groupname...
function CLEANUP:_DestroyGroup( GroupObject, CleanUpGroupName )
self:F( { GroupObject, CleanUpGroupName } )
if GroupObject then -- and GroupObject:isExist() then
trigger.action.deactivateGroup(GroupObject)
self:T( { "GroupObject Destroyed", GroupObject } )
end
--- Adds an airbase to the airbase validation list.
-- @param #CLEANUP_AIRBASE self
-- @param #string AirbaseName
-- @return #CLEANUP_AIRBASE
function CLEANUP_AIRBASE:AddAirbase( AirbaseName )
self.__.Airbases[AirbaseName] = AIRBASE:FindByName( AirbaseName )
self:F({"Airbase:", AirbaseName, self.__.Airbases[AirbaseName]:GetDesc()})
return self
end
--- Destroys a @{DCSWrapper.Unit#Unit} from the simulator, but checks first if it is still existing!
-- @param #CLEANUP self
-- @param Dcs.DCSWrapper.Unit#Unit CleanUpUnit The object to be destroyed.
-- @param #string CleanUpUnitName The Unit name ...
function CLEANUP:_DestroyUnit( CleanUpUnit, CleanUpUnitName )
self:F( { CleanUpUnit, CleanUpUnitName } )
--- Removes an airbase from the airbase validation list.
-- @param #CLEANUP_AIRBASE self
-- @param #string AirbaseName
-- @return #CLEANUP_AIRBASE
function CLEANUP_AIRBASE:RemoveAirbase( AirbaseName )
self.__.Airbases[AirbaseName] = nil
return self
end
--- Enables or disables the cleaning of missiles within the airbase zones.
-- Airbase operations stop when a missile or bomb is dropped at a runway.
-- Note that when this method is used, the airbase operations won't stop if
-- the missile or bomb was cleaned within the airbase zone, which is 8km from the center of the airbase.
-- However, there is a trade-off to make. Attacks on airbases won't be possible anymore if this method is used.
-- Note, one can also use the method @{#CLEANUP_AIRBASE.RemoveAirbase}() to remove the airbase from the control process as a whole,
-- when an enemy unit is near. That is also an option...
-- @param #CLEANUP_AIRBASE self
-- @param #string CleanMissiles (Default=true) If true, missiles fired are immediately destroyed. If false missiles are not controlled.
-- @return #CLEANUP_AIRBASE
function CLEANUP_AIRBASE:SetCleanMissiles( CleanMissiles )
if CleanMissiles then
self:HandleEvent( EVENTS.Shot, self.__.OnEventShot )
else
self:UnHandleEvent( EVENTS.Shot )
end
end
function CLEANUP_AIRBASE.__:IsInAirbase( Vec2 )
local InAirbase = false
for AirbaseName, Airbase in pairs( self.__.Airbases ) do
local Airbase = Airbase -- Wrapper.Airbase#AIRBASE
if Airbase:GetZone():IsVec2InZone( Vec2 ) then
InAirbase = true
break;
end
end
return InAirbase
end
--- Destroys a @{Unit} from the simulator, but checks first if it is still existing!
-- @param #CLEANUP_AIRBASE self
-- @param Wrapper.Unit#UNIT CleanUpUnit The object to be destroyed.
function CLEANUP_AIRBASE.__:DestroyUnit( CleanUpUnit )
self:F( { CleanUpUnit } )
if CleanUpUnit then
local CleanUpGroup = Unit.getGroup(CleanUpUnit)
local CleanUpUnitName = CleanUpUnit:GetName()
local CleanUpGroup = CleanUpUnit:GetGroup()
-- TODO Client bug in 1.5.3
if CleanUpGroup and CleanUpGroup:isExist() then
local CleanUpGroupUnits = CleanUpGroup:getUnits()
if CleanUpGroup:IsAlive() then
local CleanUpGroupUnits = CleanUpGroup:GetUnits()
if #CleanUpGroupUnits == 1 then
local CleanUpGroupName = CleanUpGroup:getName()
--self:CreateEventCrash( timer.getTime(), CleanUpUnit )
CleanUpGroup:destroy()
self:T( { "Destroyed Group:", CleanUpGroupName } )
local CleanUpGroupName = CleanUpGroup:GetName()
CleanUpGroup:Destroy()
else
CleanUpUnit:destroy()
self:T( { "Destroyed Unit:", CleanUpUnitName } )
CleanUpUnit:Destroy()
end
self.CleanUpList[CleanUpUnitName] = nil -- Cleaning from the list
CleanUpUnit = nil
self.CleanUpList[CleanUpUnitName] = nil
end
end
end
-- TODO check Dcs.DCSTypes#Weapon
--- Destroys a missile from the simulator, but checks first if it is still existing!
-- @param #CLEANUP self
-- @param Dcs.DCSTypes#Weapon MissileObject
function CLEANUP:_DestroyMissile( MissileObject )
self:F( { MissileObject } )
--- Destroys a missile from the simulator, but checks first if it is still existing!
-- @param #CLEANUP_AIRBASE self
-- @param Dcs.DCSTypes#Weapon MissileObject
function CLEANUP_AIRBASE.__:DestroyMissile( MissileObject )
self:F( { MissileObject } )
if MissileObject and MissileObject:isExist() then
MissileObject:destroy()
self:T( "MissileObject Destroyed")
end
end
--- @param #CLEANUP self
--- @param #CLEANUP_AIRBASE self
-- @param Core.Event#EVENTDATA EventData
function CLEANUP:_OnEventBirth( EventData )
function CLEANUP_AIRBASE.__:OnEventBirth( EventData )
self:F( { EventData } )
self.CleanUpList[EventData.IniDCSUnitName] = {}
self.CleanUpList[EventData.IniDCSUnitName].CleanUpUnit = EventData.IniDCSUnit
self.CleanUpList[EventData.IniDCSUnitName].CleanUpGroup = EventData.IniDCSGroup
self.CleanUpList[EventData.IniDCSUnitName].CleanUpUnit = EventData.IniUnit
self.CleanUpList[EventData.IniDCSUnitName].CleanUpGroup = EventData.IniGroup
self.CleanUpList[EventData.IniDCSUnitName].CleanUpGroupName = EventData.IniDCSGroupName
self.CleanUpList[EventData.IniDCSUnitName].CleanUpUnitName = EventData.IniDCSUnitName
EventData.IniUnit:HandleEvent( EVENTS.EngineShutdown , self._EventAddForCleanUp )
EventData.IniUnit:HandleEvent( EVENTS.EngineStartup, self._EventAddForCleanUp )
EventData.IniUnit:HandleEvent( EVENTS.Hit, self._EventAddForCleanUp )
EventData.IniUnit:HandleEvent( EVENTS.PilotDead, self._EventCrash )
EventData.IniUnit:HandleEvent( EVENTS.Dead, self._EventCrash )
EventData.IniUnit:HandleEvent( EVENTS.Crash, self._EventCrash )
EventData.IniUnit:HandleEvent( EVENTS.Shot, self._EventShot )
end
--- Detects if a crash event occurs.
-- Crashed units go into a CleanUpList for removal.
-- @param #CLEANUP self
-- @param Dcs.DCSTypes#Event event
function CLEANUP:_EventCrash( Event )
-- @param #CLEANUP_AIRBASE self
-- @param Core.Event#EVENTDATA Event
function CLEANUP_AIRBASE.__:OnEventCrash( Event )
self:F( { Event } )
--TODO: This stuff is not working due to a DCS bug. Burning units cannot be destroyed.
@ -141,171 +239,164 @@ function CLEANUP:_EventCrash( Event )
-- self:T("after deactivateGroup")
-- event.initiator:destroy()
self.CleanUpList[Event.IniDCSUnitName] = {}
self.CleanUpList[Event.IniDCSUnitName].CleanUpUnit = Event.IniDCSUnit
self.CleanUpList[Event.IniDCSUnitName].CleanUpGroup = Event.IniDCSGroup
self.CleanUpList[Event.IniDCSUnitName].CleanUpGroupName = Event.IniDCSGroupName
self.CleanUpList[Event.IniDCSUnitName].CleanUpUnitName = Event.IniDCSUnitName
if Event.IniDCSUnitName and Event.IniCategory == Object.Category.UNIT then
self.CleanUpList[Event.IniDCSUnitName] = {}
self.CleanUpList[Event.IniDCSUnitName].CleanUpUnit = Event.IniUnit
self.CleanUpList[Event.IniDCSUnitName].CleanUpGroup = Event.IniGroup
self.CleanUpList[Event.IniDCSUnitName].CleanUpGroupName = Event.IniDCSGroupName
self.CleanUpList[Event.IniDCSUnitName].CleanUpUnitName = Event.IniDCSUnitName
end
end
--- Detects if a unit shoots a missile.
-- If this occurs within one of the zones, then the weapon used must be destroyed.
-- @param #CLEANUP self
-- @param Dcs.DCSTypes#Event event
function CLEANUP:_EventShot( Event )
-- If this occurs within one of the airbases, then the weapon used must be destroyed.
-- @param #CLEANUP_AIRBASE self
-- @param Core.Event#EVENTDATA Event
function CLEANUP_AIRBASE.__:OnEventShot( Event )
self:F( { Event } )
-- Test if the missile was fired within one of the CLEANUP.ZoneNames.
local CurrentLandingZoneID = 0
CurrentLandingZoneID = routines.IsUnitInZones( Event.IniDCSUnit, self.ZoneNames )
if ( CurrentLandingZoneID ) then
-- Okay, the missile was fired within the CLEANUP.ZoneNames, destroy the fired weapon.
--_SEADmissile:destroy()
SCHEDULER:New( self, CLEANUP._DestroyMissile, { Event.Weapon }, 0.1 )
-- Test if the missile was fired within one of the CLEANUP_AIRBASE.AirbaseNames.
if self:IsInAirbase( Event.IniUnit:GetVec2() ) then
-- Okay, the missile was fired within the CLEANUP_AIRBASE.AirbaseNames, destroy the fired weapon.
self:DestroyMissile( Event.Weapon )
end
end
--- Detects if the Unit has an S_EVENT_HIT within the given ZoneNames. If this is the case, destroy the unit.
-- @param #CLEANUP self
-- @param Dcs.DCSTypes#Event event
function CLEANUP:_EventHitCleanUp( Event )
--- Detects if the Unit has an S_EVENT_HIT within the given AirbaseNames. If this is the case, destroy the unit.
-- @param #CLEANUP_AIRBASE self
-- @param Core.Event#EVENTDATA Event
function CLEANUP_AIRBASE.__:OnEventHit( Event )
self:F( { Event } )
if Event.IniDCSUnit then
if routines.IsUnitInZones( Event.IniDCSUnit, self.ZoneNames ) ~= nil then
self:T( { "Life: ", Event.IniDCSUnitName, ' = ', Event.IniDCSUnit:getLife(), "/", Event.IniDCSUnit:getLife0() } )
if Event.IniDCSUnit:getLife() < Event.IniDCSUnit:getLife0() then
if Event.IniUnit then
if self:IsInAirbase( Event.IniUnit:GetVec2() ) then
self:T( { "Life: ", Event.IniDCSUnitName, ' = ', Event.IniUnit:GetLife(), "/", Event.IniUnit:GetLife0() } )
if Event.IniUnit:GetLife() < Event.IniUnit:GetLife0() then
self:T( "CleanUp: Destroy: " .. Event.IniDCSUnitName )
SCHEDULER:New( self, CLEANUP._DestroyUnit, { Event.IniDCSUnit }, 0.1 )
CLEANUP_AIRBASE.__:DestroyUnit( Event.IniUnit )
end
end
end
if Event.TgtDCSUnit then
if routines.IsUnitInZones( Event.TgtDCSUnit, self.ZoneNames ) ~= nil then
self:T( { "Life: ", Event.TgtDCSUnitName, ' = ', Event.TgtDCSUnit:getLife(), "/", Event.TgtDCSUnit:getLife0() } )
if Event.TgtDCSUnit:getLife() < Event.TgtDCSUnit:getLife0() then
if Event.TgtUnit then
if self:IsInAirbase( Event.TgtUnit:GetVec2() ) then
self:T( { "Life: ", Event.TgtDCSUnitName, ' = ', Event.TgtUnit:GetLife(), "/", Event.TgtUnit:GetLife0() } )
if Event.TgtUnit:GetLife() < Event.TgtUnit:GetLife0() then
self:T( "CleanUp: Destroy: " .. Event.TgtDCSUnitName )
SCHEDULER:New( self, CLEANUP._DestroyUnit, { Event.TgtDCSUnit }, 0.1 )
CLEANUP_AIRBASE.__:DestroyUnit( Event.TgtUnit )
end
end
end
end
--- Add the @{DCSWrapper.Unit#Unit} to the CleanUpList for CleanUp.
function CLEANUP:_AddForCleanUp( CleanUpUnit, CleanUpUnitName )
-- @param #CLEANUP_AIRBASE self
-- @param Wrapper.Unit#UNIT CleanUpUnit
-- @oaram #string CleanUpUnitName
function CLEANUP_AIRBASE.__:AddForCleanUp( CleanUpUnit, CleanUpUnitName )
self:F( { CleanUpUnit, CleanUpUnitName } )
self.CleanUpList[CleanUpUnitName] = {}
self.CleanUpList[CleanUpUnitName].CleanUpUnit = CleanUpUnit
self.CleanUpList[CleanUpUnitName].CleanUpUnitName = CleanUpUnitName
self.CleanUpList[CleanUpUnitName].CleanUpGroup = Unit.getGroup(CleanUpUnit)
self.CleanUpList[CleanUpUnitName].CleanUpGroupName = Unit.getGroup(CleanUpUnit):getName()
local CleanUpGroup = CleanUpUnit:GetGroup()
self.CleanUpList[CleanUpUnitName].CleanUpGroup = CleanUpGroup
self.CleanUpList[CleanUpUnitName].CleanUpGroupName = CleanUpGroup:GetName()
self.CleanUpList[CleanUpUnitName].CleanUpTime = timer.getTime()
self.CleanUpList[CleanUpUnitName].CleanUpMoved = false
self:T( { "CleanUp: Add to CleanUpList: ", Unit.getGroup(CleanUpUnit):getName(), CleanUpUnitName } )
self:T( { "CleanUp: Add to CleanUpList: ", CleanUpGroup:GetName(), CleanUpUnitName } )
end
--- Detects if the Unit has an S_EVENT_ENGINE_SHUTDOWN or an S_EVENT_HIT within the given ZoneNames. If this is the case, add the Group to the CLEANUP List.
-- @param #CLEANUP self
-- @param Dcs.DCSTypes#Event event
function CLEANUP:_EventAddForCleanUp( Event )
--- Detects if the Unit has an S_EVENT_ENGINE_SHUTDOWN or an S_EVENT_HIT within the given AirbaseNames. If this is the case, add the Group to the CLEANUP_AIRBASE List.
-- @param #CLEANUP_AIRBASE.__ self
-- @param Core.Event#EVENTDATA Event
function CLEANUP_AIRBASE.__:EventAddForCleanUp( Event )
if Event.IniDCSUnit then
self:F({Event})
if Event.IniDCSUnit and Event.IniCategory == Object.Category.UNIT then
if self.CleanUpList[Event.IniDCSUnitName] == nil then
if routines.IsUnitInZones( Event.IniDCSUnit, self.ZoneNames ) ~= nil then
self:_AddForCleanUp( Event.IniDCSUnit, Event.IniDCSUnitName )
if self:IsInAirbase( Event.IniUnit:GetVec2() ) then
self:AddForCleanUp( Event.IniUnit, Event.IniDCSUnitName )
end
end
end
if Event.TgtDCSUnit then
if Event.TgtDCSUnit and Event.TgtCategory == Object.Category.UNIT then
if self.CleanUpList[Event.TgtDCSUnitName] == nil then
if routines.IsUnitInZones( Event.TgtDCSUnit, self.ZoneNames ) ~= nil then
self:_AddForCleanUp( Event.TgtDCSUnit, Event.TgtDCSUnitName )
if self:IsInAirbase( Event.TgtUnit:GetVec2() ) then
self:AddForCleanUp( Event.TgtUnit, Event.TgtDCSUnitName )
end
end
end
end
local CleanUpSurfaceTypeText = {
"LAND",
"SHALLOW_WATER",
"WATER",
"ROAD",
"RUNWAY"
}
--- At the defined time interval, CleanUp the Groups within the CleanUpList.
-- @param #CLEANUP self
function CLEANUP:_CleanUpScheduler()
self:F( { "CleanUp Scheduler" } )
-- @param #CLEANUP_AIRBASE self
function CLEANUP_AIRBASE.__:CleanUpSchedule()
local CleanUpCount = 0
for CleanUpUnitName, UnitData in pairs( self.CleanUpList ) do
for CleanUpUnitName, CleanUpListData in pairs( self.CleanUpList ) do
CleanUpCount = CleanUpCount + 1
self:T( { CleanUpUnitName, UnitData } )
local CleanUpUnit = Unit.getByName(UnitData.CleanUpUnitName)
local CleanUpGroupName = UnitData.CleanUpGroupName
local CleanUpUnitName = UnitData.CleanUpUnitName
if CleanUpUnit then
self:T( { "CleanUp Scheduler", "Checking:", CleanUpUnitName } )
local CleanUpUnit = CleanUpListData.CleanUpUnit -- Wrapper.Unit#UNIT
local CleanUpGroupName = CleanUpListData.CleanUpGroupName
if CleanUpUnit:IsAlive() ~= nil then
if _DATABASE:GetStatusGroup( CleanUpGroupName ) ~= "ReSpawn" then
local CleanUpUnitVec3 = CleanUpUnit:getPoint()
--self:T( CleanUpUnitVec3 )
local CleanUpUnitVec2 = {}
CleanUpUnitVec2.x = CleanUpUnitVec3.x
CleanUpUnitVec2.y = CleanUpUnitVec3.z
--self:T( CleanUpUnitVec2 )
local CleanUpSurfaceType = land.getSurfaceType(CleanUpUnitVec2)
--self:T( CleanUpSurfaceType )
if CleanUpUnit and CleanUpUnit:getLife() <= CleanUpUnit:getLife0() * 0.95 then
if CleanUpSurfaceType == land.SurfaceType.RUNWAY then
if CleanUpUnit:inAir() then
local CleanUpLandHeight = land.getHeight(CleanUpUnitVec2)
local CleanUpUnitHeight = CleanUpUnitVec3.y - CleanUpLandHeight
self:T( { "CleanUp Scheduler", "Height = " .. CleanUpUnitHeight } )
if CleanUpUnitHeight < 30 then
local CleanUpCoordinate = CleanUpUnit:GetCoordinate()
self:T( { "CleanUp Scheduler", CleanUpUnitName } )
if CleanUpUnit:GetLife() <= CleanUpUnit:GetLife0() * 0.95 then
if CleanUpUnit:IsAboveRunway() then
if CleanUpUnit:InAir() then
local CleanUpLandHeight = CleanUpCoordinate:GetLandHeight()
local CleanUpUnitHeight = CleanUpCoordinate.y - CleanUpLandHeight
if CleanUpUnitHeight < 100 then
self:T( { "CleanUp Scheduler", "Destroy " .. CleanUpUnitName .. " because below safe height and damaged." } )
self:_DestroyUnit(CleanUpUnit, CleanUpUnitName)
self:DestroyUnit( CleanUpUnit )
end
else
self:T( { "CleanUp Scheduler", "Destroy " .. CleanUpUnitName .. " because on runway and damaged." } )
self:_DestroyUnit(CleanUpUnit, CleanUpUnitName)
self:DestroyUnit( CleanUpUnit )
end
end
end
-- Clean Units which are waiting for a very long time in the CleanUpZone.
if CleanUpUnit then
local CleanUpUnitVelocity = CleanUpUnit:getVelocity()
local CleanUpUnitVelocityTotal = math.abs(CleanUpUnitVelocity.x) + math.abs(CleanUpUnitVelocity.y) + math.abs(CleanUpUnitVelocity.z)
if CleanUpUnitVelocityTotal < 1 then
if UnitData.CleanUpMoved then
if UnitData.CleanUpTime + 180 <= timer.getTime() then
local CleanUpUnitVelocity = CleanUpUnit:GetVelocityKMH()
if CleanUpUnitVelocity < 1 then
if CleanUpListData.CleanUpMoved then
if CleanUpListData.CleanUpTime + 180 <= timer.getTime() then
self:T( { "CleanUp Scheduler", "Destroy due to not moving anymore " .. CleanUpUnitName } )
self:_DestroyUnit(CleanUpUnit, CleanUpUnitName)
self:DestroyUnit( CleanUpUnit )
end
end
else
UnitData.CleanUpTime = timer.getTime()
UnitData.CleanUpMoved = true
CleanUpListData.CleanUpTime = timer.getTime()
CleanUpListData.CleanUpMoved = true
end
end
else
-- Do nothing ...
self.CleanUpList[CleanUpUnitName] = nil -- Not anymore in the DCSRTE
self.CleanUpList[CleanUpUnitName] = nil
end
else
self:T( "CleanUp: Group " .. CleanUpUnitName .. " cannot be found in DCS RTE, removing ..." )
self.CleanUpList[CleanUpUnitName] = nil -- Not anymore in the DCSRTE
self.CleanUpList[CleanUpUnitName] = nil
end
end
self:T(CleanUpCount)

View File

@ -1,4 +1,4 @@
--- **Functional** -- Management of target **Designation**.
--- **Functional** -- Management of target **Designation**. Lase, smoke and illuminate targets.
--
-- --![Banner Image](..\Presentations\DESIGNATE\Dia1.JPG)
--
@ -70,10 +70,14 @@ do -- DESIGNATE
-- The RecceSet is continuously detecting for potential Targets, executing its task as part of the DetectionObject.
-- Once Targets have been detected, the DesignateObject will trigger the **Detect Event**.
--
-- In order to prevent an overflow in the DesignateObject of detected targets, there is a maximum
-- amount of DetectionItems that can be put in **scope** of the DesignateObject.
-- We call this the **MaximumDesignations** term.
--
-- As part of the Detect Event, the DetectionItems list is used by the DesignateObject to provide the Players with:
--
-- * The RecceGroups are reporting to each AttackGroup, sending **Messages** containing the Threat Level and the TargetSet composition.
-- * **Menu options** are created and updated for each AttackGroup, containing the Threat Level and the TargetSet composition.
-- * **Menu options** are created and updated for each AttackGroup, containing the Detection ID and the Coordinates.
--
-- A Player can then select an action from the Designate Menu.
--
@ -109,7 +113,7 @@ do -- DESIGNATE
--
-- ### 2.1 DESIGNATE States
--
-- * **Designating** ( Group ): The process is not started yet.
-- * **Designating** ( Group ): The designation process.
--
-- ### 2.2 DESIGNATE Events
--
@ -119,9 +123,17 @@ do -- DESIGNATE
-- * **@{#DESIGNATE.Smoke}**: Smoke the targets with the specified Index.
-- * **@{#DESIGNATE.Status}**: Report designation status.
--
-- ## 3. Laser codes
-- ## 3. Maximum Designations
--
-- ### 3.1 Set possible laser codes
-- In order to prevent an overflow of designations due to many Detected Targets, there is a
-- Maximum Designations scope that is set in the DesignationObject.
--
-- The method @{#DESIGNATE.SetMaximumDesignations}() will put a limit on the amount of designations put in scope of the DesignationObject.
-- Using the menu system, the player can "forget" a designation, so that gradually a new designation can be put in scope when detected.
--
-- ## 4. Laser codes
--
-- ### 4.1. Set possible laser codes
--
-- An array of laser codes can be provided, that will be used by the DESIGNATE when lasing.
-- The laser code is communicated by the Recce when it is lasing a larget.
@ -139,11 +151,20 @@ do -- DESIGNATE
--
-- The above sets a collection of possible laser codes that can be assigned. **Note the { } notation!**
--
-- ### 3.2 Auto generate laser codes
-- ### 4.2. Auto generate laser codes
--
-- Use the method @{#DESIGNATE.GenerateLaserCodes}() to generate all possible laser codes. Logic implemented and advised by Ciribob!
--
-- ## 4. Autolase to automatically lase detected targets.
-- ### 4.3. Add specific lase codes to the lase menu
--
-- Certain plane types can only drop laser guided ordonnance when targets are lased with specific laser codes.
-- The SU-25T needs targets to be lased using laser code 1113.
-- The A-10A needs targets to be lased using laser code 1680.
--
-- The method @{#DESIGNATE.AddMenuLaserCode}() to allow a player to lase a target using a specific laser code.
-- Remove such a lase menu option using @{#DESIGNATE.RemoveMenuLaserCode}().
--
-- ## 5. Autolase to automatically lase detected targets.
--
-- DetectionItems can be auto lased once detected by Recces. As such, there is almost no action required from the Players using the Designate Menu.
-- The **auto lase** function can be activated through the Designation Menu.
@ -154,7 +175,7 @@ do -- DESIGNATE
--
-- Activate the auto lasing.
--
-- ## 5. Target prioritization on threat level
-- ## 6. Target prioritization on threat level
--
-- Targets can be detected of different types in one DetectionItem. Depending on the type of the Target, a different threat level applies in an Air to Ground combat context.
-- SAMs are of a higher threat than normal tanks. So, if the Target type was recognized, the Recces will select those targets that form the biggest threat first,
@ -167,7 +188,12 @@ do -- DESIGNATE
--
-- The example will activate the threat level prioritization for this the Designate object. Threats will be marked based on the threat level of the Target.
--
-- ## 6. Status Report
-- ## 6. Designate Menu Location for a Mission
--
-- You can make DESIGNATE work for a @{Mission#MISSION} object. In this way, the designate menu will not appear in the root of the radio menu, but in the menu of the Mission.
-- Use the method @{#DESIGNATE.SetMission}() to set the @{Mission} object for the designate function.
--
-- ## 7. Status Report
--
-- A status report is available that displays the current Targets detected, grouped per DetectionItem, and a list of which Targets are currently being marked.
--
@ -182,7 +208,6 @@ do -- DESIGNATE
-- The example will activate the flashing of the status menu for this Designate object.
--
-- @field #DESIGNATE
--
DESIGNATE = {
ClassName = "DESIGNATE",
}
@ -192,8 +217,9 @@ do -- DESIGNATE
-- @param Tasking.CommandCenter#COMMANDCENTER CC
-- @param Functional.Detection#DETECTION_BASE Detection
-- @param Core.Set#SET_GROUP AttackSet The Attack collection of GROUP objects to designate and report for.
-- @param Tasking.Mission#MISSION Mission (Optional) The Mission where the menu needs to be attached.
-- @return #DESIGNATE
function DESIGNATE:New( CC, Detection, AttackSet )
function DESIGNATE:New( CC, Detection, AttackSet, Mission )
local self = BASE:Inherit( self, FSM:New() ) -- #DESIGNATE
self:F( { Detection } )
@ -360,22 +386,33 @@ do -- DESIGNATE
self.RecceSet = Detection:GetDetectionSetGroup()
self.Recces = {}
self.Designating = {}
self:SetDesignateName()
self.LaseDuration = 60
self:SetFlashStatusMenu( false )
self:SetDesignateMenu()
self:SetMission( Mission )
self:SetLaserCodes( 1688 ) -- set self.LaserCodes
self:SetAutoLase( false ) -- set self.Autolase
self:SetLaserCodes( { 1688, 1130, 4785, 6547, 1465, 4578 } ) -- set self.LaserCodes
self:SetAutoLase( false, false ) -- set self.Autolase and don't send message.
self:SetThreatLevelPrioritization( false ) -- self.ThreatLevelPrioritization, default is threat level priorization off
self:SetMaximumDesignations( 5 ) -- Sets the maximum designations. The default is 5 designations.
self:SetMaximumDistanceDesignations( 12000 ) -- Sets the maximum distance on which designations can be accepted. The default is 8000 meters.
self:SetMaximumMarkings( 2 ) -- Per target group, a maximum of 2 markings will be made by default.
self:SetDesignateMenu()
self.LaserCodesUsed = {}
self.MenuLaserCodes = {} -- This map contains the laser codes that will be shown in the designate menu to lase with specific laser codes.
self.Detection:__Start( 2 )
self:__Detect( -15 )
self.MarkScheduler = SCHEDULER:New( self )
return self
end
@ -399,6 +436,56 @@ do -- DESIGNATE
end
--- Set the maximum amount of designations.
-- @param #DESIGNATE self
-- @param #number MaximumDesignations
-- @return #DESIGNATE
function DESIGNATE:SetMaximumDesignations( MaximumDesignations )
self.MaximumDesignations = MaximumDesignations
return self
end
--- Set the maximum ground designation distance.
-- @param #DESIGNATE self
-- @param #number MaximumDistanceGroundDesignation Maximum ground designation distance in meters.
-- @return #DESIGNATE
function DESIGNATE:SetMaximumDistanceGroundDesignation( MaximumDistanceGroundDesignation )
self.MaximumDistanceGroundDesignation = MaximumDistanceGroundDesignation
return self
end
--- Set the maximum air designation distance.
-- @param #DESIGNATE self
-- @param #number MaximumDistanceAirDesignation Maximum air designation distance in meters.
-- @return #DESIGNATE
function DESIGNATE:SetMaximumDistanceAirDesignation( MaximumDistanceAirDesignation )
self.MaximumDistanceAirDesignation = MaximumDistanceAirDesignation
return self
end
--- Set the overall maximum distance when designations can be accepted.
-- @param #DESIGNATE self
-- @param #number MaximumDistanceDesignations Maximum distance in meters to accept designations.
-- @return #DESIGNATE
function DESIGNATE:SetMaximumDistanceDesignations( MaximumDistanceDesignations )
self.MaximumDistanceDesignations = MaximumDistanceDesignations
return self
end
--- Set the maximum amount of markings FACs will do, per designated target group.
-- @param #DESIGNATE self
-- @param #number MaximumMarkings Maximum markings FACs will do, per designated target group.
-- @return #DESIGNATE
function DESIGNATE:SetMaximumMarkings( MaximumMarkings )
self.MaximumMarkings = MaximumMarkings
return self
end
--- Set an array of possible laser codes.
-- Each new lase will select a code from this table.
-- @param #DESIGNATE self
@ -407,13 +494,64 @@ do -- DESIGNATE
function DESIGNATE:SetLaserCodes( LaserCodes ) --R2.1
self.LaserCodes = ( type( LaserCodes ) == "table" ) and LaserCodes or { LaserCodes }
self:E(self.LaserCodes)
self:E( { LaserCodes = self.LaserCodes } )
self.LaserCodesUsed = {}
return self
end
--- Add a specific lase code to the designate lase menu to lase targets with a specific laser code.
-- The MenuText will appear in the lase menu.
-- @param #DESIGNATE self
-- @param #number LaserCode The specific laser code to be added to the lase menu.
-- @param #string MenuText The text to be shown to the player. If you specify a %d in the MenuText, the %d will be replaced with the LaserCode specified.
-- @return #DESIGNATE
-- @usage
-- RecceDesignation:AddMenuLaserCode( 1113, "Lase with %d for Su-25T" )
-- RecceDesignation:AddMenuLaserCode( 1680, "Lase with %d for A-10A" )
--
function DESIGNATE:AddMenuLaserCode( LaserCode, MenuText )
self.MenuLaserCodes[LaserCode] = MenuText
self:SetDesignateMenu()
return self
end
--- Removes a specific lase code from the designate lase menu.
-- @param #DESIGNATE self
-- @param #number LaserCode The specific laser code that was set to be added to the lase menu.
-- @return #DESIGNATE
-- @usage
-- RecceDesignation:RemoveMenuLaserCode( 1113 )
--
function DESIGNATE:RemoveMenuLaserCode( LaserCode )
self.MenuLaserCodes[LaserCode] = nil
self:SetDesignateMenu()
return self
end
--- Set the name of the designation. The name will appear in the menu.
-- This method can be used to control different designations for different plane types.
-- @param #DESIGNATE self
-- @param #string DesignateName
-- @return #DESIGNATE
function DESIGNATE:SetDesignateName( DesignateName )
self.DesignateName = "Designation" .. ( DesignateName and ( " for " .. DesignateName ) or "" )
return self
end
--- Generate an array of possible laser codes.
-- Each new lase will select a code from this table.
-- The entered value can range from 1111 - 1788,
@ -470,21 +608,22 @@ do -- DESIGNATE
--- Set auto lase.
-- Auto lase will start lasing targets immediately when these are in range.
-- @param #DESIGNATE self
-- @param #boolean AutoLase
-- @param #boolean AutoLase (optional) true sets autolase on, false off. Default is off.
-- @param #boolean Message (optional) true is send message, false or nil won't send a message. Default is no message sent.
-- @return #DESIGNATE
function DESIGNATE:SetAutoLase( AutoLase ) --R2.1
function DESIGNATE:SetAutoLase( AutoLase, Message )
self.AutoLase = AutoLase
self.AutoLase = AutoLase or false
local AutoLaseOnOff = ( AutoLase == true ) and "On" or "Off"
local CC = self.CC:GetPositionable()
if CC then
CC:MessageToSetGroup( "Auto Lase " .. AutoLaseOnOff .. ".", 15, self.AttackSet )
if Message then
local AutoLaseOnOff = ( self.AutoLase == true ) and "On" or "Off"
local CC = self.CC:GetPositionable()
if CC then
CC:MessageToSetGroup( self.DesignateName .. ": Auto Lase " .. AutoLaseOnOff .. ".", 15, self.AttackSet )
end
end
self:ActivateAutoLase()
self:CoordinateLase()
self:SetDesignateMenu()
return self
@ -501,6 +640,17 @@ do -- DESIGNATE
return self
end
--- Set the MISSION object for which designate will function.
-- When a MISSION object is assigned, the menu for the designation will be located at the Mission Menu.
-- @param #DESIGNATE self
-- @param Tasking.Mission#MISSION Mission The MISSION object.
-- @return #DESIGNATE
function DESIGNATE:SetMission( Mission ) --R2.2
self.Mission = Mission
return self
end
---
@ -508,15 +658,96 @@ do -- DESIGNATE
-- @return #DESIGNATE
function DESIGNATE:onafterDetect()
self:__Detect( -60 )
self:__Detect( -math.random( 60 ) )
self:ActivateAutoLase()
self:DesignationScope()
self:CoordinateLase()
self:SendStatus()
self:SetDesignateMenu()
return self
end
--- Adapt the designation scope according the detected items.
-- @param #DESIGNATE self
-- @return #DESIGNATE
function DESIGNATE:DesignationScope()
local DetectedItems = self.Detection:GetDetectedItems()
local DetectedItemCount = 0
for DesignateIndex, Designating in pairs( self.Designating ) do
local DetectedItem = DetectedItems[DesignateIndex]
if DetectedItem then
-- Check LOS...
local IsDetected = self.Detection:IsDetectedItemDetected( DetectedItem )
self:F({IsDetected = IsDetected, DetectedItem })
if IsDetected == false then
self:F("Removing")
-- This Detection is obsolete, remove from the designate scope
self.Designating[DesignateIndex] = nil
self.AttackSet:ForEachGroup(
function( AttackGroup )
local DetectionText = self.Detection:DetectedItemReportSummary( DesignateIndex, AttackGroup ):Text( ", " )
self.CC:GetPositionable():MessageToGroup( "Targets out of LOS\n" .. DetectionText, 10, AttackGroup, self.DesignateName )
end
)
else
DetectedItemCount = DetectedItemCount + 1
end
else
-- This Detection is obsolete, remove from the designate scope
self.Designating[DesignateIndex] = nil
end
end
if DetectedItemCount < 5 then
for DesignateIndex, DetectedItem in pairs( DetectedItems ) do
local IsDetected = self.Detection:IsDetectedItemDetected( DetectedItem )
if IsDetected == true then
self:F( { DistanceRecce = DetectedItem.DistanceRecce } )
if DetectedItem.DistanceRecce <= self.MaximumDistanceDesignations then
if self.Designating[DesignateIndex] == nil then
-- ok, we added one item to the designate scope.
self.AttackSet:ForEachGroup(
function( AttackGroup )
local DetectionText = self.Detection:DetectedItemReportSummary( DesignateIndex, AttackGroup ):Text( ", " )
self.CC:GetPositionable():MessageToGroup( "Targets detected at \n" .. DetectionText, 10, AttackGroup, self.DesignateName )
end
)
self.Designating[DesignateIndex] = ""
break
end
end
end
end
end
return self
end
--- Coordinates the Auto Lase.
-- @param #DESIGNATE self
-- @return #DESIGNATE
function DESIGNATE:CoordinateLase()
local DetectedItems = self.Detection:GetDetectedItems()
for DesignateIndex, Designating in pairs( self.Designating ) do
local DetectedItem = DetectedItems[DesignateIndex]
if DetectedItem then
if self.AutoLase then
self:LaseOn( DesignateIndex, self.LaseDuration )
end
end
end
return self
end
--- Sends the status to the Attack Groups.
-- @param #DESIGNATE self
-- @param Wrapper.Group#GROUP AttackGroup
@ -533,20 +764,23 @@ do -- DESIGNATE
if self.FlashStatusMenu[AttackGroup] or ( MenuAttackGroup and ( AttackGroup:GetName() == MenuAttackGroup:GetName() ) ) then
local DetectedReport = REPORT:New( "Targets designated:\n" )
local DetectedReport = REPORT:New( "Targets ready for Designation:" )
local DetectedItems = self.Detection:GetDetectedItems()
for Index, DetectedItemData in pairs( DetectedItems ) do
local Report = self.Detection:DetectedItemReportSummary( Index, AttackGroup )
DetectedReport:Add(" - " .. Report)
for DesignateIndex, Designating in pairs( self.Designating ) do
local DetectedItem = DetectedItems[DesignateIndex]
if DetectedItem then
local Report = self.Detection:DetectedItemReportSummary( DesignateIndex, AttackGroup ):Text( ", " )
DetectedReport:Add( string.rep( "-", 140 ) )
DetectedReport:Add( " - " .. Report )
end
end
local CC = self.CC:GetPositionable()
CC:MessageToGroup( DetectedReport:Text( "\n" ), Duration, AttackGroup )
CC:MessageToGroup( DetectedReport:Text( "\n" ), Duration, AttackGroup, self.DesignateName )
local DesignationReport = REPORT:New( "Targets marked:\n" )
local DesignationReport = REPORT:New( "Marking Targets:\n" )
self.RecceSet:ForEachGroup(
function( RecceGroup )
@ -560,34 +794,7 @@ do -- DESIGNATE
end
)
CC:MessageToGroup( DesignationReport:Text(), Duration, AttackGroup )
end
end
)
return self
end
--- Coordinates the Auto Lase.
-- @param #DESIGNATE self
-- @return #DESIGNATE
function DESIGNATE:ActivateAutoLase()
self.AttackSet:Flush()
self.AttackSet:ForEachGroup(
--- @param Wrapper.Group#GROUP GroupReport
function( AttackGroup )
local DetectedItems = self.Detection:GetDetectedItems()
for Index, DetectedItemData in pairs( DetectedItems ) do
if self.AutoLase then
if not self.Designating[Index] then
self:LaseOn( Index, self.LaseDuration )
end
end
CC:MessageToGroup( DesignationReport:Text(), Duration, AttackGroup, self.DesignateName )
end
end
)
@ -606,66 +813,79 @@ do -- DESIGNATE
--- @param Wrapper.Group#GROUP GroupReport
function( AttackGroup )
local DesignateMenu = AttackGroup:GetState( AttackGroup, "DesignateMenu" ) -- Core.Menu#MENU_GROUP
if DesignateMenu then
DesignateMenu:Remove()
DesignateMenu = nil
self:E("Remove Menu")
end
DesignateMenu = MENU_GROUP:New( AttackGroup, "Designate" )
self:E(DesignateMenu)
AttackGroup:SetState( AttackGroup, "DesignateMenu", DesignateMenu )
self.MenuDesignate = self.MenuDesignate or {}
local MissionMenu = nil
if self.Mission then
MissionMenu = self.Mission:GetRootMenu( AttackGroup )
end
local MenuTime = timer.getTime()
self.MenuDesignate[AttackGroup] = MENU_GROUP:New( AttackGroup, self.DesignateName, MissionMenu ):SetTime( MenuTime ):SetTag( self.DesignateName )
local MenuDesignate = self.MenuDesignate[AttackGroup] -- Core.Menu#MENU_GROUP
-- Set Menu option for auto lase
if self.AutoLase then
MENU_GROUP_COMMAND:New( AttackGroup, "Auto Lase Off", DesignateMenu, self.MenuAutoLase, self, false )
MENU_GROUP_COMMAND:New( AttackGroup, "Auto Lase Off", MenuDesignate, self.MenuAutoLase, self, false ):SetTime( MenuTime ):SetTag( self.DesignateName )
else
MENU_GROUP_COMMAND:New( AttackGroup, "Auto Lase On", DesignateMenu, self.MenuAutoLase, self, true )
MENU_GROUP_COMMAND:New( AttackGroup, "Auto Lase On", MenuDesignate, self.MenuAutoLase, self, true ):SetTime( MenuTime ):SetTag( self.DesignateName )
end
local StatusMenu = MENU_GROUP:New( AttackGroup, "Status", DesignateMenu )
MENU_GROUP_COMMAND:New( AttackGroup, "Report Status 15s", StatusMenu, self.MenuStatus, self, AttackGroup, 15 )
MENU_GROUP_COMMAND:New( AttackGroup, "Report Status 30s", StatusMenu, self.MenuStatus, self, AttackGroup, 30 )
MENU_GROUP_COMMAND:New( AttackGroup, "Report Status 60s", StatusMenu, self.MenuStatus, self, AttackGroup, 60 )
local StatusMenu = MENU_GROUP:New( AttackGroup, "Status", MenuDesignate ):SetTime( MenuTime ):SetTag( self.DesignateName )
MENU_GROUP_COMMAND:New( AttackGroup, "Report Status 15s", StatusMenu, self.MenuStatus, self, AttackGroup, 15 ):SetTime( MenuTime ):SetTag( self.DesignateName )
MENU_GROUP_COMMAND:New( AttackGroup, "Report Status 30s", StatusMenu, self.MenuStatus, self, AttackGroup, 30 ):SetTime( MenuTime ):SetTag( self.DesignateName )
MENU_GROUP_COMMAND:New( AttackGroup, "Report Status 60s", StatusMenu, self.MenuStatus, self, AttackGroup, 60 ):SetTime( MenuTime ):SetTag( self.DesignateName )
if self.FlashStatusMenu[AttackGroup] then
MENU_GROUP_COMMAND:New( AttackGroup, "Flash Status Report Off", StatusMenu, self.MenuFlashStatus, self, AttackGroup, false )
MENU_GROUP_COMMAND:New( AttackGroup, "Flash Status Report Off", StatusMenu, self.MenuFlashStatus, self, AttackGroup, false ):SetTime( MenuTime ):SetTag( self.DesignateName )
else
MENU_GROUP_COMMAND:New( AttackGroup, "Flash Status Report On", StatusMenu, self.MenuFlashStatus, self, AttackGroup, true )
MENU_GROUP_COMMAND:New( AttackGroup, "Flash Status Report On", StatusMenu, self.MenuFlashStatus, self, AttackGroup, true ):SetTime( MenuTime ):SetTag( self.DesignateName )
end
local DetectedItems = self.Detection:GetDetectedItems()
for Index, DetectedItemData in pairs( DetectedItems ) do
for DesignateIndex, Designating in pairs( self.Designating ) do
local DetectedItem = self.Detection:GetDetectedItem( DesignateIndex )
if DetectedItem then
local Report = self.Detection:DetectedItemMenu( Index, AttackGroup )
if not self.Designating[Index] then
local DetectedMenu = MENU_GROUP:New( AttackGroup, Report, DesignateMenu )
MENU_GROUP_COMMAND:New( AttackGroup, "Lase target 60 secs", DetectedMenu, self.MenuLaseOn, self, Index, 60 )
MENU_GROUP_COMMAND:New( AttackGroup, "Lase target 120 secs", DetectedMenu, self.MenuLaseOn, self, Index, 120 )
MENU_GROUP_COMMAND:New( AttackGroup, "Smoke red", DetectedMenu, self.MenuSmoke, self, Index, SMOKECOLOR.Red )
MENU_GROUP_COMMAND:New( AttackGroup, "Smoke blue", DetectedMenu, self.MenuSmoke, self, Index, SMOKECOLOR.Blue )
MENU_GROUP_COMMAND:New( AttackGroup, "Smoke green", DetectedMenu, self.MenuSmoke, self, Index, SMOKECOLOR.Green )
MENU_GROUP_COMMAND:New( AttackGroup, "Smoke white", DetectedMenu, self.MenuSmoke, self, Index, SMOKECOLOR.White )
MENU_GROUP_COMMAND:New( AttackGroup, "Smoke orange", DetectedMenu, self.MenuSmoke, self, Index, SMOKECOLOR.Orange )
MENU_GROUP_COMMAND:New( AttackGroup, "Illuminate", DetectedMenu, self.MenuIlluminate, self, Index )
else
if self.Designating[Index] == "Laser" then
Report = "Lasing " .. Report
elseif self.Designating[Index] == "Smoke" then
Report = "Smoking " .. Report
elseif self.Designating[Index] == "Illuminate" then
Report = "Illuminating " .. Report
end
local DetectedMenu = MENU_GROUP:New( AttackGroup, Report, DesignateMenu )
if self.Designating[Index] == "Laser" then
MENU_GROUP_COMMAND:New( AttackGroup, "Stop lasing", DetectedMenu, self.MenuLaseOff, self, Index )
local Coord = self.Detection:GetDetectedItemCoordinate( DesignateIndex )
local ID = self.Detection:GetDetectedItemID( DesignateIndex )
local MenuText = ID .. ", " .. Coord:ToStringA2G( AttackGroup )
if Designating == "" then
MenuText = "(-) " .. MenuText
local DetectedMenu = MENU_GROUP:New( AttackGroup, MenuText, MenuDesignate ):SetTime( MenuTime ):SetTag( self.DesignateName )
MENU_GROUP_COMMAND:New( AttackGroup, "Search other target", DetectedMenu, self.MenuForget, self, DesignateIndex ):SetTime( MenuTime ):SetTag( self.DesignateName )
for LaserCode, MenuText in pairs( self.MenuLaserCodes ) do
MENU_GROUP_COMMAND:New( AttackGroup, string.format( MenuText, LaserCode ), DetectedMenu, self.MenuLaseCode, self, DesignateIndex, 60, LaserCode ):SetTime( MenuTime ):SetTag( self.DesignateName )
end
MENU_GROUP_COMMAND:New( AttackGroup, "Lase with random laser code(s)", DetectedMenu, self.MenuLaseOn, self, DesignateIndex, 60 ):SetTime( MenuTime ):SetTag( self.DesignateName )
MENU_GROUP_COMMAND:New( AttackGroup, "Smoke red", DetectedMenu, self.MenuSmoke, self, DesignateIndex, SMOKECOLOR.Red ):SetTime( MenuTime ):SetTag( self.DesignateName )
MENU_GROUP_COMMAND:New( AttackGroup, "Smoke blue", DetectedMenu, self.MenuSmoke, self, DesignateIndex, SMOKECOLOR.Blue ):SetTime( MenuTime ):SetTag( self.DesignateName )
MENU_GROUP_COMMAND:New( AttackGroup, "Smoke green", DetectedMenu, self.MenuSmoke, self, DesignateIndex, SMOKECOLOR.Green ):SetTime( MenuTime ):SetTag( self.DesignateName )
MENU_GROUP_COMMAND:New( AttackGroup, "Smoke white", DetectedMenu, self.MenuSmoke, self, DesignateIndex, SMOKECOLOR.White ):SetTime( MenuTime ):SetTag( self.DesignateName )
MENU_GROUP_COMMAND:New( AttackGroup, "Smoke orange", DetectedMenu, self.MenuSmoke, self, DesignateIndex, SMOKECOLOR.Orange ):SetTime( MenuTime ):SetTag( self.DesignateName )
MENU_GROUP_COMMAND:New( AttackGroup, "Illuminate", DetectedMenu, self.MenuIlluminate, self, DesignateIndex ):SetTime( MenuTime ):SetTag( self.DesignateName )
else
if Designating == "Laser" then
MenuText = "(L) " .. MenuText
elseif Designating == "Smoke" then
MenuText = "(S) " .. MenuText
elseif Designating == "Illuminate" then
MenuText = "(I) " .. MenuText
end
local DetectedMenu = MENU_GROUP:New( AttackGroup, MenuText, MenuDesignate ):SetTime( MenuTime ):SetTag( self.DesignateName )
if Designating == "Laser" then
MENU_GROUP_COMMAND:New( AttackGroup, "Stop lasing", DetectedMenu, self.MenuLaseOff, self, DesignateIndex ):SetTime( MenuTime ):SetTag( self.DesignateName )
else
end
end
end
end
MenuDesignate:Remove( MenuTime, self.DesignateName )
end
)
@ -692,13 +912,23 @@ do -- DESIGNATE
end
---
-- @param #DESIGNATE self
function DESIGNATE:MenuForget( Index )
self:E("Forget")
self.Designating[Index] = nil
self:SetDesignateMenu()
end
---
-- @param #DESIGNATE self
function DESIGNATE:MenuAutoLase( AutoLase )
self:E("AutoLase")
self:SetAutoLase( AutoLase )
self:SetAutoLase( AutoLase, true )
end
---
@ -708,7 +938,7 @@ do -- DESIGNATE
self:E("Designate through Smoke")
self.Designating[Index] = "Smoke"
self:__Smoke( 1, Index, Color )
self:Smoke( Index, Color )
end
---
@ -729,96 +959,204 @@ do -- DESIGNATE
self:E("Designate through Lase")
self:__LaseOn( 1, Index, Duration )
self:SetDesignateMenu()
end
---
-- @param #DESIGNATE self
function DESIGNATE:MenuLaseCode( Index, Duration, LaserCode )
self:E( "Designate through Lase using " .. LaserCode )
self:__LaseOn( 1, Index, Duration, LaserCode )
self:SetDesignateMenu()
end
---
-- @param #DESIGNATE self
function DESIGNATE:MenuLaseOff( Index, Duration )
self:E("Lasing off")
self.Designating[Index] = nil
self.Designating[Index] = ""
self:__LaseOff( 1, Index )
self:SetDesignateMenu()
end
---
-- @param #DESIGNATE self
function DESIGNATE:onafterLaseOn( From, Event, To, Index, Duration )
function DESIGNATE:onafterLaseOn( From, Event, To, Index, Duration, LaserCode )
self.Designating[Index] = "Laser"
self:Lasing( Index, Duration )
self.LaseStart = timer.getTime()
self.LaseDuration = Duration
self:__Lasing( -1, Index, Duration, LaserCode )
end
---
-- @param #DESIGNATE self
-- @return #DESIGNATE
function DESIGNATE:onafterLasing( From, Event, To, Index, Duration )
function DESIGNATE:onafterLasing( From, Event, To, Index, Duration, LaserCodeRequested )
local TargetSetUnit = self.Detection:GetDetectedSet( Index )
local MarkingCount = 0
local MarkedTypes = {}
local ReportTypes = REPORT:New()
local ReportLaserCodes = REPORT:New()
TargetSetUnit:Flush()
--self:F( { Recces = self.Recces } )
for TargetUnit, RecceData in pairs( self.Recces ) do
local Recce = RecceData -- Wrapper.Unit#UNIT
self:F( { TargetUnit = TargetUnit, Recce = Recce:GetName() } )
if not Recce:IsLasing() then
local LaserCode = Recce:GetLaserCode() --(Not deleted when stopping with lasing).
local LaserCode = Recce:GetLaserCode() -- (Not deleted when stopping with lasing).
self:F( { ClearingLaserCode = LaserCode } )
self.LaserCodesUsed[LaserCode] = nil
self.Recces[TargetUnit] = nil
end
end
TargetSetUnit:ForEachUnitPerThreatLevel( 10, 0,
--- @param Wrapper.Unit#UNIT SmokeUnit
function( TargetUnit )
self:E("In procedure")
if TargetUnit:IsAlive() then
local Recce = self.Recces[TargetUnit]
if not Recce then
for RecceGroupID, RecceGroup in pairs( self.RecceSet:GetSet() ) do
for UnitID, UnitData in pairs( RecceGroup:GetUnits() or {} ) do
local RecceUnit = UnitData -- Wrapper.Unit#UNIT
if RecceUnit:IsLasing() == false then
if RecceUnit:IsDetected( TargetUnit ) and RecceUnit:IsLOS( TargetUnit ) then
local LaserCodeIndex = math.random( 1, #self.LaserCodes )
local LaserCode = self.LaserCodes[LaserCodeIndex]
if not self.LaserCodesUsed[LaserCode] then
self.LaserCodesUsed[LaserCode] = LaserCodeIndex
local Spot = RecceUnit:LaseUnit( TargetUnit, LaserCode, Duration )
local AttackSet = self.AttackSet
function Spot:OnAfterDestroyed( From, Event, To )
self:E( "Destroyed Message" )
self.Recce:MessageToSetGroup( "Target " .. TargetUnit:GetTypeName() .. " destroyed. " .. TargetSetUnit:Count() .. " targets left.", 5, AttackSet )
end
self.Recces[TargetUnit] = RecceUnit
RecceUnit:MessageToSetGroup( "Marking " .. TargetUnit:GetTypeName() .. " with laser " .. RecceUnit:GetSpot().LaserCode .. " for " .. Duration .. "s.", 5, self.AttackSet )
break
end
else
RecceUnit:MessageToSetGroup( "Can't mark " .. TargetUnit:GetTypeName(), 5, self.AttackSet )
end
else
-- The Recce is lasing, but the Target is not detected or within LOS. So stop lasing and send a report.
if not RecceUnit:IsDetected( TargetUnit ) or not RecceUnit:IsLOS( TargetUnit ) then
local Recce = self.Recces[TargetUnit] -- Wrapper.Unit#UNIT
if Recce then
Recce:LaseOff()
Recce:MessageToGroup( "Target " .. TargetUnit:GetTypeName() "out of LOS. Cancelling lase!", 5, self.AttackSet )
end
end
end
end
end
else
Recce:MessageToSetGroup( "Marking " .. TargetUnit:GetTypeName() .. " with laser " .. Recce.LaserCode .. ".", 5, self.AttackSet )
end
-- If a specific lasercode is requested, we disable one active lase!
if LaserCodeRequested then
for TargetUnit, RecceData in pairs( self.Recces ) do -- We break after the first has been processed.
local Recce = RecceData -- Wrapper.Unit#UNIT
self:F( { TargetUnit = TargetUnit, Recce = Recce:GetName() } )
if Recce:IsLasing() then
-- When a Recce is lasing, we switch the lasing off, and clear the references to the lasing in the DESIGNATE class.
Recce:LaseOff() -- Switch off the lasing.
local LaserCode = Recce:GetLaserCode() -- (Not deleted when stopping with lasing).
self:F( { ClearingLaserCode = LaserCode } )
self.LaserCodesUsed[LaserCode] = nil
self.Recces[TargetUnit] = nil
break
end
end
)
self:__Lasing( 15, Index, Duration )
end
self:SetDesignateMenu()
if self.AutoLase or ( not self.AutoLase and ( self.LaseStart + Duration >= timer.getTime() ) ) then
TargetSetUnit:ForEachUnitPerThreatLevel( 10, 0,
--- @param Wrapper.Unit#UNIT SmokeUnit
function( TargetUnit )
self:F( { TargetUnit = TargetUnit:GetName() } )
if MarkingCount < self.MaximumMarkings then
if TargetUnit:IsAlive() then
local Recce = self.Recces[TargetUnit]
if not Recce then
self:E( "Lasing..." )
self.RecceSet:Flush()
for RecceGroupID, RecceGroup in pairs( self.RecceSet:GetSet() ) do
for UnitID, UnitData in pairs( RecceGroup:GetUnits() or {} ) do
local RecceUnit = UnitData -- Wrapper.Unit#UNIT
local RecceUnitDesc = RecceUnit:GetDesc()
--self:F( { RecceUnit = RecceUnit:GetName(), RecceDescription = RecceUnitDesc } )
if RecceUnit:IsLasing() == false then
--self:F( { IsDetected = RecceUnit:IsDetected( TargetUnit ), IsLOS = RecceUnit:IsLOS( TargetUnit ) } )
if RecceUnit:IsDetected( TargetUnit ) and RecceUnit:IsLOS( TargetUnit ) then
local LaserCodeIndex = math.random( 1, #self.LaserCodes )
local LaserCode = self.LaserCodes[LaserCodeIndex]
--self:F( { LaserCode = LaserCode, LaserCodeUsed = self.LaserCodesUsed[LaserCode] } )
if LaserCodeRequested and LaserCodeRequested ~= LaserCode then
LaserCode = LaserCodeRequested
LaserCodeRequested = nil
end
if not self.LaserCodesUsed[LaserCode] then
self.LaserCodesUsed[LaserCode] = LaserCodeIndex
local Spot = RecceUnit:LaseUnit( TargetUnit, LaserCode, Duration )
local AttackSet = self.AttackSet
function Spot:OnAfterDestroyed( From, Event, To )
self:E( "Destroyed Message" )
self.Recce:ToSetGroup( "Target " .. TargetUnit:GetTypeName() .. " destroyed. " .. TargetSetUnit:Count() .. " targets left.", 5, AttackSet, self.DesignateName )
end
self.Recces[TargetUnit] = RecceUnit
RecceUnit:MessageToSetGroup( "Marking " .. TargetUnit:GetTypeName() .. " with laser " .. RecceUnit:GetSpot().LaserCode .. " for " .. Duration .. "s.", 5, self.AttackSet, self.DesignateName )
-- OK. We have assigned for the Recce a TargetUnit. We can exit the function.
MarkingCount = MarkingCount + 1
local TargetUnitType = TargetUnit:GetTypeName()
if not MarkedTypes[TargetUnitType] then
MarkedTypes[TargetUnitType] = true
ReportTypes:Add(TargetUnitType)
end
ReportLaserCodes:Add(RecceUnit.LaserCode)
return
end
else
--RecceUnit:MessageToSetGroup( "Can't mark " .. TargetUnit:GetTypeName(), 5, self.AttackSet )
end
else
-- The Recce is lasing, but the Target is not detected or within LOS. So stop lasing and send a report.
if not RecceUnit:IsDetected( TargetUnit ) or not RecceUnit:IsLOS( TargetUnit ) then
local Recce = self.Recces[TargetUnit] -- Wrapper.Unit#UNIT
if Recce then
Recce:LaseOff()
Recce:MessageToSetGroup( "Target " .. TargetUnit:GetTypeName() "out of LOS. Cancelling lase!", 5, self.AttackSet, self.DesignateName )
end
else
MarkingCount = MarkingCount + 1
local TargetUnitType = TargetUnit:GetTypeName()
if not MarkedTypes[TargetUnitType] then
MarkedTypes[TargetUnitType] = true
ReportTypes:Add(TargetUnitType)
end
ReportLaserCodes:Add(RecceUnit.LaserCode)
end
end
end
end
else
MarkingCount = MarkingCount + 1
local TargetUnitType = TargetUnit:GetTypeName()
if not MarkedTypes[TargetUnitType] then
MarkedTypes[TargetUnitType] = true
ReportTypes:Add(TargetUnitType)
end
ReportLaserCodes:Add(Recce.LaserCode)
--Recce:MessageToSetGroup( self.DesignateName .. ": Marking " .. TargetUnit:GetTypeName() .. " with laser " .. Recce.LaserCode .. ".", 5, self.AttackSet )
end
end
end
end
)
local MarkedTypesText = ReportTypes:Text(', ')
local MarkedLaserCodesText = ReportLaserCodes:Text(', ')
for MarkedType, MarketCount in pairs( MarkedTypes ) do
self.CC:GetPositionable():MessageToSetGroup( "Marking " .. MarkingCount .. " x " .. MarkedTypesText .. " with lasers " .. MarkedLaserCodesText .. ".", 5, self.AttackSet, self.DesignateName )
end
self:__Lasing( -30, Index, Duration, LaserCodeRequested )
self:SetDesignateMenu()
else
self:__LaseOff( 1 )
end
end
@ -830,7 +1168,7 @@ do -- DESIGNATE
local CC = self.CC:GetPositionable()
if CC then
CC:MessageToSetGroup( "Stopped lasing.", 5, self.AttackSet )
CC:MessageToSetGroup( "Stopped lasing.", 5, self.AttackSet, self.DesignateName )
end
local TargetSetUnit = self.Detection:GetDetectedSet( Index )
@ -839,7 +1177,7 @@ do -- DESIGNATE
for TargetID, RecceData in pairs( Recces ) do
local Recce = RecceData -- Wrapper.Unit#UNIT
Recce:MessageToSetGroup( "Stopped lasing " .. Recce:GetSpot().Target:GetTypeName() .. ".", 5, self.AttackSet )
Recce:MessageToSetGroup( "Stopped lasing " .. Recce:GetSpot().Target:GetTypeName() .. ".", 5, self.AttackSet, self.DesignateName )
Recce:LaseOff()
end
@ -859,19 +1197,29 @@ do -- DESIGNATE
local TargetSetUnit = self.Detection:GetDetectedSet( Index )
local TargetSetUnitCount = TargetSetUnit:Count()
TargetSetUnit:ForEachUnit(
local MarkedCount = 0
TargetSetUnit:ForEachUnitPerThreatLevel( 10, 0,
--- @param Wrapper.Unit#UNIT SmokeUnit
function( SmokeUnit )
self:E("In procedure")
if math.random( 1, TargetSetUnitCount ) == math.random( 1, TargetSetUnitCount ) then
if MarkedCount < self.MaximumMarkings then
MarkedCount = MarkedCount + 1
self:E( "Smoking ..." )
local RecceGroup = self.RecceSet:FindNearestGroupFromPointVec2(SmokeUnit:GetPointVec2())
local RecceUnit = RecceGroup:GetUnit( 1 )
if RecceUnit then
RecceUnit:MessageToSetGroup( "Smoking " .. SmokeUnit:GetTypeName() .. ".", 5, self.AttackSet )
SCHEDULER:New( self,
RecceUnit:MessageToSetGroup( "Smoking " .. SmokeUnit:GetTypeName() .. ".", 5, self.AttackSet, self.DesignateName )
self.MarkScheduler:Schedule( self,
function()
if SmokeUnit:IsAlive() then
SmokeUnit:Smoke( Color, 150 )
SmokeUnit:Smoke( Color, 50, 2 )
end
self:Done( Index )
end, {}, math.random( 5, 20 )
@ -896,8 +1244,8 @@ do -- DESIGNATE
local RecceGroup = self.RecceSet:FindNearestGroupFromPointVec2(TargetUnit:GetPointVec2())
local RecceUnit = RecceGroup:GetUnit( 1 )
if RecceUnit then
RecceUnit:MessageToSetGroup( "Illuminating " .. TargetUnit:GetTypeName() .. ".", 5, self.AttackSet )
SCHEDULER:New( self,
RecceUnit:MessageToSetGroup( "Illuminating " .. TargetUnit:GetTypeName() .. ".", 5, self.AttackSet, self.DesignateName )
self.MarkScheduler:Schedule( self,
function()
if TargetUnit:IsAlive() then
TargetUnit:GetPointVec3():AddY(300):IlluminationBomb()

File diff suppressed because it is too large Load Diff

View File

@ -875,7 +875,7 @@ function ESCORT:_AttackTarget( DetectedItemID )
end, Tasks
)
Tasks[#Tasks+1] = EscortGroup:TaskFunction( 1, 2, "_Resume", { "''" } )
Tasks[#Tasks+1] = EscortGroup:TaskFunction( "_Resume", { "''" } )
EscortGroup:SetTask(
EscortGroup:TaskCombo(
@ -1161,19 +1161,23 @@ function ESCORT:_ReportTargetsScheduler()
for ClientEscortGroupName, EscortGroupData in pairs( self.EscortClient._EscortGroups ) do
local ClientEscortTargets = EscortGroupData.Detection
--local EscortUnit = EscortGroupData:GetUnit( 1 )
for DetectedItemID, DetectedItem in ipairs( DetectedItems ) do
for DetectedItemID, DetectedItem in pairs( DetectedItems ) do
self:E( { DetectedItemID, DetectedItem } )
-- Remove the sub menus of the Attack menu of the Escort for the EscortGroup.
local DetectedItemReportSummary = self.Detection:DetectedItemReportSummary( DetectedItemID, EscortGroupData )
local DetectedItemReportSummary = self.Detection:DetectedItemReportSummary( DetectedItemID, EscortGroupData.EscortGroup, _DATABASE:GetPlayerSettings( self.EscortClient:GetPlayerName() ) )
if ClientEscortGroupName == EscortGroupName then
DetectedMsgs[#DetectedMsgs+1] = DetectedItemReportSummary
local DetectedMsg = DetectedItemReportSummary:Text("\n")
DetectedMsgs[#DetectedMsgs+1] = DetectedMsg
self:T( DetectedMsg )
MENU_CLIENT_COMMAND:New( self.EscortClient,
DetectedItemReportSummary,
DetectedMsg,
self.EscortMenuAttackNearbyTargets,
ESCORT._AttackTarget,
self,
@ -1182,10 +1186,12 @@ function ESCORT:_ReportTargetsScheduler()
else
if self.EscortMenuTargetAssistance then
self:T( DetectedItemReportSummary )
local DetectedMsg = DetectedItemReportSummary:Text("\n")
self:T( DetectedMsg )
local MenuTargetAssistance = MENU_CLIENT:New( self.EscortClient, EscortGroupData.EscortName, self.EscortMenuTargetAssistance )
MENU_CLIENT_COMMAND:New( self.EscortClient,
DetectedItemReportSummary,
DetectedMsg,
MenuTargetAssistance,
ESCORT._AssistTarget,
self,
@ -1201,7 +1207,7 @@ function ESCORT:_ReportTargetsScheduler()
end
self:E( DetectedMsgs )
if DetectedTargets then
self.EscortGroup:MessageToClient( "Detected targets:\n" .. table.concat( DetectedMsgs, "\n" ), 20, self.EscortClient )
self.EscortGroup:MessageToClient( "Reporting detected targets:\n" .. table.concat( DetectedMsgs, "\n" ), 20, self.EscortClient )
else
self.EscortGroup:MessageToClient( "No targets detected.", 10, self.EscortClient )
end

View File

@ -442,7 +442,7 @@ function MISSILETRAINER._MenuMessages( MenuParameters )
if MenuParameters.Distance ~= nil then
self.Distance = MenuParameters.Distance
MESSAGE:New( "Hit detection distance set to " .. self.Distance * 1000 .. " meters", 15, "Menu" ):ToAll()
MESSAGE:New( "Hit detection distance set to " .. ( self.Distance * 1000 ) .. " meters", 15, "Menu" ):ToAll()
end
end
@ -570,72 +570,76 @@ function MISSILETRAINER:_TrackMissiles()
for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do
local Client = ClientData.Client
self:T2( { Client:GetName() } )
if Client and Client:IsAlive() then
for MissileDataID, MissileData in pairs( ClientData.MissileData ) do
self:T3( MissileDataID )
local TrainerSourceUnit = MissileData.TrainerSourceUnit
local TrainerWeapon = MissileData.TrainerWeapon
local TrainerTargetUnit = MissileData.TrainerTargetUnit
local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName
local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched
for MissileDataID, MissileData in pairs( ClientData.MissileData ) do
self:T3( MissileDataID )
if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then
local PositionMissile = TrainerWeapon:getPosition().p
local TargetVec3 = Client:GetVec3()
local Distance = ( ( PositionMissile.x - TargetVec3.x )^2 +
( PositionMissile.y - TargetVec3.y )^2 +
( PositionMissile.z - TargetVec3.z )^2
) ^ 0.5 / 1000
if Distance <= self.Distance then
-- Hit alert
TrainerWeapon:destroy()
if self.MessagesOnOff == true and self.AlertsHitsOnOff == true then
self:T( "killed" )
local Message = MESSAGE:New(
string.format( "%s launched by %s killed %s",
TrainerWeapon:getTypeName(),
TrainerSourceUnit:GetTypeName(),
TrainerTargetUnit:GetPlayerName()
), 15, "Hit Alert" )
if self.AlertsToAll == true then
Message:ToAll()
else
Message:ToClient( Client )
local TrainerSourceUnit = MissileData.TrainerSourceUnit
local TrainerWeapon = MissileData.TrainerWeapon
local TrainerTargetUnit = MissileData.TrainerTargetUnit
local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName
local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched
if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then
local PositionMissile = TrainerWeapon:getPosition().p
local TargetVec3 = Client:GetVec3()
local Distance = ( ( PositionMissile.x - TargetVec3.x )^2 +
( PositionMissile.y - TargetVec3.y )^2 +
( PositionMissile.z - TargetVec3.z )^2
) ^ 0.5 / 1000
if Distance <= self.Distance then
-- Hit alert
TrainerWeapon:destroy()
if self.MessagesOnOff == true and self.AlertsHitsOnOff == true then
self:T( "killed" )
local Message = MESSAGE:New(
string.format( "%s launched by %s killed %s",
TrainerWeapon:getTypeName(),
TrainerSourceUnit:GetTypeName(),
TrainerTargetUnit:GetPlayerName()
), 15, "Hit Alert" )
if self.AlertsToAll == true then
Message:ToAll()
else
Message:ToClient( Client )
end
MissileData = nil
table.remove( ClientData.MissileData, MissileDataID )
self:T(ClientData.MissileData)
end
end
else
if not ( TrainerWeapon and TrainerWeapon:isExist() ) then
if self.MessagesOnOff == true and self.AlertsLaunchesOnOff == true then
-- Weapon does not exist anymore. Delete from Table
local Message = MESSAGE:New(
string.format( "%s launched by %s self destructed!",
TrainerWeaponTypeName,
TrainerSourceUnit:GetTypeName()
), 5, "Tracking" )
if self.AlertsToAll == true then
Message:ToAll()
else
Message:ToClient( Client )
end
end
MissileData = nil
table.remove( ClientData.MissileData, MissileDataID )
self:T(ClientData.MissileData)
self:T( ClientData.MissileData )
end
end
else
if not ( TrainerWeapon and TrainerWeapon:isExist() ) then
if self.MessagesOnOff == true and self.AlertsLaunchesOnOff == true then
-- Weapon does not exist anymore. Delete from Table
local Message = MESSAGE:New(
string.format( "%s launched by %s self destructed!",
TrainerWeaponTypeName,
TrainerSourceUnit:GetTypeName()
), 5, "Tracking" )
if self.AlertsToAll == true then
Message:ToAll()
else
Message:ToClient( Client )
end
end
MissileData = nil
table.remove( ClientData.MissileData, MissileDataID )
self:T( ClientData.MissileData )
end
end
else
self.TrackingMissiles[ClientDataID] = nil
end
end
@ -651,7 +655,7 @@ function MISSILETRAINER:_TrackMissiles()
for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do
local Client = ClientData.Client
self:T2( { Client:GetName() } )
--self:T2( { Client:GetName() } )
ClientData.MessageToClient = ""
@ -661,7 +665,7 @@ function MISSILETRAINER:_TrackMissiles()
for TrackingDataID, TrackingData in pairs( self.TrackingMissiles ) do
for MissileDataID, MissileData in pairs( TrackingData.MissileData ) do
self:T3( MissileDataID )
--self:T3( MissileDataID )
local TrainerSourceUnit = MissileData.TrainerSourceUnit
local TrainerWeapon = MissileData.TrainerWeapon

View File

@ -0,0 +1,305 @@
--- **Functional** -- The PROTECT class handles the protection of objects, which can be zones, units, scenery.
--
-- ===
--
-- ### Author: **Sven Van de Velde (FlightControl)**
-- ### Contributions: **MillerTime**
--
-- ====
--
-- @module Protect
--- @type PROTECT.__ Methods which are not intended for mission designers, but which are used interally by the moose designer :-)
-- @extends Core.Fsm#FSM
--- @type PROTECT
-- @extends #PROTECT.__
--- # PROTECT, extends @{Base#BASE}
--
-- @field #PROTECT
PROTECT = {
ClassName = "PROTECT",
}
--- Get the ProtectZone
-- @param #PROTECT self
-- @return Core.Zone#ZONE_BASE
function PROTECT:GetProtectZone()
return self.ProtectZone
end
--- Get the name of the ProtectZone
-- @param #PROTECT self
-- @return #string
function PROTECT:GetProtectZoneName()
return self.ProtectZone:GetName()
end
--- Set the owning coalition of the zone.
-- @param #PROTECT self
-- @param DCSCoalition.DCSCoalition#coalition Coalition
function PROTECT:SetCoalition( Coalition )
self.Coalition = Coalition
end
--- Get the owning coalition of the zone.
-- @param #PROTECT self
-- @return DCSCoalition.DCSCoalition#coalition Coalition.
function PROTECT:GetCoalition()
return self.Coalition
end
--- Get the owning coalition name of the zone.
-- @param #PROTECT self
-- @return #string Coalition name.
function PROTECT:GetCoalitionName()
if self.Coalition == coalition.side.BLUE then
return "Blue"
end
if self.Coalition == coalition.side.RED then
return "Red"
end
if self.Coalition == coalition.side.NEUTRAL then
return "Neutral"
end
return ""
end
function PROTECT:IsGuarded()
local IsGuarded = self.ProtectZone:IsAllInZoneOfCoalition( self.Coalition )
self:E( { IsGuarded = IsGuarded } )
return IsGuarded
end
function PROTECT:IsCaptured()
local IsCaptured = self.ProtectZone:IsAllInZoneOfOtherCoalition( self.Coalition )
self:E( { IsCaptured = IsCaptured } )
return IsCaptured
end
function PROTECT:IsAttacked()
local IsAttacked = self.ProtectZone:IsSomeInZoneOfCoalition( self.Coalition )
self:E( { IsAttacked = IsAttacked } )
return IsAttacked
end
function PROTECT:IsEmpty()
local IsEmpty = self.ProtectZone:IsNoneInZone()
self:E( { IsEmpty = IsEmpty } )
return IsEmpty
end
--- Check if the units are still alive.
-- @param #PROTECT self
function PROTECT:AreProtectUnitsAlive()
local IsAlive = false
local UnitSet = self.ProtectUnitSet
UnitSet:Flush()
local UnitList = UnitSet:GetSet()
for UnitID, ProtectUnit in pairs( UnitList ) do
local IsUnitAlive = ProtectUnit:IsAlive()
if IsUnitAlive == true then
IsAlive = true
break
end
end
return IsAlive
end
--- Check if the statics are still alive.
-- @param #PROTECT self
function PROTECT:AreProtectStaticsAlive()
local IsAlive = false
local StaticSet = self.ProtectStaticSet
StaticSet:Flush()
local StaticList = StaticSet:GetSet()
for UnitID, ProtectStatic in pairs( StaticList ) do
local IsStaticAlive = ProtectStatic:IsAlive()
if IsStaticAlive == true then
IsAlive = true
break
end
end
return IsAlive
end
--- Check if there is a capture unit in the zone.
-- @param #PROTECT self
function PROTECT:IsCaptureUnitInZone()
local CaptureUnitSet = self.CaptureUnitSet
CaptureUnitSet:Flush()
local IsInZone = self.CaptureUnitSet:IsPartiallyInZone( self.ProtectZone )
self:E({IsInZone = IsInZone})
return IsInZone
end
--- Smoke.
-- @param #PROTECT self
-- @param #SMOKECOLOR.Color SmokeColor
function PROTECT:Smoke( SmokeColor )
self.SmokeColor = SmokeColor
end
--- Flare.
-- @param #PROTECT self
-- @param #SMOKECOLOR.Color FlareColor
function PROTECT:Flare( FlareColor )
self.ProtectZone:FlareZone( FlareColor, math.random( 1, 360 ) )
end
--- Mark.
-- @param #PROTECT self
function PROTECT:Mark()
local Coord = self.ProtectZone:GetCoordinate()
local ZoneName = self:GetProtectZoneName()
local State = self:GetState()
if self.MarkRed and self.MarkBlue then
self:E( { MarkRed = self.MarkRed, MarkBlue = self.MarkBlue } )
Coord:RemoveMark( self.MarkRed )
Coord:RemoveMark( self.MarkBlue )
end
if self.Coalition == coalition.side.BLUE then
self.MarkBlue = Coord:MarkToCoalitionBlue( "Guard Zone: " .. ZoneName .. "\nStatus: " .. State )
self.MarkRed = Coord:MarkToCoalitionRed( "Capture Zone: " .. ZoneName .. "\nStatus: " .. State )
else
self.MarkRed = Coord:MarkToCoalitionRed( "Guard Zone: " .. ZoneName .. "\nStatus: " .. State )
self.MarkBlue = Coord:MarkToCoalitionBlue( "Capture Zone: " .. ZoneName .. "\nStatus: " .. State )
end
end
--- Bound.
-- @param #PROTECT self
function PROTECT:onafterStart()
self:ScheduleRepeat( 5, 15, 0.1, nil, self.StatusCoalition, self )
self:ScheduleRepeat( 5, 15, 0.1, nil, self.StatusZone, self )
self:ScheduleRepeat( 10, 15, 0, nil, self.StatusSmoke, self )
end
--- Bound.
-- @param #PROTECT self
function PROTECT:onenterGuarded()
if self.Coalition == coalition.side.BLUE then
--elf.ProtectZone:BoundZone( 12, country.id.USA )
else
--self.ProtectZone:BoundZone( 12, country.id.RUSSIA )
end
self:Mark()
end
function PROTECT:onenterCaptured()
local NewCoalition = self.ProtectZone:GetCoalition()
self:E( { NewCoalition = NewCoalition } )
self:SetCoalition( NewCoalition )
self:Mark()
end
function PROTECT:onenterEmpty()
self:Mark()
end
function PROTECT:onenterAttacked()
self:Mark()
end
--- Check status Coalition ownership.
-- @param #PROTECT self
function PROTECT:StatusCoalition()
self:E( { State = self:GetState() } )
self.ProtectZone:Scan()
if self:IsGuarded() then
self:Guard()
else
if self:IsCaptured() then
self:Capture()
end
end
end
--- Check status Zone.
-- @param #PROTECT self
function PROTECT:StatusZone()
self:E( { State = self:GetState() } )
self.ProtectZone:Scan()
if self:IsAttacked() then
self:Attack()
else
if self:IsEmpty() then
self:Empty()
end
end
end
--- Check status Smoke.
-- @param #PROTECT self
function PROTECT:StatusSmoke()
local CurrentTime = timer.getTime()
if self.SmokeTime == nil or self.SmokeTime + 300 <= CurrentTime then
if self.SmokeColor then
self.ProtectZone:GetCoordinate():Smoke( self.SmokeColor )
--self.SmokeColor = nil
self.SmokeTime = CurrentTime
end
end
end

File diff suppressed because it is too large Load Diff

View File

@ -102,7 +102,9 @@
-- A mission has goals and achievements. The scoring system provides an API to set additional scores when a goal or achievement event happens.
-- Use the method @{#SCORING.AddGoalScore}() to add a score for a Player at any time in your mission.
--
-- ## 1.5) Configure fratricide level.
-- ## 1.5) (Decommissioned) Configure fratricide level.
--
-- **This functionality is decomissioned until the DCS bug concerning Unit:destroy() not being functional in multi player for player units has been fixed by ED**.
--
-- When a player commits too much damage to friendlies, his penalty score will reach a certain level.
-- Use the method @{#SCORING.SetFratricide}() to define the level when a player gets kicked.
@ -258,7 +260,7 @@ function SCORING:New( GameName )
-- Configure Messages
self:SetMessagesToAll()
self:SetMessagesHit( true )
self:SetMessagesHit( false )
self:SetMessagesDestroy( true )
self:SetMessagesScore( true )
self:SetMessagesZone( true )
@ -294,7 +296,7 @@ end
-- @param #string DisplayMessagePrefix (Default="Scoring: ") The scoring prefix string.
-- @return #SCORING
function SCORING:SetDisplayMessagePrefix( DisplayMessagePrefix )
self.DisplayMessagePrefix = DisplayMessagePrefix or "Scoring: "
self.DisplayMessagePrefix = DisplayMessagePrefix or ""
return self
end
@ -608,14 +610,15 @@ function SCORING:_AddPlayerFromUnit( UnitData )
if self.Players[PlayerName].UnitCoalition ~= UnitCoalition then
self.Players[PlayerName].Penalty = self.Players[PlayerName].Penalty + 50
self.Players[PlayerName].PenaltyCoalition = self.Players[PlayerName].PenaltyCoalition + 1
MESSAGE:New( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' changed coalition from " .. _SCORINGCoalition[self.Players[PlayerName].UnitCoalition] .. " to " .. _SCORINGCoalition[UnitCoalition] ..
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' changed coalition from " .. _SCORINGCoalition[self.Players[PlayerName].UnitCoalition] .. " to " .. _SCORINGCoalition[UnitCoalition] ..
"(changed " .. self.Players[PlayerName].PenaltyCoalition .. " times the coalition). 50 Penalty points added.",
2
MESSAGE.Type.Information
):ToAll()
self:ScoreCSV( PlayerName, "", "COALITION_PENALTY", 1, -50, self.Players[PlayerName].UnitName, _SCORINGCoalition[self.Players[PlayerName].UnitCoalition], _SCORINGCategory[self.Players[PlayerName].UnitCategory], self.Players[PlayerName].UnitType,
UnitName, _SCORINGCoalition[UnitCoalition], _SCORINGCategory[UnitCategory], UnitData:GetTypeName() )
end
end
self.Players[PlayerName].UnitName = UnitName
self.Players[PlayerName].UnitCoalition = UnitCoalition
self.Players[PlayerName].UnitCategory = UnitCategory
@ -624,26 +627,59 @@ function SCORING:_AddPlayerFromUnit( UnitData )
self.Players[PlayerName].ThreatLevel = UnitThreatLevel
self.Players[PlayerName].ThreatType = UnitThreatType
-- TODO: DCS bug concerning Units with skill level client don't get destroyed in multi player. This logic is deactivated until this bug gets fixed.
--[[
if self.Players[PlayerName].Penalty > self.Fratricide * 0.50 then
if self.Players[PlayerName].PenaltyWarning < 1 then
MESSAGE:New( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "': WARNING! If you continue to commit FRATRICIDE and have a PENALTY score higher than " .. self.Fratricide .. ", you will be COURT MARTIALED and DISMISSED from this mission! \nYour total penalty is: " .. self.Players[PlayerName].Penalty,
30
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "': WARNING! If you continue to commit FRATRICIDE and have a PENALTY score higher than " .. self.Fratricide .. ", you will be COURT MARTIALED and DISMISSED from this mission! \nYour total penalty is: " .. self.Players[PlayerName].Penalty,
MESSAGE.Type.Information
):ToAll()
self.Players[PlayerName].PenaltyWarning = self.Players[PlayerName].PenaltyWarning + 1
end
end
if self.Players[PlayerName].Penalty > self.Fratricide then
--UnitData:Destroy()
MESSAGE:New( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' committed FRATRICIDE, he will be COURT MARTIALED and is DISMISSED from this mission!",
10
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' committed FRATRICIDE, he will be COURT MARTIALED and is DISMISSED from this mission!",
MESSAGE.Type.Information
):ToAll()
UnitData:GetGroup():Destroy()
end
--]]
end
end
--- Add a goal score for a player.
-- The method takes the Player name for which the Goal score needs to be set.
-- The GoalTag is a string or identifier that is taken into the CSV file scoring log to identify the goal.
-- A free text can be given that is shown to the players.
-- The Score can be both positive and negative.
-- @param #SCORING self
-- @param #string PlayerName The name of the Player.
-- @param #string GoalTag The string or identifier that is used in the CSV file to identify the goal (sort or group later in Excel).
-- @param #string Text A free text that is shown to the players.
-- @param #number Score The score can be both positive or negative ( Penalty ).
function SCORING:AddGoalScorePlayer( PlayerName, GoalTag, Text, Score )
self:E( { PlayerName, PlayerName, GoalTag, Text, Score } )
-- PlayerName can be nil, if the Unit with the player crashed or due to another reason.
if PlayerName then
local PlayerData = self.Players[PlayerName]
PlayerData.Goals[GoalTag] = PlayerData.Goals[GoalTag] or { Score = 0 }
PlayerData.Goals[GoalTag].Score = PlayerData.Goals[GoalTag].Score + Score
PlayerData.Score = PlayerData.Score + Score
MESSAGE:NewType( self.DisplayMessagePrefix .. Text, MESSAGE.Type.Information ):ToAll()
self:ScoreCSV( PlayerName, "", "GOAL_" .. string.upper( GoalTag ), 1, Score, nil )
end
end
--- Add a goal score for a player.
-- The method takes the PlayerUnit for which the Goal score needs to be set.
-- The GoalTag is a string or identifier that is taken into the CSV file scoring log to identify the goal.
@ -668,7 +704,7 @@ function SCORING:AddGoalScore( PlayerUnit, GoalTag, Text, Score )
PlayerData.Goals[GoalTag].Score = PlayerData.Goals[GoalTag].Score + Score
PlayerData.Score = PlayerData.Score + Score
MESSAGE:New( self.DisplayMessagePrefix .. Text, 30 ):ToAll()
MESSAGE:NewType( self.DisplayMessagePrefix .. Text, MESSAGE.Type.Information ):ToAll()
self:ScoreCSV( PlayerName, "", "GOAL_" .. string.upper( GoalTag ), 1, Score, PlayerUnit:GetName() )
end
@ -704,12 +740,45 @@ function SCORING:_AddMissionTaskScore( Mission, PlayerUnit, Text, Score )
PlayerData.Score = self.Players[PlayerName].Score + Score
PlayerData.Mission[MissionName].ScoreTask = self.Players[PlayerName].Mission[MissionName].ScoreTask + Score
MESSAGE:New( self.DisplayMessagePrefix .. MissionName .. " : " .. Text .. " Score: " .. Score, 30 ):ToAll()
MESSAGE:NewType( self.DisplayMessagePrefix .. MissionName .. " : " .. Text .. " Score: " .. Score, MESSAGE.Type.Information ):ToAll()
self:ScoreCSV( PlayerName, "", "TASK_" .. MissionName:gsub( ' ', '_' ), 1, Score, PlayerUnit:GetName() )
end
end
--- Registers Scores the players completing a Mission Task.
-- @param #SCORING self
-- @param Tasking.Mission#MISSION Mission
-- @param #string PlayerName
-- @param #string Text
-- @param #number Score
function SCORING:_AddMissionGoalScore( Mission, PlayerName, Text, Score )
local MissionName = Mission:GetName()
self:E( { Mission:GetName(), PlayerName, Text, Score } )
-- PlayerName can be nil, if the Unit with the player crashed or due to another reason.
if PlayerName then
local PlayerData = self.Players[PlayerName]
if not PlayerData.Mission[MissionName] then
PlayerData.Mission[MissionName] = {}
PlayerData.Mission[MissionName].ScoreTask = 0
PlayerData.Mission[MissionName].ScoreMission = 0
end
self:T( PlayerName )
self:T( PlayerData.Mission[MissionName] )
PlayerData.Score = self.Players[PlayerName].Score + Score
PlayerData.Mission[MissionName].ScoreTask = self.Players[PlayerName].Mission[MissionName].ScoreTask + Score
MESSAGE:NewType( string.format( "%s%s: %s! Player %s receives %d score!", self.DisplayMessagePrefix, MissionName, Text, PlayerName, Score ), MESSAGE.Type.Information ):ToAll()
self:ScoreCSV( PlayerName, "", "TASK_" .. MissionName:gsub( ' ', '_' ), 1, Score )
end
end
--- Registers Mission Scores for possible multiple players that contributed in the Mission.
-- @param #SCORING self
@ -732,9 +801,9 @@ function SCORING:_AddMissionScore( Mission, Text, Score )
PlayerData.Score = PlayerData.Score + Score
PlayerData.Mission[MissionName].ScoreMission = PlayerData.Mission[MissionName].ScoreMission + Score
MESSAGE:New( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' has " .. Text .. " in Mission '" .. MissionName .. "'. " ..
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' has " .. Text .. " in Mission '" .. MissionName .. "'. " ..
Score .. " mission score!",
60 ):ToAll()
MESSAGE.Type.Information ):ToAll()
self:ScoreCSV( PlayerName, "", "MISSION_" .. MissionName:gsub( ' ', '_' ), 1, Score )
end
@ -902,19 +971,19 @@ function SCORING:_EventOnHit( Event )
if TargetPlayerName ~= nil then -- It is a player hitting another player ...
MESSAGE
:New( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit friendly player '" .. TargetPlayerName .. "' " ..
:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit friendly player '" .. TargetPlayerName .. "' " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.PenaltyHit .. " times. " ..
"Penalty: -" .. PlayerHit.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty,
2
MESSAGE.Type.Update
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
else
MESSAGE
:New( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit a friendly target " ..
:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit friendly target " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.PenaltyHit .. " times. " ..
"Penalty: -" .. PlayerHit.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty,
2
MESSAGE.Type.Update
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
@ -926,19 +995,19 @@ function SCORING:_EventOnHit( Event )
PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1
if TargetPlayerName ~= nil then -- It is a player hitting another player ...
MESSAGE
:New( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit enemy player '" .. TargetPlayerName .. "' " ..
:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit enemy player '" .. TargetPlayerName .. "' " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.ScoreHit .. " times. " ..
"Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty,
2
MESSAGE.Type.Update
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
else
MESSAGE
:New( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit an enemy target " ..
:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit enemy target " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.ScoreHit .. " times. " ..
"Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty,
2
MESSAGE.Type.Update
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
@ -947,8 +1016,8 @@ function SCORING:_EventOnHit( Event )
end
else -- A scenery object was hit.
MESSAGE
:New( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit a scenery object.",
2
:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit scenery object.",
MESSAGE.Type.Update
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
@ -1008,10 +1077,10 @@ function SCORING:_EventOnHit( Event )
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1
MESSAGE
:New( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit a friendly target " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.PenaltyHit .. " times. " ..
"Penalty: -" .. PlayerHit.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty,
2
:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit friendly target " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
"Penalty: -" .. PlayerHit.Penalty .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Update
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
@ -1021,10 +1090,10 @@ function SCORING:_EventOnHit( Event )
PlayerHit.Score = PlayerHit.Score + 1
PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1
MESSAGE
:New( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit an enemy target " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.ScoreHit .. " times. " ..
"Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty,
2
:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit enemy target " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
"Score: +" .. PlayerHit.Score .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Update
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
@ -1032,8 +1101,8 @@ function SCORING:_EventOnHit( Event )
end
else -- A scenery object was hit.
MESSAGE
:New( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit a scenery object.",
2
:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit scenery object.",
MESSAGE.Type.Update
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
@ -1131,19 +1200,19 @@ function SCORING:_EventOnDeadOrCrash( Event )
if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player
MESSAGE
:New( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly player '" .. TargetPlayerName .. "' " ..
TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. TargetDestroy.PenaltyDestroy .. " times. " ..
"Penalty: -" .. TargetDestroy.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty,
15
:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly player '" .. TargetPlayerName .. "' " ..
TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Penalty: -" .. TargetDestroy.Penalty .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information
)
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
else
MESSAGE
:New( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed a friendly target " ..
TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. TargetDestroy.PenaltyDestroy .. " times. " ..
"Penalty: -" .. TargetDestroy.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty,
15
:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly target " ..
TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Penalty: -" .. TargetDestroy.Penalty .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information
)
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
@ -1165,19 +1234,19 @@ function SCORING:_EventOnDeadOrCrash( Event )
TargetDestroy.ScoreDestroy = TargetDestroy.ScoreDestroy + 1
if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player
MESSAGE
:New( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy player '" .. TargetPlayerName .. "' " ..
TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. TargetDestroy.ScoreDestroy .. " times. " ..
"Score: " .. TargetDestroy.Score .. ". Score Total:" .. Player.Score - Player.Penalty,
15
:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy player '" .. TargetPlayerName .. "' " ..
TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Score: +" .. TargetDestroy.Score .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information
)
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
else
MESSAGE
:New( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed an enemy " ..
TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. TargetDestroy.ScoreDestroy .. " times. " ..
"Score: " .. TargetDestroy.Score .. ". Total:" .. Player.Score - Player.Penalty,
15
:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy " ..
TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Score: +" .. TargetDestroy.Score .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information
)
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
@ -1191,9 +1260,9 @@ function SCORING:_EventOnDeadOrCrash( Event )
Player.Score = Player.Score + Score
TargetDestroy.Score = TargetDestroy.Score + Score
MESSAGE
:New( self.DisplayMessagePrefix .. "Special target '" .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. " destroyed! " ..
:NewType( self.DisplayMessagePrefix .. "Special target '" .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. " destroyed! " ..
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! Total: " .. Player.Score - Player.Penalty,
15
MESSAGE.Type.Information
)
:ToAllIf( self:IfMessagesScore() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesScore() and self:IfMessagesToCoalition() )
@ -1210,10 +1279,10 @@ function SCORING:_EventOnDeadOrCrash( Event )
Player.Score = Player.Score + Score
TargetDestroy.Score = TargetDestroy.Score + Score
MESSAGE
:New( self.DisplayMessagePrefix .. "Target destroyed in zone '" .. ScoreZone:GetName() .. "'." ..
:NewType( self.DisplayMessagePrefix .. "Target destroyed in zone '" .. ScoreZone:GetName() .. "'." ..
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " ..
"Total: " .. Player.Score - Player.Penalty,
15 )
MESSAGE.Type.Information )
:ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() )
self:ScoreCSV( PlayerName, TargetPlayerName, "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
@ -1232,10 +1301,10 @@ function SCORING:_EventOnDeadOrCrash( Event )
Player.Score = Player.Score + Score
TargetDestroy.Score = TargetDestroy.Score + Score
MESSAGE
:New( self.DisplayMessagePrefix .. "Scenery destroyed in zone '" .. ScoreZone:GetName() .. "'." ..
:NewType( self.DisplayMessagePrefix .. "Scenery destroyed in zone '" .. ScoreZone:GetName() .. "'." ..
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " ..
"Total: " .. Player.Score - Player.Penalty,
15
MESSAGE.Type.Information
)
:ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() )
@ -1522,7 +1591,7 @@ function SCORING:ReportScoreGroupSummary( PlayerGroup )
PlayerScore,
PlayerPenalty
)
MESSAGE:New( PlayerMessage, 30, "Player '" .. PlayerName .. "'" ):ToGroup( PlayerGroup )
MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Detailed ):ToGroup( PlayerGroup )
end
end
@ -1579,7 +1648,7 @@ function SCORING:ReportScoreGroupDetailed( PlayerGroup )
ReportGoals,
ReportMissions
)
MESSAGE:New( PlayerMessage, 30, "Player '" .. PlayerName .. "'" ):ToGroup( PlayerGroup )
MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Detailed ):ToGroup( PlayerGroup )
end
end
@ -1628,7 +1697,7 @@ function SCORING:ReportScoreAllSummary( PlayerGroup )
PlayerScore,
PlayerPenalty
)
MESSAGE:New( PlayerMessage, 30, "Player '" .. PlayerName .. "'" ):ToGroup( PlayerGroup )
MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Overview ):ToGroup( PlayerGroup )
end
end

View File

@ -0,0 +1,442 @@
--- **Functional** -- (WIP R2.3) Models the process to capture a Zone for a Coalition, which is guarded by another Coalition.
--
-- ====
--
-- ![Banner Image](..\Presentations\ZONE_CAPTURE_COALITION\Dia1.JPG)
--
-- ===
--
-- ### Contributions: **Millertime**: Concept
-- ### Author: **Sven Van de Velde (FlightControl)**
--
-- ====
--
-- @module ZoneCaptureCoalition
do -- ZONE_CAPTURE_COALITION
--- @type ZONE_CAPTURE_COALITION
-- @extends Functional.ZoneGoalCoalition#ZONE_GOAL_COALITION
--- # ZONE\_CAPTURE\_COALITION class, extends @{ZoneGoalCoalition#ZONE_GOAL_COALITION}
--
-- Models the process to capture a Zone for a Coalition, which is guarded by another Coalition.
--
-- ---
--
-- ![Banner Image](..\Presentations\ZONE_CAPTURE_COALITION\Dia1.JPG)
--
-- ---
--
-- The Zone is initially **Guarded** by the __owning coalition__, which is the coalition that initially occupies the zone with units of its coalition.
-- Once units of an other coalition are entering the Zone, the state will change to **Attacked**. As long as these units remain in the zone, the state keeps set to Attacked.
-- When all units are destroyed in the Zone, the state will change to **Empty**, which expresses that the Zone is empty, and can be captured.
-- When units of the other coalition are in the Zone, and no other units of the owning coalition is in the Zone, the Zone is captured, and its state will change to **Captured**.
--
-- Event handlers can be defined by the mission designer to action on the state transitions.
--
-- ## 1. ZONE\_CAPTURE\_COALITION constructor
--
-- * @{#ZONE_CAPTURE_COALITION.New}(): Creates a new ZONE\_CAPTURE\_COALITION object.
--
-- ## 2. ZONE\_CAPTURE\_COALITION is a finite state machine (FSM).
--
-- ### 2.1 ZONE\_CAPTURE\_COALITION States
--
-- * **Captured**: The Zone has been captured by an other coalition.
-- * **Attacked**: The Zone is currently intruded by an other coalition. There are units of the owning coalition and an other coalition in the Zone.
-- * **Guarded**: The Zone is guarded by the owning coalition. There is no other unit of an other coalition in the Zone.
-- * **Empty**: The Zone is empty. There is not valid unit in the Zone.
--
-- ### 2.2 ZONE\_CAPTURE\_COALITION Events
--
-- * **Capture**: The Zone has been captured by an other coalition.
-- * **Attack**: The Zone is currently intruded by an other coalition. There are units of the owning coalition and an other coalition in the Zone.
-- * **Guard**: The Zone is guarded by the owning coalition. There is no other unit of an other coalition in the Zone.
-- * **Empty**: The Zone is empty. There is not valid unit in the Zone.
--
-- @field #ZONE_CAPTURE_COALITION
ZONE_CAPTURE_COALITION = {
ClassName = "ZONE_CAPTURE_COALITION",
}
--- @field #table ZONE_CAPTURE_COALITION.States
ZONE_CAPTURE_COALITION.States = {}
--- ZONE_CAPTURE_COALITION Constructor.
-- @param #ZONE_CAPTURE_COALITION self
-- @param Core.Zone#ZONE Zone A @{Zone} object with the goal to be achieved.
-- @param DCSCoalition.DCSCoalition#coalition Coalition The initial coalition owning the zone.
-- @return #ZONE_CAPTURE_COALITION
-- @usage
--
-- AttackZone = ZONE:New( "AttackZone" )
--
-- ZoneCaptureCoalition = ZONE_CAPTURE_COALITION:New( AttackZone, coalition.side.RED ) -- Create a new ZONE_CAPTURE_COALITION object of zone AttackZone with ownership RED coalition.
-- ZoneCaptureCoalition:__Guard( 1 ) -- Start the Guarding of the AttackZone.
--
function ZONE_CAPTURE_COALITION:New( Zone, Coalition )
local self = BASE:Inherit( self, ZONE_GOAL_COALITION:New( Zone, Coalition ) ) -- #ZONE_CAPTURE_COALITION
self:F( { Zone = Zone, Coalition = Coalition } )
do
--- Captured State Handler OnLeave for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnLeaveCaptured
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Captured State Handler OnEnter for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnEnterCaptured
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
end
do
--- Attacked State Handler OnLeave for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnLeaveAttacked
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Attacked State Handler OnEnter for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnEnterAttacked
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
end
do
--- Guarded State Handler OnLeave for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnLeaveGuarded
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Guarded State Handler OnEnter for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnEnterGuarded
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
end
do
--- Empty State Handler OnLeave for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnLeaveEmpty
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Empty State Handler OnEnter for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnEnterEmpty
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
end
self:AddTransition( "*", "Guard", "Guarded" )
--- Guard Handler OnBefore for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnBeforeGuard
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Guard Handler OnAfter for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnAfterGuard
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Guard Trigger for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] Guard
-- @param #ZONE_CAPTURE_COALITION self
--- Guard Asynchronous Trigger for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] __Guard
-- @param #ZONE_CAPTURE_COALITION self
-- @param #number Delay
self:AddTransition( "*", "Empty", "Empty" )
--- Empty Handler OnBefore for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnBeforeEmpty
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Empty Handler OnAfter for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnAfterEmpty
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Empty Trigger for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] Empty
-- @param #ZONE_CAPTURE_COALITION self
--- Empty Asynchronous Trigger for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] __Empty
-- @param #ZONE_CAPTURE_COALITION self
-- @param #number Delay
self:AddTransition( { "Guarded", "Empty" }, "Attack", "Attacked" )
--- Attack Handler OnBefore for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnBeforeAttack
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Attack Handler OnAfter for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnAfterAttack
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Attack Trigger for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] Attack
-- @param #ZONE_CAPTURE_COALITION self
--- Attack Asynchronous Trigger for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] __Attack
-- @param #ZONE_CAPTURE_COALITION self
-- @param #number Delay
self:AddTransition( { "Guarded", "Attacked", "Empty" }, "Capture", "Captured" )
--- Capture Handler OnBefore for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnBeforeCapture
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Capture Handler OnAfter for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnAfterCapture
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Capture Trigger for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] Capture
-- @param #ZONE_CAPTURE_COALITION self
--- Capture Asynchronous Trigger for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] __Capture
-- @param #ZONE_CAPTURE_COALITION self
-- @param #number Delay
if not self.ScheduleStatusZone then
self.ScheduleStatusZone = self:ScheduleRepeat( 15, 15, 0.1, nil, self.StatusZone, self )
end
return self
end
--- @param #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:onenterCaptured()
self:GetParent( self, ZONE_CAPTURE_COALITION ).onenterCaptured( self )
self.Goal:Achieved()
end
function ZONE_CAPTURE_COALITION:IsGuarded()
local IsGuarded = self.Zone:IsAllInZoneOfCoalition( self.Coalition )
self:E( { IsGuarded = IsGuarded } )
return IsGuarded
end
function ZONE_CAPTURE_COALITION:IsEmpty()
local IsEmpty = self.Zone:IsNoneInZone()
self:E( { IsEmpty = IsEmpty } )
return IsEmpty
end
function ZONE_CAPTURE_COALITION:IsCaptured()
local IsCaptured = self.Zone:IsAllInZoneOfOtherCoalition( self.Coalition )
self:E( { IsCaptured = IsCaptured } )
return IsCaptured
end
function ZONE_CAPTURE_COALITION:IsAttacked()
local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition )
self:E( { IsAttacked = IsAttacked } )
return IsAttacked
end
--- Mark.
-- @param #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:Mark()
local Coord = self.Zone:GetCoordinate()
local ZoneName = self:GetZoneName()
local State = self:GetState()
if self.MarkRed and self.MarkBlue then
self:E( { MarkRed = self.MarkRed, MarkBlue = self.MarkBlue } )
Coord:RemoveMark( self.MarkRed )
Coord:RemoveMark( self.MarkBlue )
end
if self.Coalition == coalition.side.BLUE then
self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Blue\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State )
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Blue\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
else
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Red\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State )
self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Red\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
end
end
--- Bound.
-- @param #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:onenterGuarded()
--self:GetParent( self ):onenterGuarded()
if self.Coalition == coalition.side.BLUE then
--elf.ProtectZone:BoundZone( 12, country.id.USA )
else
--self.ProtectZone:BoundZone( 12, country.id.RUSSIA )
end
self:Mark()
end
function ZONE_CAPTURE_COALITION:onenterCaptured()
--self:GetParent( self ):onenterCaptured()
local NewCoalition = self.Zone:GetScannedCoalition()
self:E( { NewCoalition = NewCoalition } )
self:SetCoalition( NewCoalition )
self:Mark()
end
function ZONE_CAPTURE_COALITION:onenterEmpty()
--self:GetParent( self ):onenterEmpty()
self:Mark()
end
function ZONE_CAPTURE_COALITION:onenterAttacked()
--self:GetParent( self ):onenterAttacked()
self:Mark()
end
--- When started, check the Coalition status.
-- @param #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:onafterGuard()
--self:E({BASE:GetParent( self )})
--BASE:GetParent( self ).onafterGuard( self )
if not self.SmokeScheduler then
self.SmokeScheduler = self:ScheduleRepeat( 1, 1, 0.1, nil, self.StatusSmoke, self )
end
end
function ZONE_CAPTURE_COALITION:IsCaptured()
local IsCaptured = self.Zone:IsAllInZoneOfOtherCoalition( self.Coalition )
self:E( { IsCaptured = IsCaptured } )
return IsCaptured
end
function ZONE_CAPTURE_COALITION:IsAttacked()
local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition )
self:E( { IsAttacked = IsAttacked } )
return IsAttacked
end
--- Check status Coalition ownership.
-- @param #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:StatusZone()
local State = self:GetState()
self:E( { State = self:GetState() } )
self:GetParent( self, ZONE_CAPTURE_COALITION ).StatusZone( self )
if State ~= "Guarded" and self:IsGuarded() then
self:Guard()
end
if State ~= "Empty" and self:IsEmpty() then
self:Empty()
end
if State ~= "Attacked" and self:IsAttacked() then
self:Attack()
end
if State ~= "Captured" and self:IsCaptured() then
self:Capture()
end
end
end

View File

@ -0,0 +1,177 @@
--- **Functional (WIP)** -- Base class that models processes to achieve goals involving a Zone.
--
-- ====
--
-- ZONE_GOAL models processes that have a Goal with a defined achievement involving a Zone.
-- Derived classes implement the ways how the achievements can be realized.
--
-- ====
--
-- ### Author: **Sven Van de Velde (FlightControl)**
--
-- ====
--
-- @module ZoneGoal
do -- Zone
--- @type ZONE_GOAL
-- @extends Core.Fsm#FSM
--- # ZONE_GOAL class, extends @{Fsm#FSM}
--
-- ZONE_GOAL models processes that have a Goal with a defined achievement involving a Zone.
-- Derived classes implement the ways how the achievements can be realized.
--
-- ## 1. ZONE_GOAL constructor
--
-- * @{#ZONE_GOAL.New}(): Creates a new ZONE_GOAL object.
--
-- ## 2. ZONE_GOAL is a finite state machine (FSM).
--
-- ### 2.1 ZONE_GOAL States
--
-- * None: Initial State
--
-- ### 2.2 ZONE_GOAL Events
--
-- * DestroyedUnit: A @{Unit} is destroyed in the Zone. The event will only get triggered if the method @{#ZONE_GOAL.MonitorDestroyedUnits}() is used.
--
-- @field #ZONE_GOAL
ZONE_GOAL = {
ClassName = "ZONE_GOAL",
}
--- ZONE_GOAL Constructor.
-- @param #ZONE_GOAL self
-- @param Core.Zone#ZONE_BASE Zone A @{Zone} object with the goal to be achieved.
-- @return #ZONE_GOAL
function ZONE_GOAL:New( Zone )
local self = BASE:Inherit( self, FSM:New() ) -- #ZONE_GOAL
self:F( { Zone = Zone } )
self.Zone = Zone -- Core.Zone#ZONE_BASE
self.Goal = GOAL:New()
self.SmokeTime = nil
self:AddTransition( "*", "DestroyedUnit", "*" )
--- DestroyedUnit Handler OnAfter for ZONE_GOAL
-- @function [parent=#ZONE_GOAL] OnAfterDestroyedUnit
-- @param #ZONE_GOAL self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Unit#UNIT DestroyedUnit The destroyed unit.
-- @param #string PlayerName The name of the player.
return self
end
--- Get the Zone
-- @param #ZONE_GOAL self
-- @return Core.Zone#ZONE_BASE
function ZONE_GOAL:GetZone()
return self.Zone
end
--- Get the name of the ProtectZone
-- @param #ZONE_GOAL self
-- @return #string
function ZONE_GOAL:GetZoneName()
return self.Zone:GetName()
end
--- Smoke the center of theh zone.
-- @param #ZONE_GOAL self
-- @param #SMOKECOLOR.Color SmokeColor
function ZONE_GOAL:Smoke( SmokeColor )
self:F( { SmokeColor = SmokeColor} )
self.SmokeColor = SmokeColor
end
--- Flare the center of the zone.
-- @param #ZONE_GOAL self
-- @param #SMOKECOLOR.Color FlareColor
function ZONE_GOAL:Flare( FlareColor )
self.Zone:FlareZone( FlareColor, math.random( 1, 360 ) )
end
--- When started, check the Smoke and the Zone status.
-- @param #ZONE_GOAL self
function ZONE_GOAL:onafterGuard()
--self:GetParent( self ):onafterStart()
self:E("Guard")
--self:ScheduleRepeat( 15, 15, 0.1, nil, self.StatusZone, self )
if not self.SmokeScheduler then
self.SmokeScheduler = self:ScheduleRepeat( 1, 1, 0.1, nil, self.StatusSmoke, self )
end
end
--- Check status Smoke.
-- @param #ZONE_GOAL self
function ZONE_GOAL:StatusSmoke()
self:F({self.SmokeTime, self.SmokeColor})
local CurrentTime = timer.getTime()
if self.SmokeTime == nil or self.SmokeTime + 300 <= CurrentTime then
if self.SmokeColor then
self.Zone:GetCoordinate():Smoke( self.SmokeColor )
--self.SmokeColor = nil
self.SmokeTime = CurrentTime
end
end
end
--- @param #ZONE_GOAL self
-- @param Core.Event#EVENTDATA EventData
function ZONE_GOAL:__Destroyed( EventData )
self:E( { "EventDead", EventData } )
self:E( { EventData.IniUnit } )
local Vec3 = EventData.IniDCSUnit:getPosition().p
self:E( { Vec3 = Vec3 } )
local ZoneGoal = self:GetZone()
self:E({ZoneGoal})
if EventData.IniDCSUnit then
if ZoneGoal:IsVec3InZone(Vec3) then
local PlayerHits = _DATABASE.HITS[EventData.IniUnitName]
if PlayerHits then
for PlayerName, PlayerHit in pairs( PlayerHits.Players or {} ) do
self.Goal:AddPlayerContribution( PlayerName )
self:DestroyedUnit( EventData.IniUnitName, PlayerName )
end
end
end
end
end
--- Activate the event UnitDestroyed to be fired when a unit is destroyed in the zone.
-- @param #ZONE_GOAL self
function ZONE_GOAL:MonitorDestroyedUnits()
self:HandleEvent( EVENTS.Dead, self.__Destroyed )
self:HandleEvent( EVENTS.Crash, self.__Destroyed )
end
end

View File

@ -0,0 +1,452 @@
--- **Functional (WIP)** -- Base class that models processes to achieve goals involving a Zone and Cargo.
--
-- ====
--
-- ZONE_GOAL_CARGO models processes that have a Goal with a defined achievement involving a Zone and Cargo.
-- Derived classes implement the ways how the achievements can be realized.
--
-- ====
--
-- ### Author: **Sven Van de Velde (FlightControl)**
--
-- ====
--
-- @module ZoneGoalCargo
do -- ZoneGoal
--- @type ZONE_GOAL_CARGO
-- @extends Functional.ZoneGoal#ZONE_GOAL
--- # ZONE_GOAL_CARGO class, extends @{ZoneGoal#ZONE_GOAL}
--
-- ZONE_GOAL_CARGO models processes that have a Goal with a defined achievement involving a Zone and Cargo.
-- Derived classes implement the ways how the achievements can be realized.
--
-- ## 1. ZONE_GOAL_CARGO constructor
--
-- * @{#ZONE_GOAL_CARGO.New}(): Creates a new ZONE_GOAL_CARGO object.
--
-- ## 2. ZONE_GOAL_CARGO is a finite state machine (FSM).
--
-- ### 2.1 ZONE_GOAL_CARGO States
--
-- * **Deployed**: The Zone has been captured by an other coalition.
-- * **Airborne**: The Zone is currently intruded by an other coalition. There are units of the owning coalition and an other coalition in the Zone.
-- * **Loaded**: The Zone is guarded by the owning coalition. There is no other unit of an other coalition in the Zone.
-- * **Empty**: The Zone is empty. There is not valid unit in the Zone.
--
-- ### 2.2 ZONE_GOAL_CARGO Events
--
-- * **Capture**: The Zone has been captured by an other coalition.
-- * **Attack**: The Zone is currently intruded by an other coalition. There are units of the owning coalition and an other coalition in the Zone.
-- * **Guard**: The Zone is guarded by the owning coalition. There is no other unit of an other coalition in the Zone.
-- * **Empty**: The Zone is empty. There is not valid unit in the Zone.
--
-- ### 2.3 ZONE_GOAL_CARGO State Machine
--
-- @field #ZONE_GOAL_CARGO
ZONE_GOAL_CARGO = {
ClassName = "ZONE_GOAL_CARGO",
}
--- @field #table ZONE_GOAL_CARGO.States
ZONE_GOAL_CARGO.States = {}
--- ZONE_GOAL_CARGO Constructor.
-- @param #ZONE_GOAL_CARGO self
-- @param Core.Zone#ZONE Zone A @{Zone} object with the goal to be achieved.
-- @param DCSCoalition.DCSCoalition#coalition Coalition The initial coalition owning the zone.
-- @return #ZONE_GOAL_CARGO
function ZONE_GOAL_CARGO:New( Zone, Coalition )
local self = BASE:Inherit( self, ZONE_GOAL:New( Zone ) ) -- #ZONE_GOAL_CARGO
self:F( { Zone = Zone, Coalition = Coalition } )
self:SetCoalition( Coalition )
do
--- Captured State Handler OnLeave for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnLeaveCaptured
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Captured State Handler OnEnter for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnEnterCaptured
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
end
do
--- Attacked State Handler OnLeave for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnLeaveAttacked
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Attacked State Handler OnEnter for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnEnterAttacked
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
end
do
--- Guarded State Handler OnLeave for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnLeaveGuarded
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Guarded State Handler OnEnter for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnEnterGuarded
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
end
do
--- Empty State Handler OnLeave for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnLeaveEmpty
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Empty State Handler OnEnter for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnEnterEmpty
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
end
self:AddTransition( "*", "Guard", "Guarded" )
--- Guard Handler OnBefore for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnBeforeGuard
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Guard Handler OnAfter for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnAfterGuard
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Guard Trigger for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] Guard
-- @param #ZONE_GOAL_CARGO self
--- Guard Asynchronous Trigger for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] __Guard
-- @param #ZONE_GOAL_CARGO self
-- @param #number Delay
self:AddTransition( "*", "Empty", "Empty" )
--- Empty Handler OnBefore for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnBeforeEmpty
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Empty Handler OnAfter for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnAfterEmpty
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Empty Trigger for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] Empty
-- @param #ZONE_GOAL_CARGO self
--- Empty Asynchronous Trigger for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] __Empty
-- @param #ZONE_GOAL_CARGO self
-- @param #number Delay
self:AddTransition( { "Guarded", "Empty" }, "Attack", "Attacked" )
--- Attack Handler OnBefore for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnBeforeAttack
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Attack Handler OnAfter for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnAfterAttack
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Attack Trigger for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] Attack
-- @param #ZONE_GOAL_CARGO self
--- Attack Asynchronous Trigger for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] __Attack
-- @param #ZONE_GOAL_CARGO self
-- @param #number Delay
self:AddTransition( { "Guarded", "Attacked", "Empty" }, "Capture", "Captured" )
--- Capture Handler OnBefore for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnBeforeCapture
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Capture Handler OnAfter for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnAfterCapture
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Capture Trigger for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] Capture
-- @param #ZONE_GOAL_CARGO self
--- Capture Asynchronous Trigger for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] __Capture
-- @param #ZONE_GOAL_CARGO self
-- @param #number Delay
return self
end
--- Set the owning coalition of the zone.
-- @param #ZONE_GOAL_CARGO self
-- @param DCSCoalition.DCSCoalition#coalition Coalition
function ZONE_GOAL_CARGO:SetCoalition( Coalition )
self.Coalition = Coalition
end
--- Get the owning coalition of the zone.
-- @param #ZONE_GOAL_CARGO self
-- @return DCSCoalition.DCSCoalition#coalition Coalition.
function ZONE_GOAL_CARGO:GetCoalition()
return self.Coalition
end
--- Get the owning coalition name of the zone.
-- @param #ZONE_GOAL_CARGO self
-- @return #string Coalition name.
function ZONE_GOAL_CARGO:GetCoalitionName()
if self.Coalition == coalition.side.BLUE then
return "Blue"
end
if self.Coalition == coalition.side.RED then
return "Red"
end
if self.Coalition == coalition.side.NEUTRAL then
return "Neutral"
end
return ""
end
function ZONE_GOAL_CARGO:IsGuarded()
local IsGuarded = self.Zone:IsAllInZoneOfCoalition( self.Coalition )
self:E( { IsGuarded = IsGuarded } )
return IsGuarded
end
function ZONE_GOAL_CARGO:IsEmpty()
local IsEmpty = self.Zone:IsNoneInZone()
self:E( { IsEmpty = IsEmpty } )
return IsEmpty
end
function ZONE_GOAL_CARGO:IsCaptured()
local IsCaptured = self.Zone:IsAllInZoneOfOtherCoalition( self.Coalition )
self:E( { IsCaptured = IsCaptured } )
return IsCaptured
end
function ZONE_GOAL_CARGO:IsAttacked()
local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition )
self:E( { IsAttacked = IsAttacked } )
return IsAttacked
end
--- Mark.
-- @param #ZONE_GOAL_CARGO self
function ZONE_GOAL_CARGO:Mark()
local Coord = self.Zone:GetCoordinate()
local ZoneName = self:GetZoneName()
local State = self:GetState()
if self.MarkRed and self.MarkBlue then
self:E( { MarkRed = self.MarkRed, MarkBlue = self.MarkBlue } )
Coord:RemoveMark( self.MarkRed )
Coord:RemoveMark( self.MarkBlue )
end
if self.Coalition == coalition.side.BLUE then
self.MarkBlue = Coord:MarkToCoalitionBlue( "Guard Zone: " .. ZoneName .. "\nStatus: " .. State )
self.MarkRed = Coord:MarkToCoalitionRed( "Capture Zone: " .. ZoneName .. "\nStatus: " .. State )
else
self.MarkRed = Coord:MarkToCoalitionRed( "Guard Zone: " .. ZoneName .. "\nStatus: " .. State )
self.MarkBlue = Coord:MarkToCoalitionBlue( "Capture Zone: " .. ZoneName .. "\nStatus: " .. State )
end
end
--- Bound.
-- @param #ZONE_GOAL_CARGO self
function ZONE_GOAL_CARGO:onenterGuarded()
--self:GetParent( self ):onenterGuarded()
if self.Coalition == coalition.side.BLUE then
--elf.ProtectZone:BoundZone( 12, country.id.USA )
else
--self.ProtectZone:BoundZone( 12, country.id.RUSSIA )
end
self:Mark()
end
function ZONE_GOAL_CARGO:onenterCaptured()
--self:GetParent( self ):onenterCaptured()
local NewCoalition = self.Zone:GetCoalition()
self:E( { NewCoalition = NewCoalition } )
self:SetCoalition( NewCoalition )
self:Mark()
end
function ZONE_GOAL_CARGO:onenterEmpty()
--self:GetParent( self ):onenterEmpty()
self:Mark()
end
function ZONE_GOAL_CARGO:onenterAttacked()
--self:GetParent( self ):onenterAttacked()
self:Mark()
end
--- When started, check the Coalition status.
-- @param #ZONE_GOAL_CARGO self
function ZONE_GOAL_CARGO:onafterGuard()
--self:E({BASE:GetParent( self )})
--BASE:GetParent( self ).onafterGuard( self )
if not self.SmokeScheduler then
self.SmokeScheduler = self:ScheduleRepeat( 1, 1, 0.1, nil, self.StatusSmoke, self )
end
if not self.ScheduleStatusZone then
self.ScheduleStatusZone = self:ScheduleRepeat( 15, 15, 0.1, nil, self.StatusZone, self )
end
end
function ZONE_GOAL_CARGO:IsCaptured()
local IsCaptured = self.Zone:IsAllInZoneOfOtherCoalition( self.Coalition )
self:E( { IsCaptured = IsCaptured } )
return IsCaptured
end
function ZONE_GOAL_CARGO:IsAttacked()
local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition )
self:E( { IsAttacked = IsAttacked } )
return IsAttacked
end
--- Check status Coalition ownership.
-- @param #ZONE_GOAL_CARGO self
function ZONE_GOAL_CARGO:StatusZone()
local State = self:GetState()
self:E( { State = self:GetState() } )
self.Zone:Scan()
if State ~= "Guarded" and self:IsGuarded() then
self:Guard()
end
if State ~= "Empty" and self:IsEmpty() then
self:Empty()
end
if State ~= "Attacked" and self:IsAttacked() then
self:Attack()
end
if State ~= "Captured" and self:IsCaptured() then
self:Capture()
end
end
end

View File

@ -0,0 +1,113 @@
--- **Functional (WIP)** -- Base class that models processes to achieve goals involving a Zone for a Coalition.
--
-- ====
--
-- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition.
-- Derived classes implement the ways how the achievements can be realized.
--
-- ====
--
-- ### Author: **Sven Van de Velde (FlightControl)**
--
-- ====
--
-- @module ZoneGoalCoalition
do -- ZoneGoal
--- @type ZONE_GOAL_COALITION
-- @extends Functional.ZoneGoal#ZONE_GOAL
--- # ZONE_GOAL_COALITION class, extends @{ZoneGoal#ZONE_GOAL}
--
-- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition.
-- Derived classes implement the ways how the achievements can be realized.
--
-- ## 1. ZONE_GOAL_COALITION constructor
--
-- * @{#ZONE_GOAL_COALITION.New}(): Creates a new ZONE_GOAL_COALITION object.
--
-- ## 2. ZONE_GOAL_COALITION is a finite state machine (FSM).
--
-- ### 2.1 ZONE_GOAL_COALITION States
--
-- ### 2.2 ZONE_GOAL_COALITION Events
--
-- ### 2.3 ZONE_GOAL_COALITION State Machine
--
-- @field #ZONE_GOAL_COALITION
ZONE_GOAL_COALITION = {
ClassName = "ZONE_GOAL_COALITION",
}
--- @field #table ZONE_GOAL_COALITION.States
ZONE_GOAL_COALITION.States = {}
--- ZONE_GOAL_COALITION Constructor.
-- @param #ZONE_GOAL_COALITION self
-- @param Core.Zone#ZONE Zone A @{Zone} object with the goal to be achieved.
-- @param DCSCoalition.DCSCoalition#coalition Coalition The initial coalition owning the zone.
-- @return #ZONE_GOAL_COALITION
function ZONE_GOAL_COALITION:New( Zone, Coalition )
local self = BASE:Inherit( self, ZONE_GOAL:New( Zone ) ) -- #ZONE_GOAL_COALITION
self:F( { Zone = Zone, Coalition = Coalition } )
self:SetCoalition( Coalition )
return self
end
--- Set the owning coalition of the zone.
-- @param #ZONE_GOAL_COALITION self
-- @param DCSCoalition.DCSCoalition#coalition Coalition
function ZONE_GOAL_COALITION:SetCoalition( Coalition )
self.Coalition = Coalition
end
--- Get the owning coalition of the zone.
-- @param #ZONE_GOAL_COALITION self
-- @return DCSCoalition.DCSCoalition#coalition Coalition.
function ZONE_GOAL_COALITION:GetCoalition()
return self.Coalition
end
--- Get the owning coalition name of the zone.
-- @param #ZONE_GOAL_COALITION self
-- @return #string Coalition name.
function ZONE_GOAL_COALITION:GetCoalitionName()
if self.Coalition == coalition.side.BLUE then
return "Blue"
end
if self.Coalition == coalition.side.RED then
return "Red"
end
if self.Coalition == coalition.side.NEUTRAL then
return "Neutral"
end
return ""
end
--- Check status Coalition ownership.
-- @param #ZONE_GOAL_COALITION self
function ZONE_GOAL_COALITION:StatusZone()
local State = self:GetState()
self:E( { State = self:GetState() } )
self.Zone:Scan( { Object.Category.UNIT, Object.Category.STATIC } )
end
end

View File

@ -13,80 +13,7 @@
--- The REPORT class
-- @type REPORT
-- @extends Core.Base#BASE
REPORT = {
ClassName = "REPORT",
Title = "",
}
--- Create a new REPORT.
-- @param #REPORT self
-- @param #string Title
-- @return #REPORT
function REPORT:New( Title )
local self = BASE:Inherit( self, BASE:New() ) -- #REPORT
self.Report = {}
Title = Title or ""
if Title then
self.Title = Title
end
self:SetIndent( 3 )
return self
end
--- Has the REPORT Text?
-- @param #REPORT self
-- @return #boolean
function REPORT:HasText() --R2.1
return #self.Report > 0
end
--- Set indent of a REPORT.
-- @param #REPORT self
-- @param #number Indent
-- @return #REPORT
function REPORT:SetIndent( Indent ) --R2.1
self.Indent = Indent
return self
end
--- Add a new line to a REPORT.
-- @param #REPORT self
-- @param #string Text
-- @return #REPORT
function REPORT:Add( Text )
self.Report[#self.Report+1] = Text
return self
end
--- Add a new line to a REPORT.
-- @param #REPORT self
-- @param #string Text
-- @return #REPORT
function REPORT:AddIndent( Text ) --R2.1
self.Report[#self.Report+1] = string.rep(" ", self.Indent ) .. Text:gsub("\n","\n"..string.rep( " ", self.Indent ) )
return self
end
--- Produces the text of the report, taking into account an optional delimeter, which is \n by default.
-- @param #REPORT self
-- @param #string Delimiter (optional) A delimiter text.
-- @return #string The report text.
function REPORT:Text( Delimiter )
Delimiter = Delimiter or "\n"
local ReportText = ( self.Title ~= "" and self.Title .. Delimiter or self.Title ) .. table.concat( self.Report, Delimiter ) or ""
return ReportText
end
--- The COMMANDCENTER class
-- @type COMMANDCENTER
@ -207,6 +134,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
local PlayerGroup = EventData.IniGroup -- The GROUP object should be filled!
Mission:JoinUnit( PlayerUnit, PlayerGroup )
end
self:SetMenu()
end
)
@ -260,6 +188,8 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
)
self:SetMenu()
_SETTINGS:SetSystemMenu( CommandCenterPositionable )
return self
end
@ -428,10 +358,20 @@ end
-- @param #COMMANDCENTER self
-- @param #string Message
-- @param Wrapper.Group#GROUP TaskGroup
-- @param #sring Name (optional) The name of the Group used as a prefix for the message to the Group. If not provided, there will be nothing shown.
function COMMANDCENTER:MessageToGroup( Message, TaskGroup )
self:GetPositionable():MessageToGroup( Message , 15, TaskGroup, self:GetName() )
self:GetPositionable():MessageToGroup( Message, 15, TaskGroup, self:GetName() )
end
--- Send a CC message of a specified type to a GROUP.
-- @param #COMMANDCENTER self
-- @param #string Message
-- @param Wrapper.Group#GROUP TaskGroup
-- @param Core.Message#MESSAGE.MessageType MessageType The type of the message, resulting in automatic time duration and prefix of the message.
function COMMANDCENTER:MessageTypeToGroup( Message, TaskGroup, MessageType )
self:GetPositionable():MessageTypeToGroup( Message, MessageType, TaskGroup, self:GetName() )
end
@ -447,6 +387,20 @@ function COMMANDCENTER:MessageToCoalition( Message )
end
--- Send a CC message of a specified type to the coalition of the CC.
-- @param #COMMANDCENTER self
-- @param #string Message The message.
-- @param Core.Message#MESSAGE.MessageType MessageType The type of the message, resulting in automatic time duration and prefix of the message.
function COMMANDCENTER:MessageTypeToCoalition( Message, MessageType )
local CCCoalition = self:GetPositionable():GetCoalition()
--TODO: Fix coalition bug!
self:GetPositionable():MessageTypeToCoalition( Message, MessageType, CCCoalition )
end
--- Report the status of all MISSIONs to a GROUP.
-- Each Mission is listed, with an indication how many Tasks are still to be completed.
-- @param #COMMANDCENTER self

View File

@ -15,7 +15,7 @@
-- ---------------------------------
-- Derived DETECTION_MANAGER classes will reports detected units using the method @{DetectionManager#DETECTION_MANAGER.ReportDetected}(). This method implements polymorphic behaviour.
--
-- The time interval in seconds of the reporting can be changed using the methods @{DetectionManager#DETECTION_MANAGER.SetReportInterval}().
-- The time interval in seconds of the reporting can be changed using the methods @{DetectionManager#DETECTION_MANAGER.SetRefreshTimeInterval}().
-- To control how long a reporting message is displayed, use @{DetectionManager#DETECTION_MANAGER.SetReportDisplayTime}().
-- Derived classes need to implement the method @{DetectionManager#DETECTION_MANAGER.GetReportDisplayTime}() to use the correct display time for displayed messages during a report.
--
@ -126,7 +126,7 @@ do -- DETECTION MANAGER
self:AddTransition( "Started", "Report", "Started" )
self:SetReportInterval( 30 )
self:SetRefreshTimeInterval( 30 )
self:SetReportDisplayTime( 25 )
self:E( { Detection = Detection } )
@ -143,19 +143,19 @@ do -- DETECTION MANAGER
self:E( "onafterReport" )
self:__Report( -self._ReportInterval )
self:__Report( -self._RefreshTimeInterval )
self:ProcessDetected( self.Detection )
end
--- Set the reporting time interval.
-- @param #DETECTION_MANAGER self
-- @param #number ReportInterval The interval in seconds when a report needs to be done.
-- @param #number RefreshTimeInterval The interval in seconds when a report needs to be done.
-- @return #DETECTION_MANAGER self
function DETECTION_MANAGER:SetReportInterval( ReportInterval )
function DETECTION_MANAGER:SetRefreshTimeInterval( RefreshTimeInterval )
self:F2()
self._ReportInterval = ReportInterval
self._RefreshTimeInterval = RefreshTimeInterval
end

View File

@ -264,14 +264,14 @@ function MISSION:New( CommandCenter, MissionName, MissionPriority, MissionBriefi
end
-- FSM function for a MISSION
--- FSM function for a MISSION
-- @param #MISSION self
-- @param #string From
-- @param #string Event
-- @param #string To
function MISSION:onenterCOMPLETED( From, Event, To )
self:GetCommandCenter():MessageToCoalition( self:GetName() .. " has been completed! Good job guys!" )
self:GetCommandCenter():MessageTypeToCoalition( self:GetName() .. " has been completed! Good job guys!", MESSAGE.Type.Information )
end
--- Gets the mission name.
@ -450,7 +450,7 @@ do -- Group Assignment
local MissionGroupName = MissionGroup:GetName()
self.AssignedGroups[MissionGroupName] = nil
self:E( string.format( "Mission %s is unassigned to %s", MissionName, MissionGroupName ) )
--self:E( string.format( "Mission %s is unassigned to %s", MissionName, MissionGroupName ) )
return self
end
@ -475,7 +475,23 @@ function MISSION:RemoveTaskMenu( Task )
end
--- Gets the mission menu for the coalition.
--- Gets the root mission menu for the TaskGroup.
-- @param #MISSION self
-- @return Core.Menu#MENU_COALITION self
function MISSION:GetRootMenu( TaskGroup ) -- R2.2
local CommandCenter = self:GetCommandCenter()
local CommandCenterMenu = CommandCenter:GetMenu()
local MissionName = self:GetName()
--local MissionMenu = CommandCenterMenu:GetMenu( MissionName )
self.MissionMenu = self.MissionMenu or MENU_COALITION:New( self.MissionCoalition, self:GetName(), CommandCenterMenu )
return self.MissionMenu
end
--- Gets the mission menu for the TaskGroup.
-- @param #MISSION self
-- @return Core.Menu#MENU_COALITION self
function MISSION:GetMenu( TaskGroup ) -- R2.1 -- Changed Menu Structure
@ -486,27 +502,28 @@ function MISSION:GetMenu( TaskGroup ) -- R2.1 -- Changed Menu Structure
local MissionName = self:GetName()
--local MissionMenu = CommandCenterMenu:GetMenu( MissionName )
self.MissionMenu = self.MissionMenu or {}
self.MissionMenu[TaskGroup] = self.MissionMenu[TaskGroup] or {}
self.MissionGroupMenu = self.MissionGroupMenu or {}
self.MissionGroupMenu[TaskGroup] = self.MissionGroupMenu[TaskGroup] or {}
local Menu = self.MissionMenu[TaskGroup]
local GroupMenu = self.MissionGroupMenu[TaskGroup]
Menu.MainMenu = Menu.MainMenu or MENU_GROUP:New( TaskGroup, self:GetName(), CommandCenterMenu )
Menu.BriefingMenu = Menu.BriefingMenu or MENU_GROUP_COMMAND:New( TaskGroup, "Mission Briefing", Menu.MainMenu, self.MenuReportBriefing, self, TaskGroup )
self.MissionMenu = self.MissionMenu or MENU_COALITION:New( self.MissionCoalition, self:GetName(), CommandCenterMenu )
GroupMenu.BriefingMenu = GroupMenu.BriefingMenu or MENU_GROUP_COMMAND:New( TaskGroup, "Mission Briefing", self.MissionMenu, self.MenuReportBriefing, self, TaskGroup )
Menu.TaskReportsMenu = Menu.TaskReportsMenu or MENU_GROUP:New( TaskGroup, "Task Reports", Menu.MainMenu )
Menu.ReportTasksMenu = Menu.ReportTasksMenu or MENU_GROUP_COMMAND:New( TaskGroup, "Report Tasks", Menu.TaskReportsMenu, self.MenuReportTasksSummary, self, TaskGroup )
Menu.ReportPlannedTasksMenu = Menu.ReportPlannedTasksMenu or MENU_GROUP_COMMAND:New( TaskGroup, "Report Planned Tasks", Menu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Planned" )
Menu.ReportAssignedTasksMenu = Menu.ReportAssignedTasksMenu or MENU_GROUP_COMMAND:New( TaskGroup, "Report Assigned Tasks", Menu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Assigned" )
Menu.ReportSuccessTasksMenu = Menu.ReportSuccessTasksMenu or MENU_GROUP_COMMAND:New( TaskGroup, "Report Successful Tasks", Menu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Success" )
Menu.ReportFailedTasksMenu = Menu.ReportFailedTasksMenu or MENU_GROUP_COMMAND:New( TaskGroup, "Report Failed Tasks", Menu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Failed" )
Menu.ReportHeldTasksMenu = Menu.ReportHeldTasksMenu or MENU_GROUP_COMMAND:New( TaskGroup, "Report Held Tasks", Menu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Hold" )
GroupMenu.TaskReportsMenu = GroupMenu.TaskReportsMenu or MENU_GROUP:New( TaskGroup, "Task Reports", self.MissionMenu )
GroupMenu.ReportTasksMenu = GroupMenu.ReportTasksMenu or MENU_GROUP_COMMAND:New( TaskGroup, "Report Tasks", GroupMenu.TaskReportsMenu, self.MenuReportTasksSummary, self, TaskGroup )
GroupMenu.ReportPlannedTasksMenu = GroupMenu.ReportPlannedTasksMenu or MENU_GROUP_COMMAND:New( TaskGroup, "Report Planned Tasks", GroupMenu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Planned" )
GroupMenu.ReportAssignedTasksMenu = GroupMenu.ReportAssignedTasksMenu or MENU_GROUP_COMMAND:New( TaskGroup, "Report Assigned Tasks", GroupMenu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Assigned" )
GroupMenu.ReportSuccessTasksMenu = GroupMenu.ReportSuccessTasksMenu or MENU_GROUP_COMMAND:New( TaskGroup, "Report Successful Tasks", GroupMenu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Success" )
GroupMenu.ReportFailedTasksMenu = GroupMenu.ReportFailedTasksMenu or MENU_GROUP_COMMAND:New( TaskGroup, "Report Failed Tasks", GroupMenu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Failed" )
GroupMenu.ReportHeldTasksMenu = GroupMenu.ReportHeldTasksMenu or MENU_GROUP_COMMAND:New( TaskGroup, "Report Held Tasks", GroupMenu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Hold" )
Menu.PlayerReportsMenu = Menu.PlayerReportsMenu or MENU_GROUP:New( TaskGroup, "Statistics Reports", Menu.MainMenu )
Menu.ReportMissionHistory = Menu.ReportPlayersHistory or MENU_GROUP_COMMAND:New( TaskGroup, "Report Mission Progress", Menu.PlayerReportsMenu, self.MenuReportPlayersProgress, self, TaskGroup )
Menu.ReportPlayersPerTaskMenu = Menu.ReportPlayersPerTaskMenu or MENU_GROUP_COMMAND:New( TaskGroup, "Report Players per Task", Menu.PlayerReportsMenu, self.MenuReportPlayersPerTask, self, TaskGroup )
GroupMenu.PlayerReportsMenu = GroupMenu.PlayerReportsMenu or MENU_GROUP:New( TaskGroup, "Statistics Reports", self.MissionMenu )
GroupMenu.ReportMissionHistory = GroupMenu.ReportPlayersHistory or MENU_GROUP_COMMAND:New( TaskGroup, "Report Mission Progress", GroupMenu.PlayerReportsMenu, self.MenuReportPlayersProgress, self, TaskGroup )
GroupMenu.ReportPlayersPerTaskMenu = GroupMenu.ReportPlayersPerTaskMenu or MENU_GROUP_COMMAND:New( TaskGroup, "Report Players per Task", GroupMenu.PlayerReportsMenu, self.MenuReportPlayersPerTask, self, TaskGroup )
return Menu.MainMenu
return self.MissionMenu
end
@ -843,8 +860,9 @@ end
--- Create a summary report of the Mission (one line).
-- @param #MISSION self
-- @param Wrapper.Group#GROUP ReportGroup
-- @return #string
function MISSION:ReportSummary()
function MISSION:ReportSummary( ReportGroup )
local Report = REPORT:New()
@ -857,9 +875,9 @@ function MISSION:ReportSummary()
Report:Add( string.format( '%s - %s - Task Overview Report', Name, Status ) )
-- Determine how many tasks are remaining.
for TaskID, Task in pairs( self:GetTasks() ) do
for TaskID, Task in UTILS.spairs( self:GetTasks(), function( t, a, b ) return t[a]:ReportOrder( ReportGroup ) < t[b]:ReportOrder( ReportGroup ) end ) do
local Task = Task -- Tasking.Task#TASK
Report:Add( "- " .. Task:ReportSummary() )
Report:Add( "- " .. Task:ReportSummary( ReportGroup ) )
end
return Report:Text()
@ -870,6 +888,8 @@ end
-- @return #string
function MISSION:ReportOverview( ReportGroup, TaskStatus )
self:F( { TaskStatus = TaskStatus } )
local Report = REPORT:New()
-- List the name of the mission.
@ -881,12 +901,17 @@ function MISSION:ReportOverview( ReportGroup, TaskStatus )
Report:Add( string.format( '%s - %s - %s Tasks Report', Name, Status, TaskStatus ) )
-- Determine how many tasks are remaining.
local TasksRemaining = 0
for TaskID, Task in pairs( self:GetTasks() ) do
local Tasks = 0
for TaskID, Task in UTILS.spairs( self:GetTasks(), function( t, a, b ) return t[a]:ReportOrder( ReportGroup ) < t[b]:ReportOrder( ReportGroup ) end ) do
local Task = Task -- Tasking.Task#TASK
if Task:Is( TaskStatus ) then
Report:Add( string.rep( "-", 140 ) )
Report:Add( " - " .. Task:ReportOverview( ReportGroup ) )
end
Tasks = Tasks + 1
if Tasks >= 8 then
break
end
end
return Report:Text()
@ -935,7 +960,7 @@ function MISSION:MenuReportBriefing( ReportGroup )
local Report = self:ReportBriefing()
self:GetCommandCenter():MessageToGroup( Report, ReportGroup )
self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Briefing )
end
@ -945,9 +970,9 @@ end
-- @param Wrapper.Group#GROUP ReportGroup
function MISSION:MenuReportTasksSummary( ReportGroup )
local Report = self:ReportSummary()
local Report = self:ReportSummary( ReportGroup )
self:GetCommandCenter():MessageToGroup( Report, ReportGroup )
self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Overview )
end
@ -960,7 +985,7 @@ function MISSION:MenuReportTasksPerStatus( ReportGroup, TaskStatus )
local Report = self:ReportOverview( ReportGroup, TaskStatus )
self:GetCommandCenter():MessageToGroup( Report, ReportGroup )
self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Overview )
end
@ -970,7 +995,7 @@ function MISSION:MenuReportPlayersPerTask( ReportGroup )
local Report = self:ReportPlayersPerTask()
self:GetCommandCenter():MessageToGroup( Report, ReportGroup )
self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Overview )
end
--- @param #MISSION self
@ -979,7 +1004,7 @@ function MISSION:MenuReportPlayersProgress( ReportGroup )
local Report = self:ReportPlayersProgress()
self:GetCommandCenter():MessageToGroup( Report, ReportGroup )
self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Overview )
end

View File

@ -175,28 +175,34 @@ function TASK:New( Mission, SetGroupAssign, TaskName, TaskType, TaskBriefing )
--- Goal Handler OnBefore for TASK
-- @function [parent=#TASK] OnBeforeGoal
-- @param #TASK self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Unit#UNIT PlayerUnit The @{Unit} of the player.
-- @param #string PlayerName The name of the player.
-- @return #boolean
--- Goal Handler OnAfter for TASK
-- @function [parent=#TASK] OnAfterGoal
-- @param #TASK self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Unit#UNIT PlayerUnit The @{Unit} of the player.
-- @param #string PlayerName The name of the player.
--- Goal Trigger for TASK
-- @function [parent=#TASK] Goal
-- @param #TASK self
-- @param Wrapper.Unit#UNIT PlayerUnit The @{Unit} of the player.
-- @param #string PlayerName The name of the player.
--- Goal Asynchronous Trigger for TASK
-- @function [parent=#TASK] __Goal
-- @param #TASK self
-- @param #number Delay
-- @param Wrapper.Unit#UNIT PlayerUnit The @{Unit} of the player.
-- @param #string PlayerName The name of the player.
@ -272,7 +278,7 @@ function TASK:JoinUnit( PlayerUnit, PlayerGroup )
-- Check if the PlayerGroup is already assigned to the Task. If yes, the PlayerGroup is added to the Task.
-- If the PlayerGroup is not assigned to the Task, the menu needs to be set. In that case, the PlayerUnit will become the GroupPlayer leader.
if self:IsStatePlanned() or self:IsStateReplanned() then
self:SetMenuForGroup( PlayerGroup )
--self:SetMenuForGroup( PlayerGroup )
--self:MessageToGroups( PlayerUnit:GetPlayerName() .. " is planning to join Task " .. self:GetName() )
end
if self:IsStateAssigned() then
@ -309,7 +315,7 @@ function TASK:AbortGroup( PlayerGroup )
self:E( { IsGroupAssigned = IsGroupAssigned } )
if IsGroupAssigned then
local PlayerName = PlayerGroup:GetUnit(1):GetPlayerName()
self:MessageToGroups( PlayerName .. " aborted Task " .. self:GetName() )
--self:MessageToGroups( PlayerName .. " aborted Task " .. self:GetName() )
self:UnAssignFromGroup( PlayerGroup )
--self:Abort()
@ -469,7 +475,7 @@ do -- Group Assignment
local TaskGroupName = TaskGroup:GetName()
self.AssignedGroups[TaskGroupName] = nil
self:E( string.format( "Task %s is unassigned to %s", TaskName, TaskGroupName ) )
--self:E( string.format( "Task %s is unassigned to %s", TaskName, TaskGroupName ) )
-- Set the group to be assigned at mission level. This allows to decide the menu options on mission level for this group.
self:GetMission():ClearGroupAssignment( TaskGroup )
@ -479,9 +485,9 @@ do -- Group Assignment
SetAssignedGroups:ForEachGroup(
function( AssignedGroup )
if self:IsGroupAssigned(AssignedGroup) then
self:GetMission():GetCommandCenter():MessageToGroup( string.format( "Task %s is unassigned from group %s.", TaskName, TaskGroupName ), AssignedGroup )
--self:GetMission():GetCommandCenter():MessageToGroup( string.format( "Task %s is unassigned from group %s.", TaskName, TaskGroupName ), AssignedGroup )
else
self:GetMission():GetCommandCenter():MessageToGroup( string.format( "Task %s is unassigned from your group.", TaskName ), AssignedGroup )
--self:GetMission():GetCommandCenter():MessageToGroup( string.format( "Task %s is unassigned from your group.", TaskName ), AssignedGroup )
end
end
)
@ -530,8 +536,9 @@ do -- Group Assignment
--- UnAssign the @{Task} from a @{Group}.
-- @param #TASK self
-- @param Wrapper.Group#GROUP TaskGroup
function TASK:UnAssignFromGroup( TaskGroup )
self:F2( { TaskGroup } )
self:F2( { TaskGroup = TaskGroup:GetName() } )
self:ClearGroupAssignment( TaskGroup )
@ -539,7 +546,7 @@ do -- Group Assignment
for UnitID, UnitData in pairs( TaskUnits ) do
local TaskUnit = UnitData -- Wrapper.Unit#UNIT
local PlayerName = TaskUnit:GetPlayerName()
if PlayerName ~= nil or PlayerName ~= "" then
if PlayerName ~= nil and PlayerName ~= "" then -- Only remove units that have players!
self:UnAssignFromUnit( TaskUnit )
end
end
@ -573,7 +580,6 @@ function TASK:AssignToUnit( TaskUnit )
-- Assign a new FsmUnit to TaskUnit.
local FsmUnit = self:SetStateMachine( TaskUnit, FsmTemplate:Copy( TaskUnit, self ) ) -- Core.Fsm#FSM_PROCESS
self:E({"Address FsmUnit", tostring( FsmUnit ) } )
FsmUnit:SetStartState( "Planned" )
@ -738,13 +744,16 @@ function TASK:SetPlannedMenuForGroup( TaskGroup, MenuTime )
--local MissionMenu = Mission:GetMenu( TaskGroup )
local TaskPlannedMenu = MENU_GROUP:New( TaskGroup, "Planned Tasks", MissionMenu ):SetTime( MenuTime )
local TaskTypeMenu = MENU_GROUP:New( TaskGroup, TaskType, TaskPlannedMenu ):SetTime( MenuTime ):SetRemoveParent( true )
local TaskTypeMenu = MENU_GROUP:New( TaskGroup, TaskText, TaskTypeMenu ):SetTime( MenuTime ):SetRemoveParent( true )
local ReportTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Report Task Status" ), TaskTypeMenu, self.MenuTaskStatus, self, TaskGroup ):SetTime( MenuTime ):SetRemoveParent( true )
self.MenuPlanned = self.MenuPlanned or {}
self.MenuPlanned[TaskGroup] = MENU_GROUP:New( TaskGroup, "Join Planned Task", MissionMenu, Mission.MenuReportTasksPerStatus, Mission, TaskGroup, "Planned" ):SetTime( MenuTime ):SetTag( "Tasking" )
local TaskTypeMenu = MENU_GROUP:New( TaskGroup, TaskType, self.MenuPlanned[TaskGroup] ):SetTime( MenuTime ):SetTag( "Tasking" ):SetRemoveParent( true )
local TaskTypeMenu = MENU_GROUP:New( TaskGroup, TaskText, TaskTypeMenu ):SetTime( MenuTime ):SetTag( "Tasking" ):SetRemoveParent( true )
local ReportTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Report Task Status" ), TaskTypeMenu, self.MenuTaskStatus, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ):SetRemoveParent( true )
if not Mission:IsGroupAssigned( TaskGroup ) then
local JoinTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Join Task" ), TaskTypeMenu, self.MenuAssignToGroup, { self = self, TaskGroup = TaskGroup } ):SetTime( MenuTime ):SetRemoveParent( true )
self:F( { "Replacing Join Task menu" } )
local JoinTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Join Task" ), TaskTypeMenu, self.MenuAssignToGroup, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ):SetRemoveParent( true )
local MarkTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Mark Task on Map" ), TaskTypeMenu, self.MenuMarkToGroup, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ):SetRemoveParent( true )
end
return self
@ -775,9 +784,10 @@ function TASK:SetAssignedMenuForGroup( TaskGroup, MenuTime )
-- local MissionMenu = MENU_GROUP:New( TaskGroup, MissionName, CommandCenterMenu ):SetTime( MenuTime )
-- local MissionMenu = Mission:GetMenu( TaskGroup )
local TaskAssignedMenu = MENU_GROUP:New( TaskGroup, string.format( "Assigned Task %s", TaskName ), MissionMenu ):SetTime( MenuTime )
local TaskTypeMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Report Task Status" ), TaskAssignedMenu, self.MenuTaskStatus, self, TaskGroup ):SetTime( MenuTime ):SetRemoveParent( true )
local TaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Abort Group from Task" ), TaskAssignedMenu, self.MenuTaskAbort, self, TaskGroup ):SetTime( MenuTime ):SetRemoveParent( true )
self.MenuAssigned = self.MenuAssigned or {}
self.MenuAssigned[TaskGroup] = MENU_GROUP:New( TaskGroup, string.format( "Assigned Task %s", TaskName ), MissionMenu ):SetTime( MenuTime ):SetTag( "Tasking" )
local TaskTypeMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Report Task Status" ), self.MenuAssigned[TaskGroup], self.MenuTaskStatus, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ):SetRemoveParent( true )
local TaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Abort Group from Task" ), self.MenuAssigned[TaskGroup], self.MenuTaskAbort, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ):SetRemoveParent( true )
return self
end
@ -791,9 +801,7 @@ function TASK:RemoveMenu( MenuTime )
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
local TaskGroup = TaskGroup -- Wrapper.Group#GROUP
if TaskGroup:IsAlive() and TaskGroup:GetPlayerNames() then
self:RefreshMenus( TaskGroup, MenuTime )
end
self:RefreshMenus( TaskGroup, MenuTime )
end
end
@ -814,15 +822,18 @@ function TASK:RefreshMenus( TaskGroup, MenuTime )
local MissionMenu = Mission:GetMenu( TaskGroup )
local TaskName = self:GetName()
local PlannedMenu = MissionMenu:GetMenu( "Planned Tasks" )
local AssignedMenu = MissionMenu:GetMenu( string.format( "Assigned Task %s", TaskName ) )
self.MenuPlanned = self.MenuPlanned or {}
local PlannedMenu = self.MenuPlanned[TaskGroup]
self.MenuAssigned = self.MenuAssigned or {}
local AssignedMenu = self.MenuAssigned[TaskGroup]
if PlannedMenu then
PlannedMenu:Remove( MenuTime )
PlannedMenu:Remove( MenuTime , "Tasking")
end
if AssignedMenu then
AssignedMenu:Remove( MenuTime )
AssignedMenu:Remove( MenuTime, "Tasking" )
end
end
@ -846,16 +857,44 @@ function TASK:RemoveAssignedMenuForGroup( TaskGroup )
end
function TASK.MenuAssignToGroup( MenuParam )
--- @param #TASK self
-- @param Wrapper.Group#GROUP TaskGroup
function TASK:MenuAssignToGroup( TaskGroup )
local self = MenuParam.self
local TaskGroup = MenuParam.TaskGroup
self:E( "Assigned menu selected")
self:E( "Join Task menu selected")
self:AssignToGroup( TaskGroup )
end
--- @param #TASK self
-- @param Wrapper.Group#GROUP TaskGroup
function TASK:MenuMarkToGroup( TaskGroup )
self:E( "Mark Task menu selected")
self:UpdateTaskInfo()
local Report = REPORT:New():SetIndent( 0 )
-- List the name of the Task.
local Name = self:GetName()
Report:Add( "Task " .. Name .. ": " .. self:GetTaskBriefing() .. "\n" )
for TaskInfoID, TaskInfo in pairs( self.TaskInfo, function( t, a, b ) return t[a].TaskInfoOrder < t[b].TaskInfoOrder end ) do
local ReportText = self:GetMarkInfo( TaskInfoID, TaskInfo )
if ReportText then
Report:Add( ReportText )
end
end
local TargetCoordinate = self:GetInfo( "Coordinate" ) -- Core.Point#COORDINATE
local MarkText = Report:Text( ", " )
self:F( { Coordinate = TargetCoordinate, MarkText = MarkText } )
TargetCoordinate:MarkToGroup( MarkText, TaskGroup )
--Coordinate:MarkToAll( Briefing )
end
--- Report the task status.
-- @param #TASK self
function TASK:MenuTaskStatus( TaskGroup )
@ -863,7 +902,7 @@ function TASK:MenuTaskStatus( TaskGroup )
local ReportText = self:ReportDetails( TaskGroup )
self:T( ReportText )
self:GetMission():GetCommandCenter():MessageToGroup( ReportText, TaskGroup )
self:GetMission():GetCommandCenter():MessageTypeToGroup( ReportText, TaskGroup, MESSAGE.Type.Detailed )
end
@ -947,7 +986,7 @@ end
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return #TASK self
function TASK:RemoveStateMachine( TaskUnit )
self:F( { TaskUnit, self.Fsm[TaskUnit] ~= nil } )
self:F( { TaskUnit = TaskUnit:GetName(), HasFsm = ( self.Fsm[TaskUnit] ~= nil ) } )
--self:E( self.Fsm )
--for TaskUnitT, Fsm in pairs( self.Fsm ) do
@ -956,13 +995,16 @@ function TASK:RemoveStateMachine( TaskUnit )
--self.Fsm[TaskUnit] = nil
--end
self.Fsm[TaskUnit]:Remove()
self.Fsm[TaskUnit] = nil
if self.Fsm[TaskUnit] then
self.Fsm[TaskUnit]:Remove()
self.Fsm[TaskUnit] = nil
end
collectgarbage()
self:E( "Garbage Collected, Processes should be finalized now ...")
end
--- Checks if there is a FiniteStateMachine assigned to Task@{Unit} for @{Task}
-- @param #TASK self
-- @param Wrapper.Unit#UNIT TaskUnit
@ -1016,10 +1058,26 @@ end
--- Sets the Information on the Task
-- @param #TASK self
-- @param #string TaskInfo
function TASK:SetInfo( TaskInfo, TaskInfoText )
-- @param #string TaskInfo The key and title of the task information.
-- @param #string TaskInfoText The Task info text.
-- @param #number TaskInfoOrder The ordering, a number between 0 and 99.
function TASK:SetInfo( TaskInfo, TaskInfoText, TaskInfoOrder )
self.TaskInfo[TaskInfo] = TaskInfoText
self.TaskInfo = self.TaskInfo or {}
self.TaskInfo[TaskInfo] = self.TaskInfo[TaskInfo] or {}
self.TaskInfo[TaskInfo].TaskInfoText = TaskInfoText
self.TaskInfo[TaskInfo].TaskInfoOrder = TaskInfoOrder
end
--- Gets the Information of the Task
-- @param #TASK self
-- @param #string TaskInfo The key and title of the task information.
-- @return #string TaskInfoText The Task info text.
function TASK:GetInfo( TaskInfo )
self.TaskInfo = self.TaskInfo or {}
self.TaskInfo[TaskInfo] = self.TaskInfo[TaskInfo] or {}
return self.TaskInfo[TaskInfo].TaskInfoText
end
--- Gets the Type of the Task
@ -1181,9 +1239,11 @@ end
-- @param #string To
function TASK:onenterAssigned( From, Event, To, PlayerUnit, PlayerName )
self:E( { "Task Assigned", self.Dispatcher } )
--- This test is required, because the state transition will be fired also when the state does not change in case of an event.
if From ~= "Assigned" then
self:E( { From, Event, To, PlayerUnit:GetName(), PlayerName } )
self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " is assigned." )
-- Set the total Progress to be achieved.
@ -1198,7 +1258,7 @@ function TASK:onenterAssigned( From, Event, To, PlayerUnit, PlayerName )
self:GetMission():__Start( 1 )
-- When the task is assigned, the task goal needs to be checked of the derived classes.
self:__Goal( -10 ) -- Polymorphic
self:__Goal( -10, PlayerUnit, PlayerName ) -- Polymorphic
self:SetMenu()
end
@ -1239,6 +1299,23 @@ function TASK:onenterAborted( From, Event, To )
end
--- FSM function for a TASK
-- @param #TASK self
-- @param #string From
-- @param #string Event
-- @param #string To
function TASK:onenterCancelled( From, Event, To )
self:E( "Task Cancelled" )
if From ~= "Cancelled" then
self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " has been cancelled! The tactical situation has changed." )
self:UnAssignFromGroups()
self:SetMenu()
end
end
--- FSM function for a TASK
-- @param #TASK self
-- @param #string From
@ -1313,7 +1390,7 @@ function TASK:onbeforeTimeOut( From, Event, To )
return false
end
do -- Dispatcher
do -- Links
--- Set dispatcher of a task
-- @param #TASK self
@ -1323,6 +1400,19 @@ do -- Dispatcher
self.Dispatcher = Dispatcher
end
--- Set detection of a task
-- @param #TASK self
-- @param Function.Detection#DETECTION_BASE Detection
-- @param #number DetectedItemIndex
-- @return #TASK
function TASK:SetDetection( Detection, DetectedItemIndex )
self:E({DetectedItemIndex,Detection})
self.Detection = Detection
self.DetectedItemIndex = DetectedItemIndex
end
end
do -- Reporting
@ -1330,55 +1420,74 @@ do -- Reporting
--- Create a summary report of the Task.
-- List the Task Name and Status
-- @param #TASK self
-- @param Wrapper.Group#GROUP ReportGroup
-- @return #string
function TASK:ReportSummary() --R2.1 fixed report. Now nicely formatted and contains the info required.
function TASK:ReportSummary( ReportGroup )
local Report = REPORT:New()
-- List the name of the Task.
local Name = self:GetName()
Report:Add( "Task " .. self:GetName() )
-- Determine the status of the Task.
local Status = "<" .. self:GetState() .. ">"
Report:Add( "State: <" .. self:GetState() .. ">" )
Report:Add( 'Task ' .. Name .. ' - State ' .. Status )
return Report:Text()
if self.TaskInfo["Coordinate"] then
local TaskInfoIDText = string.format( "%s: ", "Coordinate" )
local TaskCoord = self.TaskInfo["Coordinate"].TaskInfoText -- Core.Point#COORDINATE
Report:Add( TaskInfoIDText .. TaskCoord:ToString( ReportGroup, nil, self ) )
end
return Report:Text( ', ' )
end
--- Create an overiew report of the Task.
-- List the Task Name and Status
-- @param #TASK self
-- @return #string
function TASK:ReportOverview( ReportGroup ) --R2.1 fixed report. Now nicely formatted and contains the info required.
function TASK:ReportOverview( ReportGroup )
self:UpdateTaskInfo()
-- List the name of the Task.
local Name = self:GetName()
local Report = REPORT:New( Name )
local TaskName = self:GetName()
local Report = REPORT:New()
local Line = 0
local LineReport = REPORT:New()
-- Determine the status of the Task.
local Status = "<" .. self:GetState() .. ">"
for TaskInfoID, TaskInfo in pairs( self.TaskInfo ) do
for TaskInfoID, TaskInfo in UTILS.spairs( self.TaskInfo, function( t, a, b ) return t[a].TaskInfoOrder < t[b].TaskInfoOrder end ) do
self:F( { TaskInfo = TaskInfo } )
if Line < math.floor( TaskInfo.TaskInfoOrder / 10 ) then
if Line ~= 0 then
Report:AddIndent( LineReport:Text( ", " ) )
else
Report:Add( "Task " .. TaskName .. ", " .. LineReport:Text( ", " ) )
end
LineReport = REPORT:New()
Line = math.floor( TaskInfo.TaskInfoOrder / 10 )
end
local TaskInfoIDText = string.format( "%s: ", TaskInfoID )
if type(TaskInfo) == "string" then
Report:Add( TaskInfoIDText .. TaskInfo )
if type( TaskInfo.TaskInfoText ) == "string" then
LineReport:Add( TaskInfoIDText .. TaskInfo.TaskInfoText )
elseif type(TaskInfo) == "table" then
if TaskInfoID == "Coordinates" then
local FromCoordinate = ReportGroup:GetUnit(1):GetCoordinate()
local ToCoordinate = TaskInfo -- Core.Point#COORDINATE
if TaskInfoID == "Coordinate" then
local ToCoordinate = TaskInfo.TaskInfoText -- Core.Point#COORDINATE
--Report:Add( TaskInfoIDText )
Report:Add( ToCoordinate:ToString( ReportGroup ) )
LineReport:Add( TaskInfoIDText .. ToCoordinate:ToString( ReportGroup, nil, self ) )
--Report:AddIndent( ToCoordinate:ToStringBULLS( ReportGroup:GetCoalition() ) )
else
end
end
end
Report:AddIndent( LineReport:Text( ", " ) )
return Report:Text( ", ")
return Report:Text()
end
--- Create a count of the players in the Task.
@ -1430,6 +1539,8 @@ end
-- @return #string
function TASK:ReportDetails( ReportGroup )
self:UpdateTaskInfo()
local Report = REPORT:New():SetIndent( 3 )
-- List the name of the Task.
@ -1438,6 +1549,8 @@ function TASK:ReportDetails( ReportGroup )
-- Determine the status of the Task.
local Status = "<" .. self:GetState() .. ">"
Report:Add( "Task " .. Name .. " - " .. Status .. " - Detailed Report" )
-- Loop each Unit active in the Task, and find Player Names.
local PlayerNames = self:GetPlayerNames()
@ -1446,30 +1559,21 @@ function TASK:ReportDetails( ReportGroup )
PlayerReport:Add( "Group " .. PlayerGroup:GetCallsign() .. ": " .. PlayerName )
end
local Players = PlayerReport:Text()
Report:Add( "Task: " .. Name .. " - " .. Status .. " - Detailed Report" )
Report:Add( " - Players:" )
Report:AddIndent( Players )
for TaskInfoID, TaskInfo in pairs( self.TaskInfo ) do
if Players ~= "" then
Report:Add( " - Players assigned:" )
Report:AddIndent( Players )
end
for TaskInfoID, TaskInfo in pairs( self.TaskInfo, function( t, a, b ) return t[a].TaskInfoOrder < t[b].TaskInfoOrder end ) do
local TaskInfoIDText = string.format( " - %s: ", TaskInfoID )
if type(TaskInfo) == "string" then
Report:Add( TaskInfoIDText .. TaskInfo )
elseif type(TaskInfo) == "table" then
if TaskInfoID == "Coordinates" then
local FromCoordinate = ReportGroup:GetUnit(1):GetCoordinate()
local ToCoordinate = TaskInfo -- Core.Point#COORDINATE
Report:Add( TaskInfoIDText )
Report:AddIndent( ToCoordinate:ToStringBRA( FromCoordinate ) .. ", " .. TaskInfo:ToStringAspect( FromCoordinate ) )
Report:AddIndent( ToCoordinate:ToStringBULLS( ReportGroup:GetCoalition() ) )
else
end
local ReportText = self:GetReportDetail( ReportGroup, TaskInfoID, TaskInfo )
if ReportText then
Report:Add( ReportText )
end
end
return Report:Text()
end

View File

@ -0,0 +1,287 @@
--- **Tasking** - The TASK_Protect models tasks for players to protect or capture specific zones.
--
-- ====
--
-- ### Author: **Sven Van de Velde (FlightControl)**
--
-- ### Contributions: MillerTime
--
-- ====
--
-- @module TaskZoneCapture
do -- TASK_ZONE_GOAL
--- The TASK_ZONE_GOAL class
-- @type TASK_ZONE_GOAL
-- @field Core.ZoneGoal#ZONE_GOAL ZoneGoal
-- @extends Tasking.Task#TASK
--- # TASK_ZONE_GOAL class, extends @{Task#TASK}
--
-- The TASK_ZONE_GOAL class defines the task to protect or capture a protection zone.
-- The TASK_ZONE_GOAL is implemented using a @{Fsm#FSM_TASK}, and has the following statuses:
--
-- * **None**: Start of the process
-- * **Planned**: The A2G task is planned.
-- * **Assigned**: The A2G task is assigned to a @{Group#GROUP}.
-- * **Success**: The A2G task is successfully completed.
-- * **Failed**: The A2G task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ.
--
-- ## Set the scoring of achievements in an A2G attack.
--
-- Scoring or penalties can be given in the following circumstances:
--
-- * @{#TASK_ZONE_GOAL.SetScoreOnDestroy}(): Set a score when a target in scope of the A2G attack, has been destroyed.
-- * @{#TASK_ZONE_GOAL.SetScoreOnSuccess}(): Set a score when all the targets in scope of the A2G attack, have been destroyed.
-- * @{#TASK_ZONE_GOAL.SetPenaltyOnFailed}(): Set a penalty when the A2G attack has failed.
--
-- @field #TASK_ZONE_GOAL
TASK_ZONE_GOAL = {
ClassName = "TASK_ZONE_GOAL",
}
--- Instantiates a new TASK_ZONE_GOAL.
-- @param #TASK_ZONE_GOAL self
-- @param Tasking.Mission#MISSION Mission
-- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned.
-- @param #string TaskName The name of the Task.
-- @param Core.ZoneGoal#ZONE_GOAL ZoneGoal
-- @return #TASK_ZONE_GOAL self
function TASK_ZONE_GOAL:New( Mission, SetGroup, TaskName, ZoneGoal, TaskType, TaskBriefing )
local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType, TaskBriefing ) ) -- #TASK_ZONE_GOAL
self:F()
self.ZoneGoal = ZoneGoal
self.TaskType = TaskType
local Fsm = self:GetUnitProcess()
Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "StartMonitoring", Rejected = "Reject" } )
Fsm:AddTransition( "Assigned", "StartMonitoring", "Monitoring" )
Fsm:AddTransition( "Monitoring", "Monitor", "Monitoring", {} )
Fsm:AddTransition( "Monitoring", "RouteTo", "Monitoring" )
Fsm:AddProcess( "Monitoring", "RouteToZone", ACT_ROUTE_ZONE:New(), {} )
--Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" )
--Fsm:AddTransition( "Accounted", "Success", "Success" )
Fsm:AddTransition( "Rejected", "Reject", "Aborted" )
Fsm:AddTransition( "Failed", "Fail", "Failed" )
self:SetTargetZone( self.ZoneGoal:GetZone() )
--- Test
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task#TASK_ZONE_GOAL Task
function Fsm:onafterStartMonitoring( TaskUnit, Task )
self:E( { self } )
self:__Monitor( 0.1 )
self:__RouteTo( 0.1 )
end
--- Monitor Loop
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task#TASK_ZONE_GOAL Task
function Fsm:onafterMonitor( TaskUnit, Task )
self:E( { self } )
self:__Monitor( 15 )
end
--- Test
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_A2G#TASK_ZONE_GOAL Task
function Fsm:onafterRouteTo( TaskUnit, Task )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
-- Determine the first Unit from the self.TargetSetUnit
if Task:GetTargetZone( TaskUnit ) then
self:__RouteTo( 0.1 )
end
end
return self
end
--- @param #TASK_ZONE_GOAL self
-- @param Core.ZoneGoal#ZONE_GOAL ZoneGoal The ZoneGoal Engine.
function TASK_ZONE_GOAL:SetProtect( ZoneGoal )
self.ZoneGoal = ZoneGoal -- Core.ZoneGoal#ZONE_GOAL
end
--- @param #TASK_ZONE_GOAL self
function TASK_ZONE_GOAL:GetPlannedMenuText()
return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.ZoneGoal:GetZoneName() .. " )"
end
--- @param #TASK_ZONE_GOAL self
-- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map.
-- @param Wrapper.Unit#UNIT TaskUnit
function TASK_ZONE_GOAL:SetTargetZone( TargetZone, TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteZone = ProcessUnit:GetProcess( "Monitoring", "RouteToZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
ActRouteZone:SetZone( TargetZone )
end
--- @param #TASK_ZONE_GOAL self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return Core.Zone#ZONE_BASE The Zone object where the Target is located on the map.
function TASK_ZONE_GOAL:GetTargetZone( TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteZone = ProcessUnit:GetProcess( "Monitoring", "RouteToZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
return ActRouteZone:GetZone()
end
function TASK_ZONE_GOAL:SetGoalTotal( GoalTotal )
self.GoalTotal = GoalTotal
end
function TASK_ZONE_GOAL:GetGoalTotal()
return self.GoalTotal
end
function TASK_ZONE_GOAL:GetMarkInfo( TaskInfoID, TaskInfo )
if type( TaskInfo.TaskInfoText ) == "string" then
return string.format( "%s: %s", TaskInfoID, TaskInfo.TaskInfoText )
elseif type( TaskInfo ) == "table" then
if TaskInfoID == "Coordinate" then
end
end
return nil
end
function TASK_ZONE_GOAL:GetReportDetail( ReportGroup, TaskInfoID, TaskInfo )
if type( TaskInfo.TaskInfoText ) == "string" then
return string.format( " - %s: %s", TaskInfoID, TaskInfo.TaskInfoText )
elseif type(TaskInfo) == "table" then
if TaskInfoID == "Coordinate" then
local FromCoordinate = ReportGroup:GetUnit(1):GetCoordinate()
local ToCoordinate = TaskInfo.TaskInfoText -- Core.Point#COORDINATE
return string.format( " - %s: %s", TaskInfoID, ToCoordinate:ToString( ReportGroup:GetUnit( 1 ), nil, self ) )
else
end
end
end
end
do -- TASK_ZONE_CAPTURE
--- The TASK_ZONE_CAPTURE class
-- @type TASK_ZONE_CAPTURE
-- @field Core.ZoneGoalCoalition#ZONE_GOAL_COALITION ZoneGoal
-- @extends #TASK_ZONE_GOAL
--- # TASK_ZONE_CAPTURE class, extends @{TaskZoneGoal#TASK_ZONE_GOAL}
--
-- The TASK_ZONE_CAPTURE class defines an Suppression or Extermination of Air Defenses task for a human player to be executed.
-- These tasks are important to be executed as they will help to achieve air superiority at the vicinity.
--
-- The TASK_ZONE_CAPTURE is used by the @{Task_A2G_Dispatcher#TASK_A2G_DISPATCHER} to automatically create SEAD tasks
-- based on detected enemy ground targets.
--
-- @field #TASK_ZONE_CAPTURE
TASK_ZONE_CAPTURE = {
ClassName = "TASK_ZONE_CAPTURE",
}
--- Instantiates a new TASK_ZONE_CAPTURE.
-- @param #TASK_ZONE_CAPTURE self
-- @param Tasking.Mission#MISSION Mission
-- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned.
-- @param #string TaskName The name of the Task.
-- @param Core.ZoneGoalCoalition#ZONE_GOAL_COALITION ZoneGoalCoalition
-- @param #string TaskBriefing The briefing of the task.
-- @return #TASK_ZONE_CAPTURE self
function TASK_ZONE_CAPTURE:New( Mission, SetGroup, TaskName, ZoneGoalCoalition, TaskBriefing)
local self = BASE:Inherit( self, TASK_ZONE_GOAL:New( Mission, SetGroup, TaskName, ZoneGoalCoalition, "CAPTURE", TaskBriefing ) ) -- #TASK_ZONE_CAPTURE
self:F()
Mission:AddTask( self )
self.TaskCoalition = ZoneGoalCoalition:GetCoalition()
self.TaskCoalitionName = ZoneGoalCoalition:GetCoalitionName()
self.TaskZoneName = ZoneGoalCoalition:GetZoneName()
ZoneGoalCoalition:MonitorDestroyedUnits()
self:SetBriefing(
TaskBriefing or
"Capture Zone " .. self.TaskZoneName
)
self:UpdateTaskInfo()
return self
end
--- Instantiates a new TASK_ZONE_CAPTURE.
-- @param #TASK_ZONE_CAPTURE self
function TASK_ZONE_CAPTURE:UpdateTaskInfo()
local ZoneCoordinate = self.ZoneGoal:GetZone():GetCoordinate()
self:SetInfo( "Coordinate", ZoneCoordinate, 0 )
self:SetInfo( "Zone Name", self.ZoneGoal:GetZoneName(), 10 )
self:SetInfo( "Zone Coalition", self.ZoneGoal:GetCoalitionName(), 11 )
end
function TASK_ZONE_CAPTURE:ReportOrder( ReportGroup )
local Coordinate = self:GetInfo( "Coordinate" )
--local Coordinate = self.TaskInfo.Coordinates.TaskInfoText
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
return Distance
end
--- @param #TASK_ZONE_CAPTURE self
-- @param Wrapper.Unit#UNIT TaskUnit
function TASK_ZONE_CAPTURE:OnAfterGoal( From, Event, To, PlayerUnit, PlayerName )
self:E( { PlayerUnit = PlayerUnit } )
if self.ZoneGoal then
if self.ZoneGoal.Goal:IsAchieved() then
self:Success()
local TotalContributions = self.ZoneGoal.Goal:GetTotalContributions()
local PlayerContributions = self.ZoneGoal.Goal:GetPlayerContributions()
self:E( { TotalContributions = TotalContributions, PlayerContributions = PlayerContributions } )
for PlayerName, PlayerContribution in pairs( PlayerContributions ) do
local Scoring = self:GetScoring()
if Scoring then
Scoring:_AddMissionGoalScore( self.Mission, PlayerName, "Zone " .. self.ZoneGoal:GetZoneName() .." captured", PlayerContribution * 200 / TotalContributions )
end
end
end
end
self:__Goal( -10, PlayerUnit, PlayerName )
end
end

View File

@ -75,7 +75,7 @@ do -- TASK_A2A
Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "Engage", "Engaging" )
Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "HoldAtRendezVous", "HoldingAtRendezVous" )
Fsm:AddProcess ( "Engaging", "Account", ACT_ACCOUNT_DEADS:New( self.TargetSetUnit, self.TaskType ), {} )
Fsm:AddProcess ( "Engaging", "Account", ACT_ACCOUNT_DEADS:New(), {} )
Fsm:AddTransition( "Engaging", "RouteToTarget", "Engaging" )
Fsm:AddProcess( "Engaging", "RouteToTargetZone", ACT_ROUTE_ZONE:New(), {} )
Fsm:AddProcess( "Engaging", "RouteToTargetPoint", ACT_ROUTE_POINT:New(), {} )
@ -277,6 +277,35 @@ do -- TASK_A2A
return self.GoalTotal
end
function TASK_A2A:GetMarkInfo( TaskInfoID, TaskInfo )
if type( TaskInfo.TaskInfoText ) == "string" then
if TaskInfoID == "Targets" then
else
return string.format( "%s: %s", TaskInfoID, TaskInfo.TaskInfoText )
end
elseif type( TaskInfo ) == "table" then
if TaskInfoID == "Coordinate" then
end
end
return nil
end
function TASK_A2A:GetReportDetail( ReportGroup, TaskInfoID, TaskInfo )
if type( TaskInfo.TaskInfoText ) == "string" then
return string.format( "%s: %s", TaskInfoID, TaskInfo.TaskInfoText )
elseif type(TaskInfo) == "table" then
if TaskInfoID == "Coordinate" then
local FromCoordinate = ReportGroup:GetUnit(1):GetCoordinate()
local ToCoordinate = TaskInfo.TaskInfoText -- Core.Point#COORDINATE
return string.format( " - %s: %s", TaskInfoID, ToCoordinate:ToString( ReportGroup:GetUnit(1), nil, self ) )
else
end
end
end
end
@ -328,16 +357,48 @@ do -- TASK_A2A_INTERCEPT
"Intercept incoming intruders.\n"
)
local TargetCoordinate = TargetSetUnit:GetFirst():GetCoordinate()
self:SetInfo( "Coordinates", TargetCoordinate )
self:SetInfo( "Threat", "[" .. string.rep( "", TargetSetUnit:CalculateThreatLevelA2G() ) .. "]" )
local DetectedItemsCount = TargetSetUnit:Count()
local DetectedItemsTypes = TargetSetUnit:GetTypeNames()
self:SetInfo( "Targets", string.format( "%d of %s", DetectedItemsCount, DetectedItemsTypes ) )
self:UpdateTaskInfo()
return self
end
end
function TASK_A2A_INTERCEPT:UpdateTaskInfo()
local TargetCoordinate = self.Detection and self.Detection:GetDetectedItemCoordinate( self.DetectedItemIndex ) or self.TargetSetUnit:GetFirst():GetCoordinate()
self:SetInfo( "Coordinate", TargetCoordinate, 0 )
self:SetInfo( "Threat", "[" .. string.rep( "", self.Detection and self.Detection:GetDetectedItemThreatLevel( self.DetectedItemIndex ) or self.TargetSetUnit:CalculateThreatLevelA2G() ) .. "]", 11 )
if self.Detection then
local DetectedItemsCount = self.TargetSetUnit:Count()
local ReportTypes = REPORT:New()
local TargetTypes = {}
for TargetUnitName, TargetUnit in pairs( self.TargetSetUnit:GetSet() ) do
local TargetType = self.Detection:GetDetectedUnitTypeName( TargetUnit )
if not TargetTypes[TargetType] then
TargetTypes[TargetType] = TargetType
ReportTypes:Add( TargetType )
end
end
self:SetInfo( "Targets", string.format( "%d of %s", DetectedItemsCount, ReportTypes:Text( ", " ) ), 10 )
else
local DetectedItemsCount = self.TargetSetUnit:Count()
local DetectedItemsTypes = self.TargetSetUnit:GetTypeNames()
self:SetInfo( "Targets", string.format( "%d of %s", DetectedItemsCount, DetectedItemsTypes ), 10 )
end
end
--- @param #TASK_A2A_INTERCEPT self
-- @param Wrapper.Group#GROUP ReportGroup
function TASK_A2A_INTERCEPT:ReportOrder( ReportGroup )
self:F( { TaskInfo = self.TaskInfo } )
local Coordinate = self.TaskInfo.Coordinates.TaskInfoText
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
return Distance
end
--- @param #TASK_A2A_INTERCEPT self
@ -439,7 +500,7 @@ do -- TASK_A2A_SWEEP
-- @param #string TaskBriefing The briefing of the task.
-- @return #TASK_A2A_SWEEP self
function TASK_A2A_SWEEP:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing )
local self = BASE:Inherit( self, TASK_A2A:New( Mission, SetGroup, TaskName, TargetSetUnit, "INTERCEPT", TaskBriefing ) ) -- #TASK_A2A_SWEEP
local self = BASE:Inherit( self, TASK_A2A:New( Mission, SetGroup, TaskName, TargetSetUnit, "SWEEP", TaskBriefing ) ) -- #TASK_A2A_SWEEP
self:F()
Mission:AddTask( self )
@ -451,17 +512,47 @@ do -- TASK_A2A_SWEEP
"Perform a fighter sweep. Incoming intruders were detected and could be hiding at the location.\n"
)
local TargetCoordinate = TargetSetUnit:GetFirst():GetCoordinate()
self:SetInfo( "Coordinates", TargetCoordinate )
self:SetInfo( "Assumed Threat", "[" .. string.rep( "", TargetSetUnit:CalculateThreatLevelA2G() ) .. "]" )
local DetectedItemsCount = TargetSetUnit:Count()
local DetectedItemsTypes = TargetSetUnit:GetTypeNames()
self:SetInfo( "Lost Targets", string.format( "%d of %s", DetectedItemsCount, DetectedItemsTypes ) )
self:UpdateTaskInfo()
return self
end
function TASK_A2A_SWEEP:UpdateTaskInfo()
local TargetCoordinate = self.Detection and self.Detection:GetDetectedItemCoordinate( self.DetectedItemIndex ) or self.TargetSetUnit:GetFirst():GetCoordinate()
self:SetInfo( "Coordinate", TargetCoordinate, 0 )
self:SetInfo( "Assumed Threat", "[" .. string.rep( "", self.Detection and self.Detection:GetDetectedItemThreatLevel( self.DetectedItemIndex ) or self.TargetSetUnit:CalculateThreatLevelA2G() ) .. "]", 11 )
if self.Detection then
local DetectedItemsCount = self.TargetSetUnit:Count()
local ReportTypes = REPORT:New()
local TargetTypes = {}
for TargetUnitName, TargetUnit in pairs( self.TargetSetUnit:GetSet() ) do
local TargetType = self.Detection:GetDetectedUnitTypeName( TargetUnit )
if not TargetTypes[TargetType] then
TargetTypes[TargetType] = TargetType
ReportTypes:Add( TargetType )
end
end
self:SetInfo( "Lost Targets", string.format( "%d of %s", DetectedItemsCount, ReportTypes:Text( ", " ) ), 10 )
else
local DetectedItemsCount = self.TargetSetUnit:Count()
local DetectedItemsTypes = self.TargetSetUnit:GetTypeNames()
self:SetInfo( "Lost Targets", string.format( "%d of %s", DetectedItemsCount, DetectedItemsTypes ), 10 )
end
end
function TASK_A2A_SWEEP:ReportOrder( ReportGroup )
local Coordinate = self.TaskInfo.Coordinates.TaskInfoText
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
return Distance
end
--- @param #TASK_A2A_SWEEP self
function TASK_A2A_SWEEP:onafterGoal( TaskUnit, From, Event, To )
local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT
@ -570,17 +661,46 @@ do -- TASK_A2A_ENGAGE
"Bogeys are nearby! Players close by are ordered to ENGAGE the intruders!\n"
)
local TargetCoordinate = TargetSetUnit:GetFirst():GetCoordinate()
self:SetInfo( "Coordinates", TargetCoordinate )
self:SetInfo( "Threat", "[" .. string.rep( "", TargetSetUnit:CalculateThreatLevelA2G() ) .. "]" )
local DetectedItemsCount = TargetSetUnit:Count()
local DetectedItemsTypes = TargetSetUnit:GetTypeNames()
self:SetInfo( "Targets", string.format( "%d of %s", DetectedItemsCount, DetectedItemsTypes ) )
self:UpdateTaskInfo()
return self
end
function TASK_A2A_ENGAGE:UpdateTaskInfo()
local TargetCoordinate = self.Detection and self.Detection:GetDetectedItemCoordinate( self.DetectedItemIndex ) or self.TargetSetUnit:GetFirst():GetCoordinate()
self:SetInfo( "Coordinate", TargetCoordinate, 0 )
self:SetInfo( "Threat", "[" .. string.rep( "", self.Detection and self.Detection:GetDetectedItemThreatLevel( self.DetectedItemIndex ) or self.TargetSetUnit:CalculateThreatLevelA2G() ) .. "]", 11 )
if self.Detection then
local DetectedItemsCount = self.TargetSetUnit:Count()
local ReportTypes = REPORT:New()
local TargetTypes = {}
for TargetUnitName, TargetUnit in pairs( self.TargetSetUnit:GetSet() ) do
local TargetType = self.Detection:GetDetectedUnitTypeName( TargetUnit )
if not TargetTypes[TargetType] then
TargetTypes[TargetType] = TargetType
ReportTypes:Add( TargetType )
end
end
self:SetInfo( "Targets", string.format( "%d of %s", DetectedItemsCount, ReportTypes:Text( ", " ) ), 10 )
else
local DetectedItemsCount = self.TargetSetUnit:Count()
local DetectedItemsTypes = self.TargetSetUnit:GetTypeNames()
self:SetInfo( "Targets", string.format( "%d of %s", DetectedItemsCount, DetectedItemsTypes ), 10 )
end
end
function TASK_A2A_ENGAGE:ReportOrder( ReportGroup )
local Coordinate = self.TaskInfo.Coordinates.TaskInfoText
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
return Distance
end
--- @param #TASK_A2A_ENGAGE self
function TASK_A2A_ENGAGE:onafterGoal( TaskUnit, From, Event, To )
local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT

View File

@ -93,7 +93,7 @@ do -- TASK_A2A_DISPATCHER
--
-- local EWRDetection = DETECTION_AREAS:New( EWRSet, 6000 )
-- EWRDetection:SetFriendliesRange( 10000 )
-- EWRDetection:SetDetectionInterval(30)
-- EWRDetection:SetRefreshTimeInterval(30)
--
-- -- Setup the A2A dispatcher, and initialize it.
-- A2ADispatcher = TASK_A2A_DISPATCHER:New( Mission, AttackGroups, EWRDetection )
@ -197,7 +197,7 @@ do -- TASK_A2A_DISPATCHER
-- TODO: Check detection through radar.
self.Detection:FilterCategories( Unit.Category.AIRPLANE, Unit.Category.HELICOPTER )
self.Detection:InitDetectRadar( true )
self.Detection:SetDetectionInterval( 30 )
self.Detection:SetRefreshTimeInterval( 30 )
self:AddTransition( "Started", "Assign", "Started" )
@ -382,9 +382,7 @@ do -- TASK_A2A_DISPATCHER
end
if DetectedItemChanged == true or Remove then
--self:E( "Removing Tasking: " .. Task:GetTaskName() )
Mission:RemoveTask( Task )
self.Tasks[DetectedItemIndex] = nil
Task = self:RemoveTask( DetectedItemIndex )
end
end
end
@ -482,6 +480,11 @@ do -- TASK_A2A_DISPATCHER
return PlayersCount, PlayerTypesReport
end
function TASK_A2A_DISPATCHER:RemoveTask( TaskIndex )
self.Mission:RemoveTask( self.Tasks[TaskIndex] )
self.Tasks[TaskIndex] = nil
end
--- Assigns tasks in relation to the detected items to the @{Set#SET_GROUP}.
-- @param #TASK_A2A_DISPATCHER self
@ -510,8 +513,7 @@ do -- TASK_A2A_DISPATCHER
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
Mission:GetCommandCenter():MessageToGroup( string.format( "Obsolete A2A task %s for %s removed.", TaskText, Mission:GetName() ), TaskGroup )
end
Mission:RemoveTask( Task )
self.Tasks[TaskIndex] = nil
Task = self:RemoveTask( TaskIndex )
end
end
end
@ -538,21 +540,24 @@ do -- TASK_A2A_DISPATCHER
local TargetSetUnit = self:EvaluateENGAGE( DetectedItem ) -- Returns a SetUnit if there are targets to be INTERCEPTed...
if TargetSetUnit then
Task = TASK_A2A_ENGAGE:New( Mission, self.SetGroup, string.format( "ENGAGE.%03d", DetectedID ), TargetSetUnit )
Task:SetDetection( Detection, TaskIndex )
else
local TargetSetUnit = self:EvaluateINTERCEPT( DetectedItem ) -- Returns a SetUnit if there are targets to be INTERCEPTed...
if TargetSetUnit then
Task = TASK_A2A_INTERCEPT:New( Mission, self.SetGroup, string.format( "INTERCEPT.%03d", DetectedID ), TargetSetUnit )
Task:SetDetection( Detection, TaskIndex )
else
local TargetSetUnit = self:EvaluateSWEEP( DetectedItem ) -- Returns a SetUnit
if TargetSetUnit then
Task = TASK_A2A_SWEEP:New( Mission, self.SetGroup, string.format( "SWEEP.%03d", DetectedID ), TargetSetUnit )
Task:SetDetection( Detection, TaskIndex )
end
end
end
if Task then
self.Tasks[TaskIndex] = Task
Task:SetTargetZone( DetectedZone, DetectedSet:GetFirst():GetAltitude(), DetectedSet:GetFirst():GetHeading() )
Task:SetTargetZone( DetectedZone, DetectedItem.Coordinate.y, DetectedItem.Coordinate.Heading )
Task:SetDispatcher( self )
Mission:AddTask( Task )
@ -565,9 +570,9 @@ do -- TASK_A2A_DISPATCHER
if Task then
local FriendliesCount, FriendliesReport = self:GetFriendliesNearBy( DetectedItem )
Task:SetInfo( "Friendlies", string.format( "%d ( %s )", FriendliesCount, FriendliesReport:Text( "," ) ) )
Task:SetInfo( "Friendlies", string.format( "%d ( %s )", FriendliesCount, FriendliesReport:Text( "," ) ), 30 )
local PlayersCount, PlayersReport = self:GetPlayerFriendliesNearBy( DetectedItem )
Task:SetInfo( "Players", string.format( "%d ( %s )", PlayersCount, PlayersReport:Text( "," ) ) )
Task:SetInfo( "Players", string.format( "%d ( %s )", PlayersCount, PlayersReport:Text( "," ) ), 31 )
end
-- OK, so the tasking has been done, now delete the changes reported for the area.

View File

@ -75,7 +75,7 @@ do -- TASK_A2G
Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "Engage", "Engaging" )
Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "HoldAtRendezVous", "HoldingAtRendezVous" )
Fsm:AddProcess ( "Engaging", "Account", ACT_ACCOUNT_DEADS:New( self.TargetSetUnit, self.TaskType ), {} )
Fsm:AddProcess ( "Engaging", "Account", ACT_ACCOUNT_DEADS:New(), {} )
Fsm:AddTransition( "Engaging", "RouteToTarget", "Engaging" )
Fsm:AddProcess( "Engaging", "RouteToTargetZone", ACT_ROUTE_ZONE:New(), {} )
Fsm:AddProcess( "Engaging", "RouteToTargetPoint", ACT_ROUTE_POINT:New(), {} )
@ -141,9 +141,9 @@ do -- TASK_A2G
else
local TargetUnit = Task.TargetSetUnit:GetFirst() -- Wrapper.Unit#UNIT
if TargetUnit then
local Coordinate = TargetUnit:GetCoordinate()
local Coordinate = TargetUnit:GetPointVec3()
self:T( { TargetCoordinate = Coordinate, Coordinate:GetX(), Coordinate:GetY(), Coordinate:GetZ() } )
Task:SetTargetCoordinate( TargetUnit:GetCoordinate(), TaskUnit )
Task:SetTargetCoordinate( Coordinate, TaskUnit )
end
self:__RouteToTargetPoint( 0.1 )
end
@ -165,6 +165,15 @@ do -- TASK_A2G
return self
end
--- @param #TASK_A2G self
-- @param Core.Set#SET_UNIT TargetSetUnit The set of targets.
function TASK_A2G:SetTargetSetUnit( TargetSetUnit )
self.TargetSetUnit = TargetSetUnit
end
--- @param #TASK_A2G self
function TASK_A2G:GetPlannedMenuText()
@ -276,6 +285,36 @@ do -- TASK_A2G
return self.GoalTotal
end
function TASK_A2G:GetMarkInfo( TaskInfoID, TaskInfo )
if type( TaskInfo.TaskInfoText ) == "string" then
if TaskInfoID == "Targets" then
else
return string.format( "%s: %s", TaskInfoID, TaskInfo.TaskInfoText )
end
elseif type( TaskInfo ) == "table" then
if TaskInfoID == "Coordinate" then
end
end
return nil
end
function TASK_A2G:GetReportDetail( ReportGroup, TaskInfoID, TaskInfo )
if type( TaskInfo.TaskInfoText ) == "string" then
return string.format( "%s: %s", TaskInfoID, TaskInfo.TaskInfoText )
elseif type(TaskInfo) == "table" then
if TaskInfoID == "Coordinate" then
local FromCoordinate = ReportGroup:GetUnit(1):GetCoordinate()
local ToCoordinate = TaskInfo.TaskInfoText -- Core.Point#COORDINATE
return string.format( " - %s: %s", TaskInfoID, ToCoordinate:ToString( ReportGroup:GetUnit(1), nil, self ) )
else
end
end
end
end
@ -308,7 +347,7 @@ do -- TASK_A2G_SEAD
-- @param Core.Set#SET_UNIT TargetSetUnit
-- @param #string TaskBriefing The briefing of the task.
-- @return #TASK_A2G_SEAD self
function TASK_A2G_SEAD:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing )
function TASK_A2G_SEAD:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing)
local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "SEAD", TaskBriefing ) ) -- #TASK_A2G_SEAD
self:F()
@ -316,20 +355,55 @@ do -- TASK_A2G_SEAD
self:SetBriefing(
TaskBriefing or
"Execute a Suppression of Enemy Air Defenses.\n"
"Execute a Suppression of Enemy Air Defenses."
)
local TargetCoordinate = TargetSetUnit:GetFirst():GetCoordinate()
self:SetInfo( "Coordinates", TargetCoordinate )
self:SetInfo( "Threat", "[" .. string.rep( "", TargetSetUnit:CalculateThreatLevelA2G() ) .. "]" )
local DetectedItemsCount = TargetSetUnit:Count()
local DetectedItemsTypes = TargetSetUnit:GetTypeNames()
self:SetInfo( "Targets", string.format( "%d of %s", DetectedItemsCount, DetectedItemsTypes ) )
return self
end
function TASK_A2G_SEAD:UpdateTaskInfo()
local TargetCoordinate = self.Detection and self.Detection:GetDetectedItemCoordinate( self.DetectedItemIndex ) or self.TargetSetUnit:GetFirst():GetCoordinate()
self:SetInfo( "Coordinate", TargetCoordinate, 0 )
local ThreatLevel, ThreatText
if self.Detection then
ThreatLevel, ThreatText = self.Detection:GetDetectedItemThreatLevel( self.DetectedItemIndex )
else
ThreatLevel, ThreatText = self.TargetSetUnit:CalculateThreatLevelA2G()
end
self:SetInfo( "Threat", ThreatText .. " [" .. string.rep( "", ThreatLevel ) .. "]", 11 )
if self.Detection then
local DetectedItemsCount = self.TargetSetUnit:Count()
local ReportTypes = REPORT:New()
local TargetTypes = {}
for TargetUnitName, TargetUnit in pairs( self.TargetSetUnit:GetSet() ) do
local TargetType = self.Detection:GetDetectedUnitTypeName( TargetUnit )
if not TargetTypes[TargetType] then
TargetTypes[TargetType] = TargetType
ReportTypes:Add( TargetType )
end
end
self:SetInfo( "Targets", string.format( "%d of %s", DetectedItemsCount, ReportTypes:Text( ", " ) ), 10 )
else
local DetectedItemsCount = self.TargetSetUnit:Count()
local DetectedItemsTypes = self.TargetSetUnit:GetTypeNames()
self:SetInfo( "Targets", string.format( "%d of %s", DetectedItemsCount, DetectedItemsTypes ), 10 )
end
end
function TASK_A2G_SEAD:ReportOrder( ReportGroup )
local Coordinate = self:GetInfo( "Coordinate" )
--local Coordinate = self.TaskInfo.Coordinates.TaskInfoText
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
return Distance
end
--- @param #TASK_A2G_SEAD self
function TASK_A2G_SEAD:onafterGoal( TaskUnit, From, Event, To )
local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT
@ -341,7 +415,7 @@ do -- TASK_A2G_SEAD
self:__Goal( -10 )
end
--- Set a score when a target in scope of the A2A attack, has been destroyed .
--- Set a score when a target in scope of the A2G attack, has been destroyed .
-- @param #TASK_A2G_SEAD self
-- @param #string PlayerName The name of the player.
-- @param #number Score The score in points to be granted when task process has been achieved.
@ -357,7 +431,7 @@ do -- TASK_A2G_SEAD
return self
end
--- Set a score when all the targets in scope of the A2A attack, have been destroyed.
--- Set a score when all the targets in scope of the A2G attack, have been destroyed.
-- @param #TASK_A2G_SEAD self
-- @param #string PlayerName The name of the player.
-- @param #number Score The score in points.
@ -373,7 +447,7 @@ do -- TASK_A2G_SEAD
return self
end
--- Set a penalty when the A2A attack has failed.
--- Set a penalty when the A2G attack has failed.
-- @param #TASK_A2G_SEAD self
-- @param #string PlayerName The name of the player.
-- @param #number Penalty The penalty in points, must be a negative value!
@ -429,19 +503,66 @@ do -- TASK_A2G_BAI
self:SetBriefing(
TaskBriefing or
"Execute a Battlefield Air Interdiction of a group of enemy targets.\n"
"Execute a Battlefield Air Interdiction of a group of enemy targets."
)
local TargetCoordinate = TargetSetUnit:GetFirst():GetCoordinate()
self:SetInfo( "Coordinates", TargetCoordinate )
self:SetInfo( "Threat", "[" .. string.rep( "", TargetSetUnit:CalculateThreatLevelA2G() ) .. "]" )
local DetectedItemsCount = TargetSetUnit:Count()
local DetectedItemsTypes = TargetSetUnit:GetTypeNames()
self:SetInfo( "Targets", string.format( "%d of %s", DetectedItemsCount, DetectedItemsTypes ) )
return self
end
end
function TASK_A2G_BAI:UpdateTaskInfo()
self:E({self.Detection, self.DetectedItemIndex})
local TargetCoordinate = self.Detection and self.Detection:GetDetectedItemCoordinate( self.DetectedItemIndex ) or self.TargetSetUnit:GetFirst():GetCoordinate()
self:SetInfo( "Coordinate", TargetCoordinate, 0 )
local ThreatLevel, ThreatText
if self.Detection then
ThreatLevel, ThreatText = self.Detection:GetDetectedItemThreatLevel( self.DetectedItemIndex )
else
ThreatLevel, ThreatText = self.TargetSetUnit:CalculateThreatLevelA2G()
end
self:SetInfo( "Threat", ThreatText .. " [" .. string.rep( "", ThreatLevel ) .. "]", 11 )
if self.Detection then
local DetectedItemsCount = self.TargetSetUnit:Count()
local ReportTypes = REPORT:New()
local TargetTypes = {}
for TargetUnitName, TargetUnit in pairs( self.TargetSetUnit:GetSet() ) do
local TargetType = self.Detection:GetDetectedUnitTypeName( TargetUnit )
if not TargetTypes[TargetType] then
TargetTypes[TargetType] = TargetType
ReportTypes:Add( TargetType )
end
end
self:SetInfo( "Targets", string.format( "%d of %s", DetectedItemsCount, ReportTypes:Text( ", " ) ), 10 )
else
local DetectedItemsCount = self.TargetSetUnit:Count()
local DetectedItemsTypes = self.TargetSetUnit:GetTypeNames()
self:SetInfo( "Targets", string.format( "%d of %s", DetectedItemsCount, DetectedItemsTypes ), 10 )
end
local TargetCoordinate = self:GetInfo( "Coordinate" ) -- Core.Point#COORDINATE
local Velocity = self.TargetSetUnit:GetVelocityVec3()
local Heading = self.TargetSetUnit:GetHeading()
TargetCoordinate:SetHeading( Heading )
TargetCoordinate:SetVelocity( Velocity )
self:SetInfo( "Position", "Targets are" .. TargetCoordinate:GetMovingText() .. ".", 12 )
end
function TASK_A2G_BAI:ReportOrder( ReportGroup )
local Coordinate = self:GetInfo( "Coordinate" )
--local Coordinate = self.TaskInfo.Coordinates.TaskInfoText
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
return Distance
end
--- @param #TASK_A2G_BAI self
function TASK_A2G_BAI:onafterGoal( TaskUnit, From, Event, To )
@ -454,7 +575,7 @@ do -- TASK_A2G_BAI
self:__Goal( -10 )
end
--- Set a score when a target in scope of the A2A attack, has been destroyed .
--- Set a score when a target in scope of the A2G attack, has been destroyed .
-- @param #TASK_A2G_BAI self
-- @param #string PlayerName The name of the player.
-- @param #number Score The score in points to be granted when task process has been achieved.
@ -470,7 +591,7 @@ do -- TASK_A2G_BAI
return self
end
--- Set a score when all the targets in scope of the A2A attack, have been destroyed.
--- Set a score when all the targets in scope of the A2G attack, have been destroyed.
-- @param #TASK_A2G_BAI self
-- @param #string PlayerName The name of the player.
-- @param #number Score The score in points.
@ -486,7 +607,7 @@ do -- TASK_A2G_BAI
return self
end
--- Set a penalty when the A2A attack has failed.
--- Set a penalty when the A2G attack has failed.
-- @param #TASK_A2G_BAI self
-- @param #string PlayerName The name of the player.
-- @param #number Penalty The penalty in points, must be a negative value!
@ -541,20 +662,56 @@ do -- TASK_A2G_CAS
self:SetBriefing(
TaskBriefing or
"Execute a Close Air Support for a group of enemy targets.\n" ..
"Beware of friendlies at the vicinity!\n"
"Execute a Close Air Support for a group of enemy targets. " ..
"Beware of friendlies at the vicinity! "
)
local TargetCoordinate = TargetSetUnit:GetFirst():GetCoordinate()
self:SetInfo( "Coordinates", TargetCoordinate )
self:SetInfo( "Threat", "[" .. string.rep( "", TargetSetUnit:CalculateThreatLevelA2G() ) .. "]" )
local DetectedItemsCount = TargetSetUnit:Count()
local DetectedItemsTypes = TargetSetUnit:GetTypeNames()
self:SetInfo( "Targets", string.format( "%d of %s", DetectedItemsCount, DetectedItemsTypes ) )
return self
end
function TASK_A2G_CAS:UpdateTaskInfo()
local TargetCoordinate = ( self.Detection and self.Detection:GetDetectedItemCoordinate( self.DetectedItemIndex ) ) or self.TargetSetUnit:GetFirst():GetCoordinate()
self:SetInfo( "Coordinate", TargetCoordinate, 0 )
local ThreatLevel, ThreatText
if self.Detection then
ThreatLevel, ThreatText = self.Detection:GetDetectedItemThreatLevel( self.DetectedItemIndex )
else
ThreatLevel, ThreatText = self.TargetSetUnit:CalculateThreatLevelA2G()
end
self:SetInfo( "Threat", ThreatText .. " [" .. string.rep( "", ThreatLevel ) .. "]", 11 )
if self.Detection then
local DetectedItemsCount = self.TargetSetUnit:Count()
local ReportTypes = REPORT:New()
local TargetTypes = {}
for TargetUnitName, TargetUnit in pairs( self.TargetSetUnit:GetSet() ) do
local TargetType = self.Detection:GetDetectedUnitTypeName( TargetUnit )
if not TargetTypes[TargetType] then
TargetTypes[TargetType] = TargetType
ReportTypes:Add( TargetType )
end
end
self:SetInfo( "Targets", string.format( "%d of %s", DetectedItemsCount, ReportTypes:Text( ", " ) ), 10 )
else
local DetectedItemsCount = self.TargetSetUnit:Count()
local DetectedItemsTypes = self.TargetSetUnit:GetTypeNames()
self:SetInfo( "Targets", string.format( "%d of %s", DetectedItemsCount, DetectedItemsTypes ), 10 )
end
end
--- @param #TASK_A2G_CAS self
function TASK_A2G_CAS:ReportOrder( ReportGroup )
local Coordinate = self:GetInfo( "Coordinate" )
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
return Distance
end
--- @param #TASK_A2G_CAS self
function TASK_A2G_CAS:onafterGoal( TaskUnit, From, Event, To )
@ -567,7 +724,7 @@ do -- TASK_A2G_CAS
self:__Goal( -10 )
end
--- Set a score when a target in scope of the A2A attack, has been destroyed .
--- Set a score when a target in scope of the A2G attack, has been destroyed .
-- @param #TASK_A2G_CAS self
-- @param #string PlayerName The name of the player.
-- @param #number Score The score in points to be granted when task process has been achieved.
@ -583,7 +740,7 @@ do -- TASK_A2G_CAS
return self
end
--- Set a score when all the targets in scope of the A2A attack, have been destroyed.
--- Set a score when all the targets in scope of the A2G attack, have been destroyed.
-- @param #TASK_A2G_CAS self
-- @param #string PlayerName The name of the player.
-- @param #number Score The score in points.
@ -599,7 +756,7 @@ do -- TASK_A2G_CAS
return self
end
--- Set a penalty when the A2A attack has failed.
--- Set a penalty when the A2G attack has failed.
-- @param #TASK_A2G_CAS self
-- @param #string PlayerName The name of the player.
-- @param #number Penalty The penalty in points, must be a negative value!

View File

@ -59,6 +59,7 @@ do -- TASK_A2G_DISPATCHER
self.Mission = Mission
self.Detection:FilterCategories( Unit.Category.GROUND_UNIT, Unit.Category.SHIP )
self.Detection:FilterFriendliesCategory( Unit.Category.GROUND_UNIT )
self:AddTransition( "Started", "Assign", "Started" )
@ -81,7 +82,7 @@ do -- TASK_A2G_DISPATCHER
--- Creates a SEAD task when there are targets for it.
-- @param #TASK_A2G_DISPATCHER self
-- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem
-- @return Set#SET_UNIT TargetSetUnit: The target set of units.
-- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units.
-- @return #nil If there are no targets to be set.
function TASK_A2G_DISPATCHER:EvaluateSEAD( DetectedItem )
self:F( { DetectedItem.ItemID } )
@ -109,7 +110,8 @@ do -- TASK_A2G_DISPATCHER
--- Creates a CAS task when there are targets for it.
-- @param #TASK_A2G_DISPATCHER self
-- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem
-- @return Tasking.Task#TASK
-- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units.
-- @return #nil If there are no targets to be set.
function TASK_A2G_DISPATCHER:EvaluateCAS( DetectedItem )
self:F( { DetectedItem.ItemID } )
@ -120,8 +122,9 @@ do -- TASK_A2G_DISPATCHER
-- Determine if the set has radar targets. If it does, construct a SEAD task.
local GroundUnitCount = DetectedSet:HasGroundUnits()
local FriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedItem )
local RadarCount = DetectedSet:HasSEAD()
if GroundUnitCount > 0 and FriendliesNearBy == true then
if RadarCount == 0 and GroundUnitCount > 0 and FriendliesNearBy == true then
-- Copy the Set
local TargetSetUnit = SET_UNIT:New()
@ -137,7 +140,8 @@ do -- TASK_A2G_DISPATCHER
--- Creates a BAI task when there are targets for it.
-- @param #TASK_A2G_DISPATCHER self
-- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem
-- @return Tasking.Task#TASK
-- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units.
-- @return #nil If there are no targets to be set.
function TASK_A2G_DISPATCHER:EvaluateBAI( DetectedItem, FriendlyCoalition )
self:F( { DetectedItem.ItemID } )
@ -148,8 +152,9 @@ do -- TASK_A2G_DISPATCHER
-- Determine if the set has radar targets. If it does, construct a SEAD task.
local GroundUnitCount = DetectedSet:HasGroundUnits()
local FriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedItem )
local RadarCount = DetectedSet:HasSEAD()
if GroundUnitCount > 0 and FriendliesNearBy == false then
if RadarCount == 0 and GroundUnitCount > 0 and FriendliesNearBy == false then
-- Copy the Set
local TargetSetUnit = SET_UNIT:New()
@ -162,6 +167,12 @@ do -- TASK_A2G_DISPATCHER
return nil
end
function TASK_A2G_DISPATCHER:RemoveTask( TaskIndex )
self.Mission:RemoveTask( self.Tasks[TaskIndex] )
self.Tasks[TaskIndex] = nil
end
--- Evaluates the removal of the Task from the Mission.
-- Can only occur when the DetectedItem is Changed AND the state of the Task is "Planned".
-- @param #TASK_A2G_DISPATCHER self
@ -173,10 +184,9 @@ do -- TASK_A2G_DISPATCHER
function TASK_A2G_DISPATCHER:EvaluateRemoveTask( Mission, Task, TaskIndex, DetectedItemChanged )
if Task then
if Task:IsStatePlanned() and DetectedItemChanged == true then
if ( Task:IsStatePlanned() and DetectedItemChanged == true ) or Task:IsStateCancelled() then
--self:E( "Removing Tasking: " .. Task:GetTaskName() )
Mission:RemoveTask( Task )
self.Tasks[TaskIndex] = nil
self:RemoveTask( TaskIndex )
end
end
@ -211,6 +221,7 @@ do -- TASK_A2G_DISPATCHER
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
Mission:GetCommandCenter():MessageToGroup( string.format( "Obsolete A2G task %s for %s removed.", TaskText, Mission:GetName() ), TaskGroup )
end
Task = self:RemoveTask( TaskIndex )
Mission:RemoveTask( Task )
self.Tasks[TaskIndex] = nil
end
@ -224,20 +235,119 @@ do -- TASK_A2G_DISPATCHER
local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT
local DetectedZone = DetectedItem.Zone
--self:E( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } )
DetectedSet:Flush()
--DetectedSet:Flush()
local DetectedItemID = DetectedItem.ID
local TaskIndex = DetectedItem.Index
local DetectedItemChanged = DetectedItem.Changed
local Task = self.Tasks[TaskIndex]
Task = self:EvaluateRemoveTask( Mission, Task, TaskIndex, DetectedItemChanged ) -- Task will be removed if it is planned and changed.
self:E( { DetectedItemChanged = DetectedItemChanged, DetectedItemID = DetectedItemID, TaskIndex = TaskIndex } )
local Task = self.Tasks[TaskIndex] -- Tasking.Task_A2G#TASK_A2G
if Task then
-- If there is a Task and the task was assigned, then we check if the task was changed ... If it was, we need to reevaluate the targets.
if Task:IsStateAssigned() then
if DetectedItemChanged == true then -- The detection has changed, thus a new TargetSet is to be evaluated and set
local TargetsReport = REPORT:New()
local TargetSetUnit = self:EvaluateSEAD( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed...
if TargetSetUnit then
if Task:IsInstanceOf( TASK_A2G_SEAD ) then
Task:SetTargetSetUnit( TargetSetUnit )
Task:UpdateTaskInfo()
TargetsReport:Add( Detection:GetChangeText( DetectedItem ) )
else
Task:Cancel()
end
else
local TargetSetUnit = self:EvaluateCAS( DetectedItem ) -- Returns a SetUnit if there are targets to be CASed...
if TargetSetUnit then
if Task:IsInstanceOf( TASK_A2G_CAS ) then
Task:SetTargetSetUnit( TargetSetUnit )
Task:SetDetection( Detection, TaskIndex )
Task:UpdateTaskInfo()
TargetsReport:Add( Detection:GetChangeText( DetectedItem ) )
else
Task:Cancel()
Task = self:RemoveTask( TaskIndex )
end
else
local TargetSetUnit = self:EvaluateBAI( DetectedItem ) -- Returns a SetUnit if there are targets to be BAIed...
if TargetSetUnit then
if Task:IsInstanceOf( TASK_A2G_BAI ) then
Task:SetTargetSetUnit( TargetSetUnit )
Task:SetDetection( Detection, TaskIndex )
Task:UpdateTaskInfo()
TargetsReport:Add( Detection:GetChangeText( DetectedItem ) )
else
Task:Cancel()
Task = self:RemoveTask( TaskIndex )
end
end
end
end
-- Now we send to each group the changes, if any.
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
local TargetsText = TargetsReport:Text(", ")
if ( Mission:IsGroupAssigned(TaskGroup) ) and TargetsText ~= "" then
Mission:GetCommandCenter():MessageToGroup( string.format( "Task %s has change of targets:\n %s", Task:GetName(), TargetsText ), TaskGroup )
end
end
end
end
end
if Task then
if Task:IsStatePlanned() then
if DetectedItemChanged == true then -- The detection has changed, thus a new TargetSet is to be evaluated and set
if Task:IsInstanceOf( TASK_A2G_SEAD ) then
local TargetSetUnit = self:EvaluateSEAD( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed...
if TargetSetUnit then
Task:SetTargetSetUnit( TargetSetUnit )
Task:UpdateTaskInfo()
else
Task:Cancel()
Task = self:RemoveTask( TaskIndex )
end
else
if Task:IsInstanceOf( TASK_A2G_CAS ) then
local TargetSetUnit = self:EvaluateCAS( DetectedItem ) -- Returns a SetUnit if there are targets to be CASed...
if TargetSetUnit then
Task:SetTargetSetUnit( TargetSetUnit )
Task:SetDetection( Detection, TaskIndex )
Task:UpdateTaskInfo()
else
Task:Cancel()
Task = self:RemoveTask( TaskIndex )
end
else
if Task:IsInstanceOf( TASK_A2G_BAI ) then
local TargetSetUnit = self:EvaluateBAI( DetectedItem ) -- Returns a SetUnit if there are targets to be BAIed...
if TargetSetUnit then
Task:SetTargetSetUnit( TargetSetUnit )
Task:SetDetection( Detection, TaskIndex )
Task:UpdateTaskInfo()
else
Task:Cancel()
Task = self:RemoveTask( TaskIndex )
end
else
Task:Cancel()
Task = self:RemoveTask( TaskIndex )
end
end
end
end
end
end
-- Evaluate SEAD
if not Task then
local TargetSetUnit = self:EvaluateSEAD( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed...
if TargetSetUnit then
Task = TASK_A2G_SEAD:New( Mission, self.SetGroup, string.format( "SEAD.%03d", DetectedItemID ), TargetSetUnit )
Task:SetDetection( Detection, TaskIndex )
end
-- Evaluate CAS
@ -245,6 +355,7 @@ do -- TASK_A2G_DISPATCHER
local TargetSetUnit = self:EvaluateCAS( DetectedItem ) -- Returns a SetUnit if there are targets to be CASed...
if TargetSetUnit then
Task = TASK_A2G_CAS:New( Mission, self.SetGroup, string.format( "CAS.%03d", DetectedItemID ), TargetSetUnit )
Task:SetDetection( Detection, TaskIndex )
end
-- Evaluate BAI
@ -252,6 +363,7 @@ do -- TASK_A2G_DISPATCHER
local TargetSetUnit = self:EvaluateBAI( DetectedItem, self.Mission:GetCommandCenter():GetPositionable():GetCoalition() ) -- Returns a SetUnit if there are targets to be BAIed...
if TargetSetUnit then
Task = TASK_A2G_BAI:New( Mission, self.SetGroup, string.format( "BAI.%03d", DetectedItemID ), TargetSetUnit )
Task:SetDetection( Detection, TaskIndex )
end
end
end
@ -260,13 +372,13 @@ do -- TASK_A2G_DISPATCHER
self.Tasks[TaskIndex] = Task
Task:SetTargetZone( DetectedZone )
Task:SetDispatcher( self )
Task:UpdateTaskInfo()
Mission:AddTask( Task )
TaskReport:Add( Task:GetName() )
else
self:E("This should not happen")
end
end
@ -278,7 +390,6 @@ do -- TASK_A2G_DISPATCHER
Mission:GetCommandCenter():SetMenu()
local TaskText = TaskReport:Text(", ")
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" then
Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetName(), TaskText ), TaskGroup )

View File

@ -161,10 +161,15 @@ do -- TASK_CARGO
self.TaskType = TaskType
self.SmokeColor = SMOKECOLOR.Red
self.CargoItemCount = {} -- Map of Carriers having a cargo item count to check the cargo loading limits.
self.CargoLimit = 2
self.DeployZones = {} -- setmetatable( {}, { __mode = "v" } ) -- weak table on value
local Fsm = self:GetUnitProcess()
Fsm:SetStartState( "Planned" )
Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "SelectAction", Rejected = "Reject" } )
@ -202,12 +207,19 @@ do -- TASK_CARGO
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_CARGO#TASK_CARGO Task
function Fsm:onafterSelectAction( TaskUnit, Task )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
local TaskUnitName = TaskUnit:GetName()
self:E( { TaskUnit = TaskUnitName, Task = Task and Task:GetClassNameAndID() } )
local MenuTime = timer.getTime()
TaskUnit.Menu = MENU_GROUP:New( TaskUnit:GetGroup(), Task:GetName() .. " @ " .. TaskUnit:GetName() ):SetTime( MenuTime )
local CargoItemCount = TaskUnit:CargoItemCount()
--Task:GetMission():GetCommandCenter():MessageToGroup( "Cargo in carrier: " .. CargoItemCount, TaskUnit:GetGroup() )
Task.SetCargo:ForEachCargo(
@ -226,51 +238,37 @@ do -- TASK_CARGO
-- Cargo
-- ):SetTime(MenuTime)
-- end
if Cargo:IsUnLoaded() then
if Cargo:IsInRadius( TaskUnit:GetPointVec2() ) then
MENU_GROUP_COMMAND:New(
TaskUnit:GetGroup(),
"Board cargo " .. Cargo.Name,
TaskUnit.Menu,
self.MenuBoardCargo,
self,
Cargo
):SetTime(MenuTime)
else
MENU_GROUP_COMMAND:New(
TaskUnit:GetGroup(),
"Route to Pickup cargo " .. Cargo.Name,
TaskUnit.Menu,
self.MenuRouteToPickup,
self,
Cargo
):SetTime(MenuTime)
if CargoItemCount < Task.CargoLimit then
if Cargo:IsInRadius( TaskUnit:GetPointVec2() ) then
local NotInDeployZones = true
for DeployZoneName, DeployZone in pairs( Task.DeployZones ) do
if Cargo:IsInZone( DeployZone ) then
NotInDeployZones = false
end
end
if NotInDeployZones then
if not TaskUnit:InAir() then
MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), "Board cargo " .. Cargo.Name, TaskUnit.Menu, self.MenuBoardCargo, self, Cargo ):SetTime(MenuTime)
end
end
else
MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), "Route to Pickup cargo " .. Cargo.Name, TaskUnit.Menu, self.MenuRouteToPickup, self, Cargo ):SetTime(MenuTime)
end
end
end
if Cargo:IsLoaded() then
MENU_GROUP_COMMAND:New(
TaskUnit:GetGroup(),
"Unboard cargo " .. Cargo.Name,
TaskUnit.Menu,
self.MenuUnBoardCargo,
self,
Cargo
):SetTime(MenuTime)
if not TaskUnit:InAir() then
MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), "Unboard cargo " .. Cargo.Name, TaskUnit.Menu, self.MenuUnBoardCargo, self, Cargo ):SetTime(MenuTime)
end
-- Deployzones are optional zones that can be selected to request routing information.
for DeployZoneName, DeployZone in pairs( Task.DeployZones ) do
if not Cargo:IsInZone( DeployZone ) then
MENU_GROUP_COMMAND:New(
TaskUnit:GetGroup(),
"Route to Deploy cargo at " .. DeployZoneName,
TaskUnit.Menu,
self.MenuRouteToDeploy,
self,
DeployZone
):SetTime(MenuTime)
MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), "Route to Deploy cargo at " .. DeployZoneName, TaskUnit.Menu, self.MenuRouteToDeploy, self, DeployZone ):SetTime(MenuTime)
end
end
end
@ -284,9 +282,9 @@ do -- TASK_CARGO
self:__SelectAction( -15 )
--Task:GetMission():GetCommandCenter():MessageToGroup("Cargo menu is ready ...", TaskUnit:GetGroup() )
end
---
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
@ -320,8 +318,7 @@ do -- TASK_CARGO
--#Wrapper.Unit#UNIT
--- Route to Cargo
-- @param #FSM_PROCESS self
--- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
-- @param From
@ -341,15 +338,15 @@ do -- TASK_CARGO
---
-- @param #FSM_PROCESS self
--- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterArriveAtPickup( TaskUnit, Task )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
if self.Cargo:IsAlive() then
TaskUnit:Smoke( Task:GetSmokeColor(), 15 )
self.Cargo:Smoke( Task:GetSmokeColor(), 15 )
if TaskUnit:IsAir() then
Task:GetMission():GetCommandCenter():MessageToGroup( "Land", TaskUnit:GetGroup() )
self:__Land( -0.1, "Pickup" )
else
self:__SelectAction( -0.1 )
@ -358,8 +355,7 @@ do -- TASK_CARGO
end
---
-- @param #FSM_PROCESS self
--- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterCancelRouteToPickup( TaskUnit, Task )
@ -369,8 +365,7 @@ do -- TASK_CARGO
end
--- Route to DeployZone
-- @param #FSM_PROCESS self
--- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
function Fsm:onafterRouteToDeploy( TaskUnit, Task, From, Event, To, DeployZone )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
@ -382,14 +377,14 @@ do -- TASK_CARGO
end
---
-- @param #FSM_PROCESS self
--- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterArriveAtDeploy( TaskUnit, Task )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
if TaskUnit:IsAir() then
Task:GetMission():GetCommandCenter():MessageToGroup( "Land", TaskUnit:GetGroup() )
self:__Land( -0.1, "Deploy" )
else
self:__SelectAction( -0.1 )
@ -397,8 +392,7 @@ do -- TASK_CARGO
end
---
-- @param #FSM_PROCESS self
--- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterCancelRouteToDeploy( TaskUnit, Task )
@ -409,7 +403,7 @@ do -- TASK_CARGO
-- @param #FSM_PROCESS self
--- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterLand( TaskUnit, Task, From, Event, To, Action )
@ -418,7 +412,6 @@ do -- TASK_CARGO
if self.Cargo:IsAlive() then
if self.Cargo:IsInRadius( TaskUnit:GetPointVec2() ) then
if TaskUnit:InAir() then
Task:GetMission():GetCommandCenter():MessageToGroup( "Land", TaskUnit:GetGroup() )
self:__Land( -10, Action )
else
Task:GetMission():GetCommandCenter():MessageToGroup( "Landed ...", TaskUnit:GetGroup() )
@ -434,8 +427,7 @@ do -- TASK_CARGO
end
end
---
-- @param #FSM_PROCESS self
--- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterLanded( TaskUnit, Task, From, Event, To, Action )
@ -458,8 +450,7 @@ do -- TASK_CARGO
end
end
---
-- @param #FSM_PROCESS self
--- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterPrepareBoarding( TaskUnit, Task, From, Event, To, Cargo )
@ -471,8 +462,7 @@ do -- TASK_CARGO
end
end
---
-- @param #FSM_PROCESS self
--- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterBoard( TaskUnit, Task )
@ -498,14 +488,18 @@ do -- TASK_CARGO
end
---
-- @param #FSM_PROCESS self
--- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterBoarded( TaskUnit, Task )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
local TaskUnitName = TaskUnit:GetName()
self:E( { TaskUnit = TaskUnitName, Task = Task and Task:GetClassNameAndID() } )
self.Cargo:MessageToGroup( "Boarded ...", TaskUnit:GetGroup() )
TaskUnit:AddCargo( self.Cargo )
self:__SelectAction( 1 )
-- TODO:I need to find a more decent solution for this.
@ -579,12 +573,28 @@ do -- TASK_CARGO
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterUnBoarded( TaskUnit, Task )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
local TaskUnitName = TaskUnit:GetName()
self:E( { TaskUnit = TaskUnitName, Task = Task and Task:GetClassNameAndID() } )
self.Cargo:MessageToGroup( "UnBoarded ...", TaskUnit:GetGroup() )
TaskUnit:RemoveCargo( self.Cargo )
local NotInDeployZones = true
for DeployZoneName, DeployZone in pairs( Task.DeployZones ) do
if self.Cargo:IsInZone( DeployZone ) then
NotInDeployZones = false
end
end
if NotInDeployZones == false then
self.Cargo:SetDeployed( true )
end
-- TODO:I need to find a more decent solution for this.
Task:E( { CargoDeployed = Task.CargoDeployed } )
Task:E( { CargoDeployed = Task.CargoDeployed and "true" or "false" } )
Task:E( { CargoIsAlive = self.Cargo:IsAlive() and "true" or "false" } )
if self.Cargo:IsAlive() then
if Task.CargoDeployed then
Task:CargoDeployed( TaskUnit, self.Cargo, self.DeployZone )
@ -598,7 +608,17 @@ do -- TASK_CARGO
return self
end
--- Set a limit on the amount of cargo items that can be loaded into the Carriers.
-- @param #TASK_CARGO self
-- @param CargoLimit Specifies a number of cargo items that can be loaded in the helicopter.
-- @return #TASK_CARGO
function TASK_CARGO:SetCargoLimit( CargoLimit )
self.CargoLimit = CargoLimit
return self
end
---@param Color Might be SMOKECOLOR.Blue, SMOKECOLOR.Red SMOKECOLOR.Orange, SMOKECOLOR.White or SMOKECOLOR.Green
function TASK_CARGO:SetSmokeColor(SmokeColor)
@ -770,6 +790,17 @@ do -- TASK_CARGO
return self
end
function TASK_CARGO:SetGoalTotal()
self.GoalTotal = self.SetCargo:Count()
end
function TASK_CARGO:GetGoalTotal()
return self.GoalTotal
end
end
@ -802,6 +833,8 @@ do -- TASK_CARGO_TRANSPORT
self:AddTransition( "*", "CargoPickedUp", "*" )
self:AddTransition( "*", "CargoDeployed", "*" )
self:E( { CargoDeployed = self.CargoDeployed ~= nil and "true" or "false" } )
--- OnBefore Transition Handler for Event CargoPickedUp.
-- @function [parent=#TASK_CARGO_TRANSPORT] OnBeforeCargoPickedUp
-- @param #TASK_CARGO_TRANSPORT self
@ -880,7 +913,7 @@ do -- TASK_CARGO_TRANSPORT
local CargoType = Cargo:GetType()
local CargoName = Cargo:GetName()
local CargoCoordinate = Cargo:GetCoordinate()
CargoReport:Add( string.format( '- "%s" (%s) at %s', CargoName, CargoType, CargoCoordinate:ToString() ) )
CargoReport:Add( string.format( '- "%s" (%s) at %s', CargoName, CargoType, CargoCoordinate:ToStringMGRS() ) )
end
)
@ -892,6 +925,12 @@ do -- TASK_CARGO_TRANSPORT
return self
end
function TASK_CARGO_TRANSPORT:ReportOrder( ReportGroup )
return true
end
---
-- @param #TASK_CARGO_TRANSPORT self
@ -908,27 +947,36 @@ do -- TASK_CARGO_TRANSPORT
-- Loop the CargoSet (so evaluate each Cargo in the SET_CARGO ).
for CargoID, CargoData in pairs( Set ) do
local Cargo = CargoData -- Core.Cargo#CARGO
if Cargo:IsDeployed() then
-- Loop the DeployZones set for the TASK_CARGO_TRANSPORT.
for DeployZoneID, DeployZone in pairs( DeployZones ) do
-- If there is a Cargo not in one of DeployZones, then not all Cargo is deployed.
self:T( { Cargo.CargoObject } )
if Cargo:IsInZone( DeployZone ) then
else
CargoDeployed = false
-- Loop the DeployZones set for the TASK_CARGO_TRANSPORT.
for DeployZoneID, DeployZone in pairs( DeployZones ) do
-- If there is a Cargo not in one of DeployZones, then not all Cargo is deployed.
self:T( { Cargo.CargoObject } )
if Cargo:IsInZone( DeployZone ) == false then
CargoDeployed = false
end
end
else
CargoDeployed = false
end
end
return CargoDeployed
end
---
--- @param #TASK_CARGO_TRANSPORT self
function TASK_CARGO_TRANSPORT:onafterGoal( TaskUnit, From, Event, To )
local CargoSet = self.CargoSet
if self:IsAllCargoTransported() then
self:Success()
end
self:__Goal( -10 )
end
end

View File

@ -31,7 +31,72 @@ FLARECOLOR = trigger.flareColor -- #FLARECOLOR
--- Utilities static class.
-- @type UTILS
UTILS = {}
UTILS = {
_MarkID = 1
}
--- Function to infer instance of an object
--
-- ### Examples:
--
-- * UTILS.IsInstanceOf( 'some text', 'string' ) will return true
-- * UTILS.IsInstanceOf( some_function, 'function' ) will return true
-- * UTILS.IsInstanceOf( 10, 'number' ) will return true
-- * UTILS.IsInstanceOf( false, 'boolean' ) will return true
-- * UTILS.IsInstanceOf( nil, 'nil' ) will return true
--
-- * UTILS.IsInstanceOf( ZONE:New( 'some zone', ZONE ) will return true
-- * UTILS.IsInstanceOf( ZONE:New( 'some zone', 'ZONE' ) will return true
-- * UTILS.IsInstanceOf( ZONE:New( 'some zone', 'zone' ) will return true
-- * UTILS.IsInstanceOf( ZONE:New( 'some zone', 'BASE' ) will return true
--
-- * UTILS.IsInstanceOf( ZONE:New( 'some zone', 'GROUP' ) will return false
--
--
-- @param object is the object to be evaluated
-- @param className is the name of the class to evaluate (can be either a string or a Moose class)
-- @return #boolean
UTILS.IsInstanceOf = function( object, className )
-- Is className NOT a string ?
if not type( className ) == 'string' then
-- Is className a Moose class ?
if type( className ) == 'table' and className.IsInstanceOf ~= nil then
-- Get the name of the Moose class as a string
className = className.ClassName
-- className is neither a string nor a Moose class, throw an error
else
-- I'm not sure if this should take advantage of MOOSE logging function, or throw an error for pcall
local err_str = 'className parameter should be a string; parameter received: '..type( className )
self:E( err_str )
return false
-- error( err_str )
end
end
-- Is the object a Moose class instance ?
if type( object ) == 'table' and object.IsInstanceOf ~= nil then
-- Use the IsInstanceOf method of the BASE class
return object:IsInstanceOf( className )
else
-- If the object is not an instance of a Moose class, evaluate against lua basic data types
local basicDataTypes = { 'string', 'number', 'function', 'boolean', 'nil', 'table' }
for _, basicDataType in ipairs( basicDataTypes ) do
if className == basicDataType then
return type( object ) == basicDataType
end
end
end
-- Check failed
return false
end
--from http://lua-users.org/wiki/CopyTable
@ -171,22 +236,36 @@ UTILS.FeetToMeters = function(feet)
return feet*0.3048
end
UTILS.MpsToKnots = function(mps)
return mps*3600/1852
UTILS.KnotsToKmph = function(knots)
return knots* 1.852
end
UTILS.MpsToKmph = function(mps)
return mps*3.6
UTILS.KmphToMps = function( kmph )
return kmph / 3.6
end
UTILS.KnotsToMps = function(knots)
return knots*1852/3600
UTILS.MpsToKmph = function( mps )
return mps * 3.6
end
UTILS.KmphToMps = function(kmph)
return kmph/3.6
UTILS.MiphToMps = function( miph )
return miph * 0.44704
end
UTILS.MpsToMiph = function( mps )
return mps / 0.44704
end
UTILS.MpsToKnots = function( mps )
return mps * 3600 / 1852
end
UTILS.KnotsToMps = function( knots )
return knots * 1852 / 3600
end
--[[acc:
in DM: decimal point of minutes.
In DMS: decimal point of seconds.
@ -238,12 +317,13 @@ UTILS.tostringLL = function( lat, lon, acc, DMS)
end
local secFrmtStr -- create the formatting string for the seconds place
if acc <= 0 then -- no decimal place.
secFrmtStr = '%02d'
else
local width = 3 + acc -- 01.310 - that's a width of 6, for example.
secFrmtStr = '%0' .. width .. '.' .. acc .. 'f'
end
secFrmtStr = '%02d'
-- if acc <= 0 then -- no decimal place.
-- secFrmtStr = '%02d'
-- else
-- local width = 3 + acc -- 01.310 - that's a width of 6, for example.
-- secFrmtStr = '%0' .. width .. '.' .. acc .. 'f'
-- end
return string.format('%02d', latDeg) .. ' ' .. string.format('%02d', latMin) .. '\' ' .. string.format(secFrmtStr, latSec) .. '"' .. latHemi .. ' '
.. string.format('%02d', lonDeg) .. ' ' .. string.format('%02d', lonMin) .. '\' ' .. string.format(secFrmtStr, lonSec) .. '"' .. lonHemi
@ -327,3 +407,28 @@ function UTILS.spairs( t, order )
end
end
end
-- get a new mark ID for markings
function UTILS.GetMarkID()
UTILS._MarkID = UTILS._MarkID + 1
return UTILS._MarkID
end
-- Test if a Vec2 is in a radius of another Vec2
function UTILS.IsInRadius( InVec2, Vec2, Radius )
local InRadius = ( ( InVec2.x - Vec2.x ) ^2 + ( InVec2.y - Vec2.y ) ^2 ) ^ 0.5 <= Radius
return InRadius
end
-- Test if a Vec3 is in the sphere of another Vec3
function UTILS.IsInSphere( InVec3, Vec3, Radius )
local InSphere = ( ( InVec3.x - Vec3.x ) ^2 + ( InVec3.y - Vec3.y ) ^2 + ( InVec3.z - Vec3.z ) ^2 ) ^ 0.5 <= Radius
return InSphere
end

View File

@ -57,7 +57,33 @@ AIRBASE = {
},
}
--- @field Caucasus
--- Enumeration to identify the airbases in the Caucasus region.
--
-- These are all airbases of Caucasus:
--
-- * AIRBASE.Caucasus.Gelendzhik
-- * AIRBASE.Caucasus.Krasnodar_Pashkovsky
-- * AIRBASE.Caucasus.Sukhumi_Babushara
-- * AIRBASE.Caucasus.Gudauta
-- * AIRBASE.Caucasus.Batumi
-- * AIRBASE.Caucasus.Senaki_Kolkhi
-- * AIRBASE.Caucasus.Kobuleti
-- * AIRBASE.Caucasus.Kutaisi
-- * AIRBASE.Caucasus.Tbilisi_Lochini
-- * AIRBASE.Caucasus.Soganlug
-- * AIRBASE.Caucasus.Vaziani
-- * AIRBASE.Caucasus.Anapa_Vityazevo
-- * AIRBASE.Caucasus.Krasnodar_Center
-- * AIRBASE.Caucasus.Novorossiysk
-- * AIRBASE.Caucasus.Krymsk
-- * AIRBASE.Caucasus.Maykop_Khanskaya
-- * AIRBASE.Caucasus.Sochi_Adler
-- * AIRBASE.Caucasus.Mineralnye_Vody
-- * AIRBASE.Caucasus.Nalchik
-- * AIRBASE.Caucasus.Mozdok
-- * AIRBASE.Caucasus.Beslan
--
-- @field Caucasus
AIRBASE.Caucasus = {
["Gelendzhik"] = "Gelendzhik",
["Krasnodar_Pashkovsky"] = "Krasnodar-Pashkovsky",
@ -83,6 +109,28 @@ AIRBASE.Caucasus = {
}
--- @field Nevada
--
-- These are all airbases of Nevada:
--
-- * AIRBASE.Nevada.Creech_AFB
-- * AIRBASE.Nevada.Groom_Lake_AFB
-- * AIRBASE.Nevada.McCarran_International_Airport
-- * AIRBASE.Nevada.Nellis_AFB
-- * AIRBASE.Nevada.Beatty_Airport
-- * AIRBASE.Nevada.Boulder_City_Airport
-- * AIRBASE.Nevada.Echo_Bay
-- * AIRBASE.Nevada.Henderson_Executive_Airport
-- * AIRBASE.Nevada.Jean_Airport
-- * AIRBASE.Nevada.Laughlin_Airport
-- * AIRBASE.Nevada.Lincoln_County
-- * AIRBASE.Nevada.Mellan_Airstrip
-- * AIRBASE.Nevada.Mesquite
-- * AIRBASE.Nevada.Mina_Airport_3Q0
-- * AIRBASE.Nevada.North_Las_Vegas
-- * AIRBASE.Nevada.Pahute_Mesa_Airstrip
-- * AIRBASE.Nevada.Tonopah_Airport
-- * AIRBASE.Nevada.Tonopah_Test_Range_Airfield
--
AIRBASE.Nevada = {
["Creech_AFB"] = "Creech AFB",
["Groom_Lake_AFB"] = "Groom Lake AFB",
@ -105,6 +153,40 @@ AIRBASE.Nevada = {
}
--- @field Normandy
--
-- These are all airbases of Normandy:
--
-- * AIRBASE.Normandy.Saint_Pierre_du_Mont
-- * AIRBASE.Normandy.Lignerolles
-- * AIRBASE.Normandy.Cretteville
-- * AIRBASE.Normandy.Maupertus
-- * AIRBASE.Normandy.Brucheville
-- * AIRBASE.Normandy.Meautis
-- * AIRBASE.Normandy.Cricqueville_en_Bessin
-- * AIRBASE.Normandy.Lessay
-- * AIRBASE.Normandy.Sainte_Laurent_sur_Mer
-- * AIRBASE.Normandy.Biniville
-- * AIRBASE.Normandy.Cardonville
-- * AIRBASE.Normandy.Deux_Jumeaux
-- * AIRBASE.Normandy.Chippelle
-- * AIRBASE.Normandy.Beuzeville
-- * AIRBASE.Normandy.Azeville
-- * AIRBASE.Normandy.Picauville
-- * AIRBASE.Normandy.Le_Molay
-- * AIRBASE.Normandy.Longues_sur_Mer
-- * AIRBASE.Normandy.Carpiquet
-- * AIRBASE.Normandy.Bazenville
-- * AIRBASE.Normandy.Sainte_Croix_sur_Mer
-- * AIRBASE.Normandy.Beny_sur_Mer
-- * AIRBASE.Normandy.Rucqueville
-- * AIRBASE.Normandy.Sommervieu
-- * AIRBASE.Normandy.Lantheuil
-- * AIRBASE.Normandy.Evreux
-- * AIRBASE.Normandy.Chailey
-- * AIRBASE.Normandy.Needs_Oar_Point
-- * AIRBASE.Normandy.Funtington
-- * AIRBASE.Normandy.Tangmere
-- * AIRBASE.Normandy.Ford
AIRBASE.Normandy = {
["Saint_Pierre_du_Mont"] = "Saint Pierre du Mont",
["Lignerolles"] = "Lignerolles",
@ -149,6 +231,7 @@ function AIRBASE:Register( AirbaseName )
local self = BASE:Inherit( self, POSITIONABLE:New( AirbaseName ) )
self.AirbaseName = AirbaseName
self.AirbaseZone = ZONE_RADIUS:New( AirbaseName, self:GetVec2(), 2500 )
return self
end
@ -185,5 +268,12 @@ function AIRBASE:GetDCSObject()
return nil
end
--- Get the airbase zone.
-- @param #AIRBASE self
-- @return Core.Zone#ZONE_RADIUS The zone radius of the airbase.
function AIRBASE:GetZone()
return self.AirbaseZone
end

View File

@ -95,6 +95,22 @@
-- * @{#CONTROLLABLE.TaskCondition}: Return a condition section for a controlled task.
-- * @{#CONTROLLABLE.TaskControlled}: Return a Controlled Task taking a Task and a TaskCondition.
--
-- ### Call a function as a Task
--
-- A function can be called which is part of a Task. The method @{#CONTROLLABLE.TaskFunction}() prepares
-- a Task that can call a GLOBAL function from within the Controller execution.
-- This method can also be used to **embed a function call when a certain waypoint has been reached**.
-- See below the **Tasks at Waypoints** section.
--
-- Demonstration Mission: [GRP-502 - Route at waypoint to random point](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/release-2-2-pre/GRP - Group Commands/GRP-502 - Route at waypoint to random point)
--
-- ### Tasks at Waypoints
--
-- Special Task methods are available to set tasks at certain waypoints.
-- The method @{#CONTROLLABLE.SetTaskWaypoint}() helps preparing a Route, embedding a Task at the Waypoint of the Route.
--
-- This creates a Task element, with an action to call a function as part of a Wrapped Task.
--
-- ### Obtain the mission from controllable templates
--
-- Controllable templates contain complete mission descriptions. Sometimes you want to copy a complete mission from a controllable and assign it to another:
@ -108,7 +124,15 @@
-- * @{#CONTROLLABLE.CommandDoScript}: Do Script command.
-- * @{#CONTROLLABLE.CommandSwitchWayPoint}: Perform a switch waypoint command.
--
-- ## CONTROLLABLE Option methods
-- ## Routing of Controllables
--
-- Different routing methods exist to route GROUPs and UNITs to different locations:
--
-- * @{#CONTROLLABLE.Route}(): Make the Controllable to follow a given route.
-- * @{#CONTROLLABLE.RouteGroundTo}(): Make the GROUND Controllable to drive towards a specific coordinate.
-- * @{#CONTROLLABLE.RouteAirTo}(): Make the AIR Controllable to fly towards a specific coordinate.
--
-- ## Option methods
--
-- Controllable **Option methods** change the behaviour of the Controllable while being alive.
--
@ -257,6 +281,16 @@ function CONTROLLABLE:GetLife0()
return nil
end
--- Returns relative amount of fuel (from 0.0 to 1.0) the unit has in its internal tanks.
-- This method returns nil to ensure polymorphic behaviour! This method needs to be overridden by GROUP or UNIT.
-- @param #CONTROLLABLE self
-- @return #nil The CONTROLLABLE is not existing or alive.
function CONTROLLABLE:GetFuel()
self:F( self.ControllableName )
return nil
end
@ -334,16 +368,25 @@ function CONTROLLABLE:SetTask( DCSTask, WaitTime )
if DCSControllable then
local Controller = self:_GetController()
local DCSControllableName = self:GetName()
-- When a controllable SPAWNs, it takes about a second to get the controllable in the simulator. Setting tasks to unspawned controllables provides unexpected results.
-- Therefore we schedule the functions to set the mission and options for the Controllable.
-- Controller.setTask( Controller, DCSTask )
if not WaitTime then
Controller:setTask( DCSTask )
local function SetTask( Controller, DCSTask )
if self and self:IsAlive() then
local Controller = self:_GetController()
Controller:setTask( DCSTask )
else
BASE:E( DCSControllableName .. " is not alive anymore. Cannot set DCSTask " .. DCSTask )
end
end
if not WaitTime or WaitTime == 0 then
SetTask( self, DCSTask )
else
self.TaskScheduler:Schedule( Controller, Controller.setTask, { DCSTask }, WaitTime )
self.TaskScheduler:Schedule( self, SetTask, { DCSTask }, WaitTime )
end
return self
@ -449,11 +492,11 @@ function CONTROLLABLE:TaskWrappedAction( DCSCommand, Index )
self:F2( { DCSCommand } )
local DCSTaskWrappedAction
DCSTaskWrappedAction = {
id = "WrappedAction",
enabled = true,
number = Index,
number = Index or 1,
auto = false,
params = {
action = DCSCommand,
@ -464,6 +507,22 @@ function CONTROLLABLE:TaskWrappedAction( DCSCommand, Index )
return DCSTaskWrappedAction
end
--- Set a Task at a Waypoint using a Route list.
-- @param #CONTROLLABLE self
-- @param #table Waypoint The Waypoint!
-- @param Dcs.DCSTasking.Task#Task Task The Task structure to be executed!
-- @return Dcs.DCSTasking.Task#Task
function CONTROLLABLE:SetTaskWaypoint( Waypoint, Task )
Waypoint.task = self:TaskCombo( { Task } )
self:T3( { Waypoint.task } )
return Waypoint.task
end
--- Executes a command action
-- @param #CONTROLLABLE self
-- @param Dcs.DCSCommand#Command DCSCommand
@ -1480,6 +1539,84 @@ function CONTROLLABLE:TaskEmbarkToTransport( Point, Radius )
return DCSTask
end
--- This creates a Task element, with an action to call a function as part of a Wrapped Task.
-- This Task can then be embedded at a Waypoint by calling the method @{#CONTROLLABLE.SetTaskWaypoint}.
-- @param #CONTROLLABLE self
-- @param #string FunctionString The function name embedded as a string that will be called.
-- @param ... The variable arguments passed to the function when called! These arguments can be of any type!
-- @return #CONTROLLABLE
-- @usage
--
-- local ZoneList = {
-- ZONE:New( "ZONE1" ),
-- ZONE:New( "ZONE2" ),
-- ZONE:New( "ZONE3" ),
-- ZONE:New( "ZONE4" ),
-- ZONE:New( "ZONE5" )
-- }
--
-- GroundGroup = GROUP:FindByName( "Vehicle" )
--
-- --- @param Wrapper.Group#GROUP GroundGroup
-- function RouteToZone( Vehicle, ZoneRoute )
--
-- local Route = {}
--
-- Vehicle:E( { ZoneRoute = ZoneRoute } )
--
-- Vehicle:MessageToAll( "Moving to zone " .. ZoneRoute:GetName(), 10 )
--
-- -- Get the current coordinate of the Vehicle
-- local FromCoord = Vehicle:GetCoordinate()
--
-- -- Select a random Zone and get the Coordinate of the new Zone.
-- local RandomZone = ZoneList[ math.random( 1, #ZoneList ) ] -- Core.Zone#ZONE
-- local ToCoord = RandomZone:GetCoordinate()
--
-- -- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task
-- Route[#Route+1] = FromCoord:WaypointGround( 72 )
-- Route[#Route+1] = ToCoord:WaypointGround( 60, "Vee" )
--
-- local TaskRouteToZone = Vehicle:TaskFunction( "RouteToZone", RandomZone )
--
-- Vehicle:SetTaskWaypoint( Route, #Route, TaskRouteToZone ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
--
-- Vehicle:Route( Route, math.random( 10, 20 ) ) -- Move after a random seconds to the Route. See the Route method for details.
--
-- end
--
-- RouteToZone( GroundGroup, ZoneList[1] )
--
function CONTROLLABLE:TaskFunction( FunctionString, ... )
self:F2( { FunctionString, arg } )
local DCSTask
local DCSScript = {}
DCSScript[#DCSScript+1] = "local MissionControllable = GROUP:Find( ... ) "
if arg and arg.n > 0 then
local ArgumentKey = '_' .. tostring( arg ):match("table: (.*)")
self:SetState( self, ArgumentKey, arg )
DCSScript[#DCSScript+1] = "local Arguments = MissionControllable:GetState( MissionControllable, '" .. ArgumentKey .. "' ) "
--DCSScript[#DCSScript+1] = "MissionControllable:ClearState( MissionControllable, '" .. ArgumentKey .. "' ) "
DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable, unpack( Arguments ) )"
else
DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable )"
end
DCSTask = self:TaskWrappedAction(
self:CommandDoScript(
table.concat( DCSScript )
)
)
self:T( DCSTask )
return DCSTask
end
--- (AIR + GROUND) Return a mission task from a mission template.
@ -1496,6 +1633,140 @@ function CONTROLLABLE:TaskMission( TaskMission )
return DCSTask
end
do -- Patrol methods
--- (GROUND) Patrol iteratively using the waypoints the for the (parent) group.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE
function CONTROLLABLE:PatrolRoute()
local PatrolGroup = self -- Wrapper.Group#GROUP
if not self:IsInstanceOf( "GROUP" ) then
PatrolGroup = self:GetGroup() -- Wrapper.Group#GROUP
end
self:E( { PatrolGroup = PatrolGroup:GetName() } )
if PatrolGroup:IsGround() or PatrolGroup:IsShip() then
local Waypoints = PatrolGroup:GetTemplateRoutePoints()
-- Calculate the new Route.
local FromCoord = PatrolGroup:GetCoordinate()
local From = FromCoord:WaypointGround( 120 )
table.insert( Waypoints, 1, From )
local TaskRoute = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRoute" )
self:E({Waypoints = Waypoints})
local Waypoint = Waypoints[#Waypoints]
PatrolGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
PatrolGroup:Route( Waypoints ) -- Move after a random seconds to the Route. See the Route method for details.
end
end
--- (GROUND) Patrol randomly to the waypoints the for the (parent) group.
-- A random waypoint will be picked and the group will move towards that point.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE
function CONTROLLABLE:PatrolRouteRandom( Speed, Formation, ToWaypoint )
local PatrolGroup = self -- Wrapper.Group#GROUP
if not self:IsInstanceOf( "GROUP" ) then
PatrolGroup = self:GetGroup() -- Wrapper.Group#GROUP
end
self:E( { PatrolGroup = PatrolGroup:GetName() } )
if PatrolGroup:IsGround() or PatrolGroup:IsShip() then
local Waypoints = PatrolGroup:GetTemplateRoutePoints()
-- Calculate the new Route.
local FromCoord = PatrolGroup:GetCoordinate()
local FromWaypoint = 1
if ToWaypoint then
FromWaypoint = ToWaypoint
end
-- Loop until a waypoint has been found that is not the same as the current waypoint.
-- Otherwise the object zon't move or drive in circles and the algorithm would not do exactly
-- what it is supposed to do, which is making groups drive around.
local ToWaypoint
repeat
-- Select a random waypoint and check if it is not the same waypoint as where the object is about.
ToWaypoint = math.random( 1, #Waypoints )
until( ToWaypoint ~= FromWaypoint )
self:E( { FromWaypoint = FromWaypoint, ToWaypoint = ToWaypoint } )
local Waypoint = Waypoints[ToWaypoint] -- Select random waypoint.
local ToCoord = COORDINATE:NewFromVec2( { x = Waypoint.x, y = Waypoint.y } )
-- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task
local Route = {}
Route[#Route+1] = FromCoord:WaypointGround( 0 )
Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation )
local TaskRouteToZone = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRouteRandom", Speed, Formation, ToWaypoint )
PatrolGroup:SetTaskWaypoint( Route[#Route], TaskRouteToZone ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
PatrolGroup:Route( Route, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
end
end
--- (GROUND) Patrol randomly to the waypoints the for the (parent) group.
-- A random waypoint will be picked and the group will move towards that point.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE
function CONTROLLABLE:PatrolZones( ZoneList, Speed, Formation )
if not type( ZoneList ) == "table" then
ZoneList = { ZoneList }
end
local PatrolGroup = self -- Wrapper.Group#GROUP
if not self:IsInstanceOf( "GROUP" ) then
PatrolGroup = self:GetGroup() -- Wrapper.Group#GROUP
end
self:E( { PatrolGroup = PatrolGroup:GetName() } )
if PatrolGroup:IsGround() or PatrolGroup:IsShip() then
local Waypoints = PatrolGroup:GetTemplateRoutePoints()
local Waypoint = Waypoints[math.random( 1, #Waypoints )] -- Select random waypoint.
-- Calculate the new Route.
local FromCoord = PatrolGroup:GetCoordinate()
-- Select a random Zone and get the Coordinate of the new Zone.
local RandomZone = ZoneList[ math.random( 1, #ZoneList ) ] -- Core.Zone#ZONE
local ToCoord = RandomZone:GetRandomCoordinate( 10 )
-- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task
local Route = {}
Route[#Route+1] = FromCoord:WaypointGround( 120 )
Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation )
local TaskRouteToZone = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolZones", ZoneList, Speed, Formation )
PatrolGroup:SetTaskWaypoint( Route[#Route], TaskRouteToZone ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
PatrolGroup:Route( Route, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
end
end
end
--- Return a Misson task to follow a given route defined by Points.
-- @param #CONTROLLABLE self
-- @param #table Points A table of route points.
@ -1620,19 +1891,16 @@ end
--- Make the controllable to follow a given route.
-- @param #CONTROLLABLE self
-- @param #table GoPoints A table of Route Points.
-- @return #CONTROLLABLE self
function CONTROLLABLE:Route( GoPoints )
self:F2( GoPoints )
-- @param #table Route A table of Route Points.
-- @param #number DelaySeconds Wait for the specified seconds before executing the Route.
-- @return #CONTROLLABLE The CONTROLLABLE.
function CONTROLLABLE:Route( Route, DelaySeconds )
self:F2( Route )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local Points = routines.utils.deepCopy( GoPoints )
local MissionTask = { id = 'Mission', params = { route = { points = Points, }, }, }
local Controller = self:_GetController()
--Controller.setTask( Controller, MissionTask )
self.TaskScheduler:Schedule( Controller, Controller.setTask, { MissionTask }, 1 )
local RouteTask = self:TaskRoute( Route ) -- Create a RouteTask, that will route the CONTROLLABLE to the Route.
self:SetTask( RouteTask, DelaySeconds or 1 ) -- Execute the RouteTask after the specified seconds (default is 1).
return self
end
@ -1640,6 +1908,47 @@ function CONTROLLABLE:Route( GoPoints )
end
--- Make the GROUND Controllable to drive towards a specific point.
-- @param #CONTROLLABLE self
-- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to.
-- @param #number Speed (optional) Speed in km/h. The default speed is 999 km/h.
-- @param #string Formation (optional) The route point Formation, which is a text string that specifies exactly the Text in the Type of the route point, like "Vee", "Echelon Right".
-- @param #number DelaySeconds Wait for the specified seconds before executing the Route.
-- @return #CONTROLLABLE The CONTROLLABLE.
function CONTROLLABLE:RouteGroundTo( ToCoordinate, Speed, Formation, DelaySeconds )
local FromCoordinate = self:GetCoordinate()
local FromWP = FromCoordinate:WaypointGround()
local ToWP = ToCoordinate:WaypointGround( Speed, Formation )
self:Route( { FromWP, ToWP }, DelaySeconds )
return self
end
--- Make the AIR Controllable fly towards a specific point.
-- @param #CONTROLLABLE self
-- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to.
-- @param Core.Point#COORDINATE.RoutePointAltType AltType The altitude type.
-- @param Core.Point#COORDINATE.RoutePointType Type The route point type.
-- @param Core.Point#COORDINATE.RoutePointAction Action The route point action.
-- @param #number Speed (optional) Speed in km/h. The default speed is 999 km/h.
-- @param #number DelaySeconds Wait for the specified seconds before executing the Route.
-- @return #CONTROLLABLE The CONTROLLABLE.
function CONTROLLABLE:RouteAirTo( ToCoordinate, AltType, Type, Action, Speed, DelaySeconds )
local FromCoordinate = self:GetCoordinate()
local FromWP = FromCoordinate:WaypointAir()
local ToWP = ToCoordinate:WaypointAir( AltType, Type, Action, Speed )
self:Route( { FromWP, ToWP }, DelaySeconds )
return self
end
--- (AIR + GROUND) Route the controllable to a given zone.
-- The controllable final destination point can be randomized.
@ -2228,6 +2537,72 @@ function CONTROLLABLE:OptionROTVertical()
return nil
end
--- Alarm state to Auto: AI will automatically switch alarm states based on the presence of threats. The AI kind of cheats in this regard.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE self
function CONTROLLABLE:OptionAlarmStateAuto()
self:F2( { self.ControllableName } )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local Controller = self:_GetController()
if self:IsGround() then
Controller:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.AUTO)
elseif self:IsShip() then
Controller:setOption(AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.AUTO)
end
return self
end
return nil
end
--- Alarm state to Green: Group is not combat ready. Sensors are stowed if possible.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE self
function CONTROLLABLE:OptionAlarmStateGreen()
self:F2( { self.ControllableName } )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local Controller = self:_GetController()
if self:IsGround() then
Controller:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.GREEN)
elseif self:IsShip() then
Controller:setOption(AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.GREEN)
end
return self
end
return nil
end
--- Alarm state to Red: Group is combat ready and actively searching for targets.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE self
function CONTROLLABLE:OptionAlarmStateRed()
self:F2( { self.ControllableName } )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local Controller = self:_GetController()
if self:IsGround() then
Controller:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.RED)
elseif self:IsShip() then
Controller:setOption(AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.RED)
end
return self
end
return nil
end
--- Set RTB on bingo fuel.
-- @param #CONTROLLABLE self
@ -2321,37 +2696,11 @@ function CONTROLLABLE:WayPointFunction( WayPoint, WayPointIndex, WayPointFunctio
self:F2( { WayPoint, WayPointIndex, WayPointFunction } )
table.insert( self.WayPoints[WayPoint].task.params.tasks, WayPointIndex )
self.WayPoints[WayPoint].task.params.tasks[WayPointIndex] = self:TaskFunction( WayPoint, WayPointIndex, WayPointFunction, arg )
self.WayPoints[WayPoint].task.params.tasks[WayPointIndex] = self:TaskFunction( WayPointFunction, arg )
return self
end
function CONTROLLABLE:TaskFunction( WayPoint, WayPointIndex, FunctionString, FunctionArguments )
self:F2( { WayPoint, WayPointIndex, FunctionString, FunctionArguments } )
local DCSTask
local DCSScript = {}
DCSScript[#DCSScript+1] = "local MissionControllable = GROUP:Find( ... ) "
if FunctionArguments and #FunctionArguments > 0 then
DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable, " .. table.concat( FunctionArguments, "," ) .. ")"
else
DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable )"
end
DCSTask = self:TaskWrappedAction(
self:CommandDoScript(
table.concat( DCSScript )
), WayPointIndex
)
self:T( DCSTask )
return DCSTask
end
--- Executes the WayPoint plan.
-- The function gets a WayPoint parameter, that you can use to restart the mission at a specific WayPoint.
-- Note that when the WayPoint parameter is used, the new start mission waypoint of the controllable will be 1!
@ -2388,12 +2737,22 @@ function CONTROLLABLE:IsAirPlane()
if DCSObject then
local Category = DCSObject:getDesc().category
self:T( Category )
return Category == Unit.Category.AIRPLANE
end
return nil
end
function CONTROLLABLE:GetSize()
local DCSObject = self:GetDCSObject()
if DCSObject then
return 1
else
return 0
end
end
-- Message APIs

View File

@ -104,15 +104,39 @@ GROUP.Takeoff = {
GROUPTEMPLATE = {}
GROUPTEMPLATE.Takeoff = {
[GROUP.Takeoff.Air] = "Turning Point",
[GROUP.Takeoff.Runway] = "TakeOff",
[GROUP.Takeoff.Hot] = "TakeOffParkingHot",
[GROUP.Takeoff.Cold] = "TakeOffParking",
[GROUP.Takeoff.Air] = { "Turning Point", "Turning Point" },
[GROUP.Takeoff.Runway] = { "TakeOff", "From Runway" },
[GROUP.Takeoff.Hot] = { "TakeOffParkingHot", "From Parking Area Hot" },
[GROUP.Takeoff.Cold] = { "TakeOffParking", "From Parking Area" }
}
--- Create a new GROUP from a DCSGroup
--- Create a new GROUP from a given GroupTemplate as a parameter.
-- Note that the GroupTemplate is NOT spawned into the mission.
-- It is merely added to the @{Database}.
-- @param #GROUP self
-- @param Dcs.DCSWrapper.Group#Group GroupName The DCS Group name
-- @param #table GroupTemplate The GroupTemplate Structure exactly as defined within the mission editor.
-- @param Dcs.DCScoalition#coalition.side CoalitionSide The coalition.side of the group.
-- @param Dcs.DCSGroup#Group.Category CategoryID The Group.Category of the group.
-- @param Dcs.DCScountry#country.id CountryID the country.id of the group.
-- @return #GROUP self
function GROUP:NewTemplate( GroupTemplate, CoalitionSide, CategoryID, CountryID )
local GroupName = GroupTemplate.name
_DATABASE:_RegisterGroupTemplate( GroupTemplate, CategoryID, CountryID, CoalitionSide, GroupName )
self = BASE:Inherit( self, CONTROLLABLE:New( GroupName ) )
self:F2( GroupName )
self.GroupName = GroupName
_DATABASE:AddGroup( GroupName )
self:SetEventPriority( 4 )
return self
end
--- Create a new GROUP from an existing Group in the Mission.
-- @param #GROUP self
-- @param #string GroupName The Group name
-- @return #GROUP self
function GROUP:Register( GroupName )
self = BASE:Inherit( self, CONTROLLABLE:New( GroupName ) )
@ -224,6 +248,7 @@ function GROUP:Destroy()
for Index, UnitData in pairs( DCSGroup:getUnits() ) do
self:CreateEventCrash( timer.getTime(), UnitData )
end
USERFLAG:New( self:GetName() ):Set( 100 )
DCSGroup:destroy()
DCSGroup = nil
end
@ -231,6 +256,7 @@ function GROUP:Destroy()
return nil
end
--- Returns category of the DCS Group.
-- @param #GROUP self
-- @return Dcs.DCSWrapper.Group#Group.Category The category ID
@ -353,8 +379,13 @@ function GROUP:GetSize()
if DCSGroup then
local GroupSize = DCSGroup:getSize()
self:T3( GroupSize )
return GroupSize
if GroupSize then
self:T3( GroupSize )
return GroupSize
else
return 0
end
end
return nil
@ -502,7 +533,6 @@ end
--- Returns a COORDINATE object indicating the point of the first UNIT of the GROUP within the mission.
-- @param Wrapper.Group#GROUP self
-- @return Core.Point#COORDINATE The COORDINATE of the GROUP.
-- @return #nil The POSITIONABLE is not existing or alive.
function GROUP:GetCoordinate()
self:F2( self.PositionableName )
@ -560,6 +590,32 @@ function GROUP:GetHeading()
end
--- Returns relative amount of fuel (from 0.0 to 1.0) the group has in its internal tanks. If there are additional fuel tanks the value may be greater than 1.0.
-- @param #GROUP self
-- @return #number The relative amount of fuel (from 0.0 to 1.0).
-- @return #nil The GROUP is not existing or alive.
function GROUP:GetFuel()
self:F( self.ControllableName )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local GroupSize = self:GetSize()
local TotalFuel = 0
for UnitID, UnitData in pairs( self:GetUnits() ) do
local Unit = UnitData -- Wrapper.Unit#UNIT
local UnitFuel = Unit:GetFuel()
self:F( { Fuel = UnitFuel } )
TotalFuel = TotalFuel + UnitFuel
end
local GroupFuel = TotalFuel / GroupSize
return GroupFuel
end
return 0
end
do -- Is Zone methods
--- Returns true if all units of the group are within a @{Zone}.
@ -569,6 +625,8 @@ do -- Is Zone methods
function GROUP:IsCompletelyInZone( Zone )
self:F2( { self.GroupName, Zone } )
if not self:IsAlive() then return false end
for UnitID, UnitData in pairs( self:GetUnits() ) do
local Unit = UnitData -- Wrapper.Unit#UNIT
if Zone:IsVec3InZone( Unit:GetVec3() ) then
@ -590,6 +648,8 @@ function GROUP:IsPartlyInZone( Zone )
local IsOneUnitInZone = false
local IsOneUnitOutsideZone = false
if not self:IsAlive() then return false end
for UnitID, UnitData in pairs( self:GetUnits() ) do
local Unit = UnitData -- Wrapper.Unit#UNIT
if Zone:IsVec3InZone( Unit:GetVec3() ) then
@ -613,6 +673,8 @@ end
function GROUP:IsNotInZone( Zone )
self:F2( { self.GroupName, Zone } )
if not self:IsAlive() then return true end
for UnitID, UnitData in pairs( self:GetUnits() ) do
local Unit = UnitData -- Wrapper.Unit#UNIT
if Zone:IsVec3InZone( Unit:GetVec3() ) then
@ -631,6 +693,8 @@ function GROUP:CountInZone( Zone )
self:F2( {self.GroupName, Zone} )
local Count = 0
if not self:IsAlive() then return Count end
for UnitID, UnitData in pairs( self:GetUnits() ) do
local Unit = UnitData -- Wrapper.Unit#UNIT
if Zone:IsVec3InZone( Unit:GetVec3() ) then
@ -864,25 +928,28 @@ end
-- @param #table Template The template of the Group retrieved with GROUP:GetTemplate()
function GROUP:Respawn( Template )
local Vec3 = self:GetVec3()
Template.x = Vec3.x
Template.y = Vec3.z
--Template.x = nil
--Template.y = nil
self:E( #Template.units )
for UnitID, UnitData in pairs( self:GetUnits() ) do
local GroupUnit = UnitData -- Wrapper.Unit#UNIT
self:E( GroupUnit:GetName() )
if GroupUnit:IsAlive() then
local GroupUnitVec3 = GroupUnit:GetVec3()
local GroupUnitHeading = GroupUnit:GetHeading()
Template.units[UnitID].alt = GroupUnitVec3.y
Template.units[UnitID].x = GroupUnitVec3.x
Template.units[UnitID].y = GroupUnitVec3.z
Template.units[UnitID].heading = GroupUnitHeading
self:E( { UnitID, Template.units[UnitID], Template.units[UnitID] } )
if self:IsAlive() then
local Vec3 = self:GetVec3()
Template.x = Vec3.x
Template.y = Vec3.z
--Template.x = nil
--Template.y = nil
self:E( #Template.units )
for UnitID, UnitData in pairs( self:GetUnits() ) do
local GroupUnit = UnitData -- Wrapper.Unit#UNIT
self:E( GroupUnit:GetName() )
if GroupUnit:IsAlive() then
local GroupUnitVec3 = GroupUnit:GetVec3()
local GroupUnitHeading = GroupUnit:GetHeading()
Template.units[UnitID].alt = GroupUnitVec3.y
Template.units[UnitID].x = GroupUnitVec3.x
Template.units[UnitID].y = GroupUnitVec3.z
Template.units[UnitID].heading = GroupUnitHeading
self:E( { UnitID, Template.units[UnitID], Template.units[UnitID] } )
end
end
end
self:Destroy()
@ -897,10 +964,19 @@ end
-- @return #table
function GROUP:GetTemplate()
local GroupName = self:GetName()
self:E( GroupName )
return _DATABASE:GetGroupTemplate( GroupName )
return UTILS.DeepCopy( _DATABASE:GetGroupTemplate( GroupName ) )
end
--- Returns the group template route.points[] (the waypoints) from the @{DATABASE} (_DATABASE object).
-- @param #GROUP self
-- @return #table
function GROUP:GetTemplateRoutePoints()
local GroupName = self:GetName()
return UTILS.DeepCopy( _DATABASE:GetGroupTemplate( GroupName ).route.points )
end
--- Sets the controlled status in a Template.
-- @param #GROUP self
-- @param #boolean Controlled true is controlled, false is uncontrolled.
@ -1075,7 +1151,7 @@ do -- Route methods
local PointTo = {}
local AirbasePointVec2 = RTBAirbase:GetPointVec2()
local AirbaseAirPoint = AirbasePointVec2:RoutePointAir(
local AirbaseAirPoint = AirbasePointVec2:WaypointAir(
POINT_VEC3.RoutePointAltType.BARO,
"Land",
"Landing",
@ -1120,9 +1196,9 @@ do -- Event Handling
-- @param Core.Event#EVENTS Event
-- @param #function EventFunction (optional) The function to be called when the event occurs for the GROUP.
-- @return #GROUP
function GROUP:HandleEvent( Event, EventFunction )
function GROUP:HandleEvent( Event, EventFunction, ... )
self:EventDispatcher():OnEventForGroup( self:GetName(), EventFunction, self, Event )
self:EventDispatcher():OnEventForGroup( self:GetName(), EventFunction, self, Event, ... )
return self
end
@ -1133,7 +1209,7 @@ do -- Event Handling
-- @return #GROUP
function GROUP:UnHandleEvent( Event )
self:EventDispatcher():Remove( self, Event )
self:EventDispatcher():RemoveEvent( self, Event )
return self
end
@ -1162,7 +1238,7 @@ do -- Players
-- @return #nil The group has no players
function GROUP:GetPlayerNames()
local PlayerNames = nil
local PlayerNames = {}
local Units = self:GetUnits()
for UnitID, UnitData in pairs( Units ) do
@ -1178,4 +1254,96 @@ do -- Players
return PlayerNames
end
end
end
--do -- Smoke
--
----- Signal a flare at the position of the GROUP.
---- @param #GROUP self
---- @param Utilities.Utils#FLARECOLOR FlareColor
--function GROUP:Flare( FlareColor )
-- self:F2()
-- trigger.action.signalFlare( self:GetVec3(), FlareColor , 0 )
--end
--
----- Signal a white flare at the position of the GROUP.
---- @param #GROUP self
--function GROUP:FlareWhite()
-- self:F2()
-- trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.White , 0 )
--end
--
----- Signal a yellow flare at the position of the GROUP.
---- @param #GROUP self
--function GROUP:FlareYellow()
-- self:F2()
-- trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Yellow , 0 )
--end
--
----- Signal a green flare at the position of the GROUP.
---- @param #GROUP self
--function GROUP:FlareGreen()
-- self:F2()
-- trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Green , 0 )
--end
--
----- Signal a red flare at the position of the GROUP.
---- @param #GROUP self
--function GROUP:FlareRed()
-- self:F2()
-- local Vec3 = self:GetVec3()
-- if Vec3 then
-- trigger.action.signalFlare( Vec3, trigger.flareColor.Red, 0 )
-- end
--end
--
----- Smoke the GROUP.
---- @param #GROUP self
--function GROUP:Smoke( SmokeColor, Range )
-- self:F2()
-- if Range then
-- trigger.action.smoke( self:GetRandomVec3( Range ), SmokeColor )
-- else
-- trigger.action.smoke( self:GetVec3(), SmokeColor )
-- end
--
--end
--
----- Smoke the GROUP Green.
---- @param #GROUP self
--function GROUP:SmokeGreen()
-- self:F2()
-- trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Green )
--end
--
----- Smoke the GROUP Red.
---- @param #GROUP self
--function GROUP:SmokeRed()
-- self:F2()
-- trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Red )
--end
--
----- Smoke the GROUP White.
---- @param #GROUP self
--function GROUP:SmokeWhite()
-- self:F2()
-- trigger.action.smoke( self:GetVec3(), trigger.smokeColor.White )
--end
--
----- Smoke the GROUP Orange.
---- @param #GROUP self
--function GROUP:SmokeOrange()
-- self:F2()
-- trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Orange )
--end
--
----- Smoke the GROUP Blue.
---- @param #GROUP self
--function GROUP:SmokeBlue()
-- self:F2()
-- trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Blue )
--end
--
--
--
--end

View File

@ -83,15 +83,8 @@ end
function IDENTIFIABLE:GetName()
self:F2( self.IdentifiableName )
local DCSIdentifiable = self:GetDCSObject()
if DCSIdentifiable then
local IdentifiableName = self.IdentifiableName
return IdentifiableName
end
self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" )
return nil
local IdentifiableName = self.IdentifiableName
return IdentifiableName
end
@ -166,6 +159,36 @@ function IDENTIFIABLE:GetCoalition()
return nil
end
--- Returns the name of the coalition of the Identifiable.
-- @param #IDENTIFIABLE self
-- @return #string The name of the coalition.
-- @return #nil The DCS Identifiable is not existing or alive.
function IDENTIFIABLE:GetCoalitionName()
self:F2( self.IdentifiableName )
local DCSIdentifiable = self:GetDCSObject()
if DCSIdentifiable then
local IdentifiableCoalition = DCSIdentifiable:getCoalition()
self:T3( IdentifiableCoalition )
if IdentifiableCoalition == coalition.side.BLUE then
return "Blue"
end
if IdentifiableCoalition == coalition.side.RED then
return "Red"
end
if IdentifiableCoalition == coalition.side.NEUTRAL then
return "Neutral"
end
end
self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" )
return nil
end
--- Returns country of the Identifiable.
-- @param #IDENTIFIABLE self
-- @return Dcs.DCScountry#country.id The country identifier.

View File

@ -79,7 +79,7 @@ function OBJECT:Destroy()
local DCSObject = self:GetDCSObject()
if DCSObject then
--BASE:CreateEventCrash( timer.getTime(), DCSObject )
DCSObject:destroy()
end

View File

@ -10,13 +10,11 @@
--
-- @module Positionable
--- The POSITIONABLE class
-- @type POSITIONABLE
--- @type POSITIONABLE.__ Methods which are not intended for mission designers, but which are used interally by the moose designer :-)
-- @extends Wrapper.Identifiable#IDENTIFIABLE
--- @type POSITIONABLE
-- @extends Wrapper.Identifiable#IDENTIFIABLE
-- @field #string PositionableName The name of the measurable.
-- @field Core.Spot#SPOT Spot The laser Spot.
-- @field #number LaserCode The last assigned laser code.
--- # POSITIONABLE class, extends @{Identifiable#IDENTIFIABLE}
@ -49,6 +47,14 @@ POSITIONABLE = {
ClassName = "POSITIONABLE",
PositionableName = "",
}
--- @field #POSITIONABLE.__
POSITIONABLE.__ = {}
--- @field #POSITIONABLE.__.Cargo
POSITIONABLE.__.Cargo = {}
--- A DCSPositionable
-- @type DCSPositionable
-- @field id_ The ID of the controllable in DCS
@ -150,7 +156,6 @@ end
--- Returns a COORDINATE object indicating the point in 3D of the POSITIONABLE within the mission.
-- @param Wrapper.Positionable#POSITIONABLE self
-- @return Core.Point#COORDINATE The COORDINATE of the POSITIONABLE.
-- @return #nil The POSITIONABLE is not existing or alive.
function POSITIONABLE:GetCoordinate()
self:F2( self.PositionableName )
@ -159,8 +164,9 @@ function POSITIONABLE:GetCoordinate()
if DCSPositionable then
local PositionableVec3 = self:GetPositionVec3()
local PositionableCoordinate = POINT_VEC3:NewFromVec3( PositionableVec3 )
local PositionableCoordinate = COORDINATE:NewFromVec3( PositionableVec3 )
PositionableCoordinate:SetHeading( self:GetHeading() )
PositionableCoordinate:SetVelocity( self:GetVelocityMPS() )
self:T2( PositionableCoordinate )
return PositionableCoordinate
@ -320,16 +326,35 @@ function POSITIONABLE:InAir()
return nil
end
--- Returns the POSITIONABLE velocity vector.
--- Returns the a @{Velocity} object from the positionable.
-- @param Wrapper.Positionable#POSITIONABLE self
-- @return Dcs.DCSTypes#Vec3 The velocity vector
-- @return Core.Velocity#VELOCITY Velocity The Velocity object.
-- @return #nil The POSITIONABLE is not existing or alive.
function POSITIONABLE:GetVelocity()
self:F2( self.PositionableName )
local DCSPositionable = self:GetDCSObject()
if DCSPositionable then
local Velocity = VELOCITY:New( self )
return Velocity
end
return nil
end
--- Returns the POSITIONABLE velocity Vec3 vector.
-- @param Wrapper.Positionable#POSITIONABLE self
-- @return Dcs.DCSTypes#Vec3 The velocity Vec3 vector
-- @return #nil The POSITIONABLE is not existing or alive.
function POSITIONABLE:GetVelocityVec3()
self:F2( self.PositionableName )
local DCSPositionable = self:GetDCSObject()
if DCSPositionable then
local PositionableVelocityVec3 = DCSPositionable:getVelocity()
self:T3( PositionableVelocityVec3 )
@ -365,40 +390,38 @@ end
--- Returns the POSITIONABLE velocity in km/h.
-- @param Wrapper.Positionable#POSITIONABLE self
-- @return #number The velocity in km/h
-- @return #nil The POSITIONABLE is not existing or alive.
function POSITIONABLE:GetVelocityKMH()
self:F2( self.PositionableName )
local DCSPositionable = self:GetDCSObject()
if DCSPositionable then
local VelocityVec3 = self:GetVelocity()
local VelocityVec3 = self:GetVelocityVec3()
local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec
local Velocity = Velocity * 3.6 -- now it is in km/h.
self:T3( Velocity )
return Velocity
end
return nil
return 0
end
--- Returns the POSITIONABLE velocity in meters per second.
-- @param Wrapper.Positionable#POSITIONABLE self
-- @return #number The velocity in meters per second.
-- @return #nil The POSITIONABLE is not existing or alive.
function POSITIONABLE:GetVelocityMPS()
self:F2( self.PositionableName )
local DCSPositionable = self:GetDCSObject()
if DCSPositionable then
local VelocityVec3 = self:GetVelocity()
local VelocityVec3 = self:GetVelocityVec3()
local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec
self:T3( Velocity )
return Velocity
end
return nil
return 0
end
@ -412,8 +435,8 @@ function POSITIONABLE:GetMessageText( Message, Name ) --R2.1 added
local DCSObject = self:GetDCSObject()
if DCSObject then
Name = Name and ( " (" .. Name .. ")" ) or ""
local Callsign = string.format( "[%s]", self:GetCallsign() ~= "" and self:GetCallsign() or self:GetName() )
local MessageText = Callsign .. Name .. ": " .. Message
local Callsign = string.format( "%s", self:GetCallsign() ~= "" and self:GetCallsign() or self:GetName() )
local MessageText = string.format("[%s%s]: %s", Callsign, Name, Message )
return MessageText
end
@ -438,6 +461,23 @@ function POSITIONABLE:GetMessage( Message, Duration, Name ) --R2.1 changed calls
return nil
end
--- Returns a message of a specified type with the callsign embedded (if there is one).
-- @param #POSITIONABLE self
-- @param #string Message The message text
-- @param Core.Message#MESSAGE MessageType MessageType The message type.
-- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable.
-- @return Core.Message#MESSAGE
function POSITIONABLE:GetMessageType( Message, MessageType, Name ) -- R2.2 changed callsign and name and using GetMessageText
local DCSObject = self:GetDCSObject()
if DCSObject then
local MessageText = self:GetMessageText( Message, Name )
return MESSAGE:NewType( MessageText, MessageType )
end
return nil
end
--- Send a message to all coalitions.
-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message.
-- @param #POSITIONABLE self
@ -468,12 +508,6 @@ function POSITIONABLE:MessageToCoalition( Message, Duration, MessageCoalition )
local DCSObject = self:GetDCSObject()
if DCSObject then
if MessageCoalition == coalition.side.BLUE then
Name = "Blue coalition"
end
if MessageCoalition == coalition.side.RED then
Name = "Red coalition"
end
self:GetMessage( Message, Duration, Name ):ToCoalition( MessageCoalition )
end
@ -481,6 +515,26 @@ function POSITIONABLE:MessageToCoalition( Message, Duration, MessageCoalition )
end
--- Send a message to a coalition.
-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message.
-- @param #POSITIONABLE self
-- @param #string Message The message text
-- @param Core.Message#MESSAGE.Type MessageType The message type that determines the duration.
-- @param Dcs.DCScoalition#coalition MessageCoalition The Coalition receiving the message.
function POSITIONABLE:MessageTypeToCoalition( Message, MessageType, MessageCoalition )
self:F2( { Message, MessageType } )
local Name = ""
local DCSObject = self:GetDCSObject()
if DCSObject then
self:GetMessageType( Message, MessageType, Name ):ToCoalition( MessageCoalition )
end
return nil
end
--- Send a message to the red coalition.
-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message.
-- @param #POSITIONABLE self
@ -553,6 +607,26 @@ function POSITIONABLE:MessageToGroup( Message, Duration, MessageGroup, Name )
return nil
end
--- Send a message of a message type to a @{Group}.
-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message.
-- @param #POSITIONABLE self
-- @param #string Message The message text
-- @param Core.Message#MESSAGE.Type MessageType The message type that determines the duration.
-- @param Wrapper.Group#GROUP MessageGroup The GROUP object receiving the message.
-- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable.
function POSITIONABLE:MessageTypeToGroup( Message, MessageType, MessageGroup, Name )
self:F2( { Message, MessageType } )
local DCSObject = self:GetDCSObject()
if DCSObject then
if DCSObject:isExist() then
self:GetMessageType( Message, MessageType, Name ):ToGroup( MessageGroup )
end
end
return nil
end
--- Send a message to a @{Set#SET_GROUP}.
-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message.
-- @param #POSITIONABLE self
@ -679,5 +753,139 @@ function POSITIONABLE:GetLaserCode() --R2.1
return self.LaserCode
end
--- Add cargo.
-- @param #POSITIONABLE self
-- @param Core.Cargo#CARGO Cargo
-- @return #POSITIONABLE
function POSITIONABLE:AddCargo( Cargo )
self.__.Cargo[Cargo] = Cargo
return self
end
--- Remove cargo.
-- @param #POSITIONABLE self
-- @param Core.Cargo#CARGO Cargo
-- @return #POSITIONABLE
function POSITIONABLE:RemoveCargo( Cargo )
self.__.Cargo[Cargo] = nil
return self
end
--- Returns if carrier has given cargo.
-- @param #POSITIONABLE self
-- @return Core.Cargo#CARGO Cargo
function POSITIONABLE:HasCargo( Cargo )
return self.__.Cargo[Cargo]
end
--- Clear all cargo.
-- @param #POSITIONABLE self
function POSITIONABLE:ClearCargo()
self.__.Cargo = {}
end
--- Get cargo item count.
-- @param #POSITIONABLE self
-- @return Core.Cargo#CARGO Cargo
function POSITIONABLE:CargoItemCount()
local ItemCount = 0
for CargoName, Cargo in pairs( self.__.Cargo ) do
ItemCount = ItemCount + Cargo:GetCount()
end
return ItemCount
end
--- Signal a flare at the position of the POSITIONABLE.
-- @param #POSITIONABLE self
-- @param Utilities.Utils#FLARECOLOR FlareColor
function POSITIONABLE:Flare( FlareColor )
self:F2()
trigger.action.signalFlare( self:GetVec3(), FlareColor , 0 )
end
--- Signal a white flare at the position of the POSITIONABLE.
-- @param #POSITIONABLE self
function POSITIONABLE:FlareWhite()
self:F2()
trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.White , 0 )
end
--- Signal a yellow flare at the position of the POSITIONABLE.
-- @param #POSITIONABLE self
function POSITIONABLE:FlareYellow()
self:F2()
trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Yellow , 0 )
end
--- Signal a green flare at the position of the POSITIONABLE.
-- @param #POSITIONABLE self
function POSITIONABLE:FlareGreen()
self:F2()
trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Green , 0 )
end
--- Signal a red flare at the position of the POSITIONABLE.
-- @param #POSITIONABLE self
function POSITIONABLE:FlareRed()
self:F2()
local Vec3 = self:GetVec3()
if Vec3 then
trigger.action.signalFlare( Vec3, trigger.flareColor.Red, 0 )
end
end
--- Smoke the POSITIONABLE.
-- @param #POSITIONABLE self
-- @param Utilities.Utils#SMOKECOLOR SmokeColor The color to smoke to positionable.
-- @param #number Range The range in meters to randomize the smoking around the positionable.
-- @param #number AddHeight The height in meters to add to the altitude of the positionable.
function POSITIONABLE:Smoke( SmokeColor, Range, AddHeight )
self:F2()
if Range then
local Vec3 = self:GetRandomVec3( Range )
Vec3.y = Vec3.y + AddHeight or 0
trigger.action.smoke( Vec3, SmokeColor )
else
local Vec3 = self:GetVec3()
Vec3.y = Vec3.y + AddHeight or 0
trigger.action.smoke( self:GetVec3(), SmokeColor )
end
end
--- Smoke the POSITIONABLE Green.
-- @param #POSITIONABLE self
function POSITIONABLE:SmokeGreen()
self:F2()
trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Green )
end
--- Smoke the POSITIONABLE Red.
-- @param #POSITIONABLE self
function POSITIONABLE:SmokeRed()
self:F2()
trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Red )
end
--- Smoke the POSITIONABLE White.
-- @param #POSITIONABLE self
function POSITIONABLE:SmokeWhite()
self:F2()
trigger.action.smoke( self:GetVec3(), trigger.smokeColor.White )
end
--- Smoke the POSITIONABLE Orange.
-- @param #POSITIONABLE self
function POSITIONABLE:SmokeOrange()
self:F2()
trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Orange )
end
--- Smoke the POSITIONABLE Blue.
-- @param #POSITIONABLE self
function POSITIONABLE:SmokeBlue()
self:F2()
trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Blue )
end

View File

@ -61,7 +61,6 @@ function STATIC:FindByName( StaticName, RaiseError )
if StaticFound then
StaticFound:F3( { StaticName } )
return StaticFound
end
@ -92,4 +91,18 @@ end
function STATIC:GetThreatLevel()
return 1, "Static"
end
end
--- Respawn the @{Unit} using a (tweaked) template of the parent Group.
-- @param #UNIT self
-- @param Core.Point#COORDINATE Coordinate The coordinate where to spawn the new Static.
-- @param #number Heading The heading of the unit respawn.
function STATIC:ReSpawn( Coordinate, Heading )
-- todo: need to fix country
local SpawnStatic = SPAWNSTATIC:NewFromStatic( self.StaticName, country.id.USA )
SpawnStatic:SpawnFromPointVec2( Coordinate, Heading, self.StaticName )
end

View File

@ -159,6 +159,27 @@ function UNIT:GetDCSObject()
return nil
end
--- Destroys the UNIT.
-- @param #UNIT self
-- @return #nil The DCS Unit is not existing or alive.
function UNIT:Destroy()
self:F2( self.ObjectName )
local DCSObject = self:GetDCSObject()
if DCSObject then
local UnitGroup = self:GetGroup()
local UnitGroupName = UnitGroup:GetName()
self:F( { UnitGroupName = UnitGroupName } )
USERFLAG:New( UnitGroupName ):Set( 100 )
--BASE:CreateEventCrash( timer.getTime(), DCSObject )
DCSObject:destroy()
end
return nil
end
--- Respawn the @{Unit} using a (tweaked) template of the parent Group.
--
-- This function will:
@ -486,12 +507,12 @@ function UNIT:GetRadar()
return nil, nil
end
--- Returns relative amount of fuel (from 0.0 to 1.0) the unit has in its internal tanks. If there are additional fuel tanks the value may be greater than 1.0.
--- Returns relative amount of fuel (from 0.0 to 1.0) the UNIT has in its internal tanks. If there are additional fuel tanks the value may be greater than 1.0.
-- @param #UNIT self
-- @return #number The relative amount of fuel (from 0.0 to 1.0).
-- @return #nil The DCS Unit is not existing or alive.
function UNIT:GetFuel()
self:F2( self.UnitName )
self:F( self.UnitName )
local DCSUnit = self:GetDCSObject()
@ -536,7 +557,7 @@ function UNIT:GetLife()
return UnitLife
end
return nil
return -1
end
--- Returns the Unit's initial health.
@ -553,7 +574,7 @@ function UNIT:GetLife0()
return UnitLife0
end
return nil
return 0
end
--- Returns the category name of the #UNIT.
@ -598,122 +619,128 @@ end
-- @param #UNIT self
function UNIT:GetThreatLevel()
local Attributes = self:GetDesc().attributes
self:T( Attributes )
local ThreatLevel = 0
local ThreatText = ""
if self:IsGround() then
local Descriptor = self:GetDesc()
self:T( "Ground" )
if Descriptor then
local ThreatLevels = {
"Unarmed",
"Infantry",
"Old Tanks & APCs",
"Tanks & IFVs without ATGM",
"Tanks & IFV with ATGM",
"Modern Tanks",
"AAA",
"IR Guided SAMs",
"SR SAMs",
"MR SAMs",
"LR SAMs"
}
local Attributes = Descriptor.attributes
self:T( Attributes )
if self:IsGround() then
self:T( "Ground" )
if Attributes["LR SAM"] then ThreatLevel = 10
elseif Attributes["MR SAM"] then ThreatLevel = 9
elseif Attributes["SR SAM"] and
not Attributes["IR Guided SAM"] then ThreatLevel = 8
elseif ( Attributes["SR SAM"] or Attributes["MANPADS"] ) and
Attributes["IR Guided SAM"] then ThreatLevel = 7
elseif Attributes["AAA"] then ThreatLevel = 6
elseif Attributes["Modern Tanks"] then ThreatLevel = 5
elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and
Attributes["ATGM"] then ThreatLevel = 4
elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and
not Attributes["ATGM"] then ThreatLevel = 3
elseif Attributes["Old Tanks"] or Attributes["APC"] or Attributes["Artillery"] then ThreatLevel = 2
elseif Attributes["Infantry"] then ThreatLevel = 1
local ThreatLevels = {
"Unarmed",
"Infantry",
"Old Tanks & APCs",
"Tanks & IFVs without ATGM",
"Tanks & IFV with ATGM",
"Modern Tanks",
"AAA",
"IR Guided SAMs",
"SR SAMs",
"MR SAMs",
"LR SAMs"
}
if Attributes["LR SAM"] then ThreatLevel = 10
elseif Attributes["MR SAM"] then ThreatLevel = 9
elseif Attributes["SR SAM"] and
not Attributes["IR Guided SAM"] then ThreatLevel = 8
elseif ( Attributes["SR SAM"] or Attributes["MANPADS"] ) and
Attributes["IR Guided SAM"] then ThreatLevel = 7
elseif Attributes["AAA"] then ThreatLevel = 6
elseif Attributes["Modern Tanks"] then ThreatLevel = 5
elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and
Attributes["ATGM"] then ThreatLevel = 4
elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and
not Attributes["ATGM"] then ThreatLevel = 3
elseif Attributes["Old Tanks"] or Attributes["APC"] or Attributes["Artillery"] then ThreatLevel = 2
elseif Attributes["Infantry"] then ThreatLevel = 1
end
ThreatText = ThreatLevels[ThreatLevel+1]
end
ThreatText = ThreatLevels[ThreatLevel+1]
end
if self:IsAir() then
self:T( "Air" )
local ThreatLevels = {
"Unarmed",
"Tanker",
"AWACS",
"Transport Helicopter",
"UAV",
"Bomber",
"Strategic Bomber",
"Attack Helicopter",
"Battleplane",
"Multirole Fighter",
"Fighter"
}
if self:IsAir() then
if Attributes["Fighters"] then ThreatLevel = 10
elseif Attributes["Multirole fighters"] then ThreatLevel = 9
elseif Attributes["Battleplanes"] then ThreatLevel = 8
elseif Attributes["Attack helicopters"] then ThreatLevel = 7
elseif Attributes["Strategic bombers"] then ThreatLevel = 6
elseif Attributes["Bombers"] then ThreatLevel = 5
elseif Attributes["UAVs"] then ThreatLevel = 4
elseif Attributes["Transport helicopters"] then ThreatLevel = 3
elseif Attributes["AWACS"] then ThreatLevel = 2
elseif Attributes["Tankers"] then ThreatLevel = 1
self:T( "Air" )
local ThreatLevels = {
"Unarmed",
"Tanker",
"AWACS",
"Transport Helicopter",
"UAV",
"Bomber",
"Strategic Bomber",
"Attack Helicopter",
"Battleplane",
"Multirole Fighter",
"Fighter"
}
if Attributes["Fighters"] then ThreatLevel = 10
elseif Attributes["Multirole fighters"] then ThreatLevel = 9
elseif Attributes["Battleplanes"] then ThreatLevel = 8
elseif Attributes["Attack helicopters"] then ThreatLevel = 7
elseif Attributes["Strategic bombers"] then ThreatLevel = 6
elseif Attributes["Bombers"] then ThreatLevel = 5
elseif Attributes["UAVs"] then ThreatLevel = 4
elseif Attributes["Transport helicopters"] then ThreatLevel = 3
elseif Attributes["AWACS"] then ThreatLevel = 2
elseif Attributes["Tankers"] then ThreatLevel = 1
end
ThreatText = ThreatLevels[ThreatLevel+1]
end
ThreatText = ThreatLevels[ThreatLevel+1]
end
if self:IsShip() then
self:T( "Ship" )
--["Aircraft Carriers"] = {"Heavy armed ships",},
--["Cruisers"] = {"Heavy armed ships",},
--["Destroyers"] = {"Heavy armed ships",},
--["Frigates"] = {"Heavy armed ships",},
--["Corvettes"] = {"Heavy armed ships",},
--["Heavy armed ships"] = {"Armed ships", "Armed Air Defence", "HeavyArmoredUnits",},
--["Light armed ships"] = {"Armed ships","NonArmoredUnits"},
--["Armed ships"] = {"Ships"},
--["Unarmed ships"] = {"Ships","HeavyArmoredUnits",},
local ThreatLevels = {
"Unarmed ship",
"Light armed ships",
"Corvettes",
"",
"Frigates",
"",
"Cruiser",
"",
"Destroyer",
"",
"Aircraft Carrier"
}
if self:IsShip() then
self:T( "Ship" )
--["Aircraft Carriers"] = {"Heavy armed ships",},
--["Cruisers"] = {"Heavy armed ships",},
--["Destroyers"] = {"Heavy armed ships",},
--["Frigates"] = {"Heavy armed ships",},
--["Corvettes"] = {"Heavy armed ships",},
--["Heavy armed ships"] = {"Armed ships", "Armed Air Defence", "HeavyArmoredUnits",},
--["Light armed ships"] = {"Armed ships","NonArmoredUnits"},
--["Armed ships"] = {"Ships"},
--["Unarmed ships"] = {"Ships","HeavyArmoredUnits",},
if Attributes["Aircraft Carriers"] then ThreatLevel = 10
elseif Attributes["Destroyers"] then ThreatLevel = 8
elseif Attributes["Cruisers"] then ThreatLevel = 6
elseif Attributes["Frigates"] then ThreatLevel = 4
elseif Attributes["Corvettes"] then ThreatLevel = 2
elseif Attributes["Light armed ships"] then ThreatLevel = 1
local ThreatLevels = {
"Unarmed ship",
"Light armed ships",
"Corvettes",
"",
"Frigates",
"",
"Cruiser",
"",
"Destroyer",
"",
"Aircraft Carrier"
}
if Attributes["Aircraft Carriers"] then ThreatLevel = 10
elseif Attributes["Destroyers"] then ThreatLevel = 8
elseif Attributes["Cruisers"] then ThreatLevel = 6
elseif Attributes["Frigates"] then ThreatLevel = 4
elseif Attributes["Corvettes"] then ThreatLevel = 2
elseif Attributes["Light armed ships"] then ThreatLevel = 1
end
ThreatText = ThreatLevels[ThreatLevel+1]
end
ThreatText = ThreatLevels[ThreatLevel+1]
end
self:T2( ThreatLevel )
@ -734,10 +761,9 @@ function UNIT:IsInZone( Zone )
if self:IsAlive() then
local IsInZone = Zone:IsVec3InZone( self:GetVec3() )
self:T2( { IsInZone } )
self:E( { Unit = self.UnitName, IsInZone = IsInZone } )
return IsInZone
end
return false
end
@ -788,92 +814,6 @@ end
--- Signal a flare at the position of the UNIT.
-- @param #UNIT self
-- @param Utilities.Utils#FLARECOLOR FlareColor
function UNIT:Flare( FlareColor )
self:F2()
trigger.action.signalFlare( self:GetVec3(), FlareColor , 0 )
end
--- Signal a white flare at the position of the UNIT.
-- @param #UNIT self
function UNIT:FlareWhite()
self:F2()
trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.White , 0 )
end
--- Signal a yellow flare at the position of the UNIT.
-- @param #UNIT self
function UNIT:FlareYellow()
self:F2()
trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Yellow , 0 )
end
--- Signal a green flare at the position of the UNIT.
-- @param #UNIT self
function UNIT:FlareGreen()
self:F2()
trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Green , 0 )
end
--- Signal a red flare at the position of the UNIT.
-- @param #UNIT self
function UNIT:FlareRed()
self:F2()
local Vec3 = self:GetVec3()
if Vec3 then
trigger.action.signalFlare( Vec3, trigger.flareColor.Red, 0 )
end
end
--- Smoke the UNIT.
-- @param #UNIT self
function UNIT:Smoke( SmokeColor, Range )
self:F2()
if Range then
trigger.action.smoke( self:GetRandomVec3( Range ), SmokeColor )
else
trigger.action.smoke( self:GetVec3(), SmokeColor )
end
end
--- Smoke the UNIT Green.
-- @param #UNIT self
function UNIT:SmokeGreen()
self:F2()
trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Green )
end
--- Smoke the UNIT Red.
-- @param #UNIT self
function UNIT:SmokeRed()
self:F2()
trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Red )
end
--- Smoke the UNIT White.
-- @param #UNIT self
function UNIT:SmokeWhite()
self:F2()
trigger.action.smoke( self:GetVec3(), trigger.smokeColor.White )
end
--- Smoke the UNIT Orange.
-- @param #UNIT self
function UNIT:SmokeOrange()
self:F2()
trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Orange )
end
--- Smoke the UNIT Blue.
-- @param #UNIT self
function UNIT:SmokeBlue()
self:F2()
trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Blue )
end
-- Is methods

View File

@ -2,6 +2,9 @@ Utilities/Routines.lua
Utilities/Utils.lua
Core/Base.lua
Core/UserFlag.lua
Core/UserSound.lua
Core/Report.lua
Core/Scheduler.lua
Core/ScheduleDispatcher.lua
Core/Event.lua
@ -11,10 +14,13 @@ Core/Zone.lua
Core/Database.lua
Core/Set.lua
Core/Point.lua
Core/Velocity.lua
Core/Message.lua
Core/Fsm.lua
Core/Radio.lua
Core/Spawn.lua
Core/SpawnStatic.lua
Core/Goal.lua
Core/Cargo.lua
Core/Spot.lua
@ -31,14 +37,17 @@ Wrapper/Scenery.lua
Functional/Scoring.lua
Functional/CleanUp.lua
Functional/Spawn.lua
Functional/Movement.lua
Functional/Sead.lua
Functional/Escort.lua
Functional/MissileTrainer.lua
Functional/AirbasePolice.lua
Functional/ATC_Ground.lua
Functional/Detection.lua
Functional/Designate.lua
Functional/RAT.lua
Functional/ZoneGoal.lua
Functional/ZoneGoalCoalition.lua
Functional/ZoneCaptureCoalition.lua
AI/AI_Balancer.lua
AI/AI_A2A.lua
@ -66,6 +75,7 @@ Tasking/Task_A2G.lua
Tasking/Task_A2A_Dispatcher.lua
Tasking/Task_A2A.lua
Tasking/Task_Cargo.lua
Tasking/TaskZoneCapture.lua
Moose.lua
Mission.lua

View File

@ -0,0 +1,93 @@
env.info('*** MOOSE DYNAMIC INCLUDE START *** ')
env.info('Moose Generation Timestamp: 20171031_0744')
local base=_G
__Moose={}
__Moose.Include=function(IncludeFile)
if not __Moose.Includes[IncludeFile]then
__Moose.Includes[IncludeFile]=IncludeFile
local f=assert(base.loadfile(__Moose.ProgramPath..IncludeFile))
if f==nil then
error("Moose: Could not load Moose file "..IncludeFile)
else
env.info("Moose: "..IncludeFile.." dynamically loaded from "..__Moose.ProgramPath)
return f()
end
end
end
__Moose.ProgramPath="Scripts/Moose/"
__Moose.Includes={}
__Moose.Include('Utilities/Routines.lua')
__Moose.Include('Utilities/Utils.lua')
__Moose.Include('Core/Base.lua')
__Moose.Include('Core/UserFlag.lua')
__Moose.Include('Core/UserSound.lua')
__Moose.Include('Core/Report.lua')
__Moose.Include('Core/Scheduler.lua')
__Moose.Include('Core/ScheduleDispatcher.lua')
__Moose.Include('Core/Event.lua')
__Moose.Include('Core/Settings.lua')
__Moose.Include('Core/Menu.lua')
__Moose.Include('Core/Zone.lua')
__Moose.Include('Core/Database.lua')
__Moose.Include('Core/Set.lua')
__Moose.Include('Core/Point.lua')
__Moose.Include('Core/Velocity.lua')
__Moose.Include('Core/Message.lua')
__Moose.Include('Core/Fsm.lua')
__Moose.Include('Core/Radio.lua')
__Moose.Include('Core/Spawn.lua')
__Moose.Include('Core/SpawnStatic.lua')
__Moose.Include('Core/Goal.lua')
__Moose.Include('Core/Cargo.lua')
__Moose.Include('Core/Spot.lua')
__Moose.Include('Wrapper/Object.lua')
__Moose.Include('Wrapper/Identifiable.lua')
__Moose.Include('Wrapper/Positionable.lua')
__Moose.Include('Wrapper/Controllable.lua')
__Moose.Include('Wrapper/Group.lua')
__Moose.Include('Wrapper/Unit.lua')
__Moose.Include('Wrapper/Client.lua')
__Moose.Include('Wrapper/Static.lua')
__Moose.Include('Wrapper/Airbase.lua')
__Moose.Include('Wrapper/Scenery.lua')
__Moose.Include('Functional/Scoring.lua')
__Moose.Include('Functional/CleanUp.lua')
__Moose.Include('Functional/Movement.lua')
__Moose.Include('Functional/Sead.lua')
__Moose.Include('Functional/Escort.lua')
__Moose.Include('Functional/MissileTrainer.lua')
__Moose.Include('Functional/ATC_Ground.lua')
__Moose.Include('Functional/Detection.lua')
__Moose.Include('Functional/Designate.lua')
__Moose.Include('Functional/RAT.lua')
__Moose.Include('Functional/ZoneGoal.lua')
__Moose.Include('Functional/ZoneGoalCoalition.lua')
__Moose.Include('Functional/ZoneCaptureCoalition.lua')
__Moose.Include('AI/AI_Balancer.lua')
__Moose.Include('AI/AI_A2A.lua')
__Moose.Include('AI/AI_A2A_Patrol.lua')
__Moose.Include('AI/AI_A2A_Cap.lua')
__Moose.Include('AI/AI_A2A_Gci.lua')
__Moose.Include('AI/AI_A2A_Dispatcher.lua')
__Moose.Include('AI/AI_Patrol.lua')
__Moose.Include('AI/AI_Cap.lua')
__Moose.Include('AI/AI_Cas.lua')
__Moose.Include('AI/AI_Bai.lua')
__Moose.Include('AI/AI_Formation.lua')
__Moose.Include('Actions/Act_Assign.lua')
__Moose.Include('Actions/Act_Route.lua')
__Moose.Include('Actions/Act_Account.lua')
__Moose.Include('Actions/Act_Assist.lua')
__Moose.Include('Tasking/CommandCenter.lua')
__Moose.Include('Tasking/Mission.lua')
__Moose.Include('Tasking/Task.lua')
__Moose.Include('Tasking/DetectionManager.lua')
__Moose.Include('Tasking/Task_A2G_Dispatcher.lua')
__Moose.Include('Tasking/Task_A2G.lua')
__Moose.Include('Tasking/Task_A2A_Dispatcher.lua')
__Moose.Include('Tasking/Task_A2A.lua')
__Moose.Include('Tasking/Task_Cargo.lua')
__Moose.Include('Tasking/TaskZoneCapture.lua')
__Moose.Include('Moose.lua')
BASE:TraceOnOff(true)
env.info('*** MOOSE INCLUDE END *** ')

View File

@ -1,362 +0,0 @@
[SIZE=7]MOOSE Release 2.1.0[/SIZE]
Finally it is here, release 2.1.0 of MOOSE!
It took some time to prepare this release, as it was a lot of work to get the building blocks of the framework developed and tested. You'll find in this release a lot of new features as well as a couple of important bug fixes.
Release 2.1.0 is now published into the [B]master-release-2.1[/B] branch of this repository on github.
You can download the file moose.lua below to use MOOSE in your missions.
The moose.lua file is also located [URL="https://github.com/FlightControl-Master/MOOSE/blob/master-release-2.1/Moose%20Mission%20Setup/Moose.lua"]here[/URL] in the [B]master-release-2.1[/B] branch.
Those who are using the [B]master[/B] branch can continue to beta test, as new bleeding edge features will be added soon in preparation for release 2.2.0! There are many topics on the agenda to be added.
[B]This release would not have been possible without the help and contribution of many members of this community. THANK YOU![/B]
[SIZE=6]In summary:[/SIZE]
This release brings you [B]an improved tasking mechanism[/B].
Tasking is the system in MOOSE that allows to:
* Execute [B]co-op[/B] missions and tasks
* [B]Detect[/B] targets dynamically
* Define new tasks [B]dynamically[/B]
* Execute the tasks
* Complete the mission [B]goals[/B]
* Extensive menu system and briefings/reports for [B]player interaction[/B]
* Improved Scoring of mission goal achievements, and task achievements.
On top, release brings you new functionality by the introduction of new classes to:
* [B]Designate targets[/B] (lase, smoke or illuminate targets) by AI, assisting your attack. Allows to drop laser guides bombs.
* A new [B]tasking[/B] system to [B]transport cargo[/B] of various types
* Dynamically [B]spawn static objects[/B]
* Improved [B]coordinate system[/B]
* Build [B]large formations[/B], like bombers flying to a target area
[SIZE=6]1. TASKING SYSTEM![/SIZE]
A lot of work has been done in improving the tasking framework within MOOSE.
**The tasking system comes with TASK DISPATCHING mechanisms, that DYNAMICALLY
allocate new tasks based on the tactical or strategical situation in the mission!!!
These tasks can then be engaged upon by the players!!!**
The [URL="http://flightcontrol-master.github.io/MOOSE/Documentation/Task_A2G_Dispatcher.html"]TASK_A2G_DISPATCHER[/URL] class implements the dynamic dispatching of tasks upon groups of detected units determined a Set of FAC (groups). The FAC will detect units, will group them, and will dispatch Tasks to groups of players. Depending on the type of target detected, different tasks will be dispatched. Find a summary below describing for which situation a task type is created:
* [B]CAS Task[/B]: Is created when there are enemy ground units within range of the FAC, while there are friendly units in the FAC perimeter.
* [B]BAI Task[/B]: Is created when there are enemy ground units within range of the FAC, while there are NO other friendly units within the FAC perimeter.
* [B]SEAD Task[/B]: Is created when there are enemy ground units wihtin range of the FAC, with air search radars.
More TASK_... dispatcher classes are to come in the future, like A2A, G2G, etc...
Improvements on the TASKING are in summary:
* A COMMANDCENTER has a dedicated menu.
* A MISSION has a dedicated menu system.
* A MISSION has a briefing report.
* A MISSION has dedicated status reports.
* A MISSION has for each TASK TYPE a menu.
* A MISSION has for each TASK TYPE a dedicated menu system for each TASK defined.
* A MISSION has an "assigned" task menu that contains menu actions relevant to the assigned task.
* A TASK (of various types) has a dedicated menu system.
* A TASK has a briefing report.
* A TASK has dedicated status reports.
* Player reports can be retrieved that explain which player is at which task.
* ...
TASKING is vast, and at the moment there is too much to explain.
[B]The best way to explore the TASKING is to TRY it...[/B]
I suggest you have a look at the [URL="https://www.youtube.com/watch?v=v2Us8SS1-44&t=1070s"]GORI Valley Mission - Iteration 3[/URL].
Many people have contributed in the testing of the mechanism, especially:
@baluballa, @doom, @whiplash
[SIZE=6]2. New MOOSE classes have been added.[/SIZE]
MOOSE 2.1.0 comes with new classes that extends the functionality of the MOOSE framework and allow you to do new things in your missions:
[SIZE=5]2.1. Target designation by laser, smoke or illumination.[/SIZE]
[URL="http://flightcontrol-master.github.io/MOOSE/Documentation/Designate.html"]DESIGNATE[/URL] is orchestrating the designation of potential targets executed by a Recce group,
and communicates these to a dedicated attacking group of players,
so that following a dynamically generated menu system,
each detected set of potential targets can be lased or smoked...
Targets can be:
* [B]Lased[/B] for a period of time.
* [B]Smoked[/B]. Artillery or airplanes with Illuminatino ordonance need to be present. (WIP, but early demo ready.)
* [B]Illuminated[/B] through an illumination bomb. Artillery or airplanes with Illuminatino ordonance need to be present. (WIP, but early demo ready.
This class was made with the help of @EasyEB and many others.
[URL="https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0dQ9UKQMb7YL8z2sKSqemH"]DESIGNATE is demonstrated on youtube[/URL]
DESIGNATE demonstration missions:
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/DES%20-%20Designation"]DES - Designation[/URL]
[SIZE=5]2.2. Transport cargo of different types to various locations as a human task within a mission.[/SIZE]
The Moose framework provides various CARGO classes that allow DCS physical or logical objects to be transported or sling loaded by Carriers.
The CARGO_ classes, as part of the moose core, are able to Board, Load, UnBoard and UnLoad cargo between Carrier units.
This collection of classes in this module define tasks for human players to handle these cargo objects.
Cargo can be transported, picked-up, deployed and sling-loaded from and to other places.
[URL="http://flightcontrol-master.github.io/MOOSE/Documentation/Task_Cargo.html#TASK_CARGO_TRANSPORT"]TASK_CARGO_TRANSPORT[/URL] defines a task for a human player to transport a set of cargo between various zones.
It is the first class that forms part of the TASK_CARGO classes suite.
The TASK_CARGO classes provide you with a flexible tasking sytem,
that allows you to transport cargo of various types between various locations
and various dedicated deployment zones.
A human player can join the battle field in a client airborne slot or a ground vehicle within the CA module (ALT-J).
The player needs to accept the task from the task overview list within the mission, using the radio menus.
Once the TASK_CARGO_TRANSPORT is assigned to the player and accepted by the player, the player will obtain
an extra [B]Cargo Handling Radio Menu[/B] that contains the CARGO objects that need to be transported.
Cargo can be transported towards different [B]Deployment Zones[/B], but can also be deployed anywhere within the battle field.
The Cargo Handling Radio Menu system allows to execute [B]various actions[/B] to handle the cargo.
In the menu, you'll find for each CARGO, that is part of the scope of the task, various actions that can be completed.
Depending on the location of your Carrier unit, the menu options will vary.
The [URL="http://flightcontrol-master.github.io/MOOSE/Documentation/Cargo.html#CARGO_GROUP"]CARGO_GROUP[/URL] class defines a
cargo that is represented by a GROUP object within the simulator, and can be transported by a carrier.
The [URL="http://flightcontrol-master.github.io/MOOSE/Documentation/Cargo.html#CARGO_UNIT"]CARGO_UNIT[/URL] class defines a
cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier.
Mission designers can use the [URL="http://flightcontrol-master.github.io/MOOSE/Documentation/Set.html#SET_CARGO"]SET_CARGO[/URL]
class to build sets of cargos.
Note 1: [B]Various other CARGO classes are defined and are WIP[/B].
Now that the foundation for Cargo handling is getting form, future releases will bring other types of CARGO handling
classes to the MOOSE framework quickly. Sling-loading, package, beacon and other types of CARGO will be released soon.
Note 2: [B]AI_CARGO has been renamed to CARGO and now forms part of the Core or MOOSE[/B].
If you were using AI_CARGO in your missions, please rename AI_CARGO with CARGO...
TASK_TRANSPORT_CARGO is demonstrated at the [URL="https://www.youtube.com/watch?v=v2Us8SS1-44&t=1070s"]GORI Valley Mission - Iteration 4[/URL]
TASK_TRANSPORT_CARGO demonstration missions:
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/TSK%20-%20Task%20Modelling/TSK-110%20-%20Ground%20-%20Transport%20Cargo%20Group"]TSK-110 - Ground - Transport Cargo Group[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/TSK%20-%20Task%20Modelling/TSK-210%20-%20Helicopter%20-%20Transport%20Cargo%20Group"]TSK-210 - Helicopter - Transport Cargo Group[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/TSK%20-%20Task%20Modelling/TSK-211%20-%20Helicopter%20-%20Transport%20Multiple%20Cargo%20Groups"]TSK-211 - Helicopter - Transport Multiple Cargo Groups[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/TSK%20-%20Task%20Modelling/TSK-212%20-%20Helicopter%20-%20Cargo%20handle%20PickedUp%20and%20Deployed%20events"]TSK-212 - Helicopter - Cargo handle PickedUp and Deployed events[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/TSK%20-%20Task%20Modelling/TSK-213%20-%20Helicopter%20-%20Cargo%20Group%20Destroyed"]TSK-213 - Helicopter - Cargo Group Destroyed[/URL]
[SIZE=5]2.3. Dynamically spawn STATIC objects into your mission.[/SIZE]
The [URL="http://flightcontrol-master.github.io/MOOSE/Documentation/SpawnStatic.html#SPAWNSTATIC"]SPAWNSTATIC[/URL] class allows to spawn dynamically new Statics.
By creating a copy of an existing static object template as defined in the Mission Editor (ME), SPAWNSTATIC can retireve the properties of the defined static object template (like type, category etc), and "copy" these properties to create a new static object and place it at the desired coordinate.
New spawned Statics get the same name as the name of the template Static, or gets the given name when a new name is provided at the Spawn method.
SPAWNSTATIC demonstration missions:
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/SPS%20-%20Spawning%20Statics/SPS-100%20-%20Simple%20Spawning"]SPS-100 - Simple Spawning[/URL]
[SIZE=5]2.4. Better coordinate management in MGRS or LLor LLDecimal.[/SIZE]
The [URL="http://flightcontrol-master.github.io/MOOSE/Documentation/Point.html#COORDINATE"]COORDINATE[/URL] class
defines a 2D coordinate in the simulator. A COORDINATE can be expressed in LL or in MGRS.
[SIZE=5]2.5. Improved scoring system[/SIZE]
Scoring is implemented throught the [URL="http://flightcontrol-master.github.io/MOOSE/Documentation/Scoring.html"]SCORING[/URL] class.
The scoring system has been improved a lot! Now, the scoring is correctly counting scores on normal units, statics and scenary objects.
Specific scores can be registered for specific targets. The scoring works together with the tasking system, so players can achieve
additional scores when they achieve goals!
SCORING demonstration missions:
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCO%20-%20Scoring/SCO-100%20-%20Scoring%20of%20Statics"]SCO-100 - Scoring of Statics[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCO%20-%20Scoring/SCO-101%20-%20Scoring%20Client%20to%20Client"]SCO-101 - Scoring Client to Client[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCO%20-%20Scoring/SCO-500%20-%20Scoring%20Multi%20Player%20Demo%20Mission%201"]SCO-500 - Scoring Multi Player Demo Mission 1[/URL]
[SIZE=5]2.6. Beacons and Radio[/SIZE]
The Radio contains 2 classes : RADIO and BEACON
What are radio communications in DCS ?
* Radio transmissions consist of [B]sound files[/B] that are broadcasted on a specific [B]frequency[/B] (e.g. 115MHz) and [B]modulation[/B] (e.g. AM),
* They can be [B]subtitled[/B] for a specific [B]duration[/B], the [B]power[/B] in Watts of the transmiter's antenna can be set, and the transmission can be [B]looped[/B].
These classes are the work of @Grey-Echo.
RADIO and BEACON demonstration missions:
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/RAD%20-%20Radio/RAD-000%20-%20Transmission%20from%20Static"]RAD-000 - Transmission from Static[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/RAD%20-%20Radio/RAD-001%20-%20Transmission%20from%20UNIT%20or%20GROUP"]RAD-001 - Transmission from UNIT or GROUP[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/RAD%20-%20Radio/RAD-002%20-%20Transmission%20Tips%20and%20Tricks"]RAD-002 - Transmission Tips and Tricks[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/RAD%20-%20Radio/RAD-010%20-%20Beacons"] RAD-010 - Beacons[/URL]
[SIZE=5]2.7. Build large formations of AI.[/SIZE]
[URL="http://flightcontrol-master.github.io/MOOSE/Documentation/AI_Formation.html"]AI_FORMATION[/URL] makes AI @{GROUP}s fly in formation of various compositions.
The AI_FORMATION class models formations in a different manner than the internal DCS formation logic!!!
The purpose of the class is to:
* Make formation building a process that can be managed while in flight, rather than a task.
* Human players can guide formations, consisting of larget planes.
* Build large formations (like a large bomber field).
* Form formations that DCS does not support off the shelve.
AI_FORMATION Demo Missions: [URL=""]FOR - AI Group Formation[/URL]
AI_FORMATION demonstration missions:
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-100%20-%20Bomber%20Left%20Line%20Formation"]FOR-100 - Bomber Left Line Formation[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-101%20-%20Bomber%20Right%20Line%20Formation"]FOR-101 - Bomber Right Line Formation[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-102%20-%20Bomber%20Left%20Wing%20Formation"]FOR-102 - Bomber Left Wing Formation[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-103%20-%20Bomber%20Right%20Wing%20Formation"]FOR-103 - Bomber Right Wing Formation[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-104%20-%20Bomber%20Center%20Wing%20Formation"]FOR-104 - Bomber Center Wing Formation[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-105%20-%20Bomber%20Trail%20Formation"]FOR-105 - Bomber Trail Formation[/URL]
* [URL="https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-106%20-%20Bomber%20Box%20Formation"]FOR-106 - Bomber Box Formation[/URL]
Note: The AI_FORMATION is currently a first version showing the potential, a "building block". From this class, further classes will be derived and the class will be fine-tuned.
[SIZE=6]3. A lot of components have been reworked and bugs have been fixed.[/SIZE]
[SIZE=5]3.1. Better event handling and event dispatching.[/SIZE]
The underlying mechanisms to handle DCS events has been improved. Bugs have been fixed.
The MISSION_END event is now also supported.
[SIZE=5]2.2. Cargo handling has been made much better now.[/SIZE]
As a result, some of the WIP cargo classes that were defined earlier are still WIP.
But as mentioned earlier, new CARGO classes can be published faster now.
The framework is now more consistent internally.
[SIZE=6]3. A lot of new methods have been defined in several existing or new classes.[/SIZE]
AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefing ) --R2.1
AI_FORMATION:TestSmokeDirectionVector( SmokeDirection ) --R2.1
AI_FORMATION:onafterFormationLine( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationTrail( FollowGroupSet, From , Event , To, XStart, XSpace, YStart ) --R2.1
AI_FORMATION:onafterFormationStack( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace ) --R2.1
AI_FORMATION:onafterFormationLeftLine( FollowGroupSet, From , Event , To, XStart, YStart, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationRightLine( FollowGroupSet, From , Event , To, XStart, YStart, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationLeftWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationRightWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationCenterWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationVic( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationBox( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace, ZLevels ) --R2.1
AI_FORMATION:SetFlightRandomization( FlightRandomization ) --R2.1
AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1
CARGO:GetName()
CARGO:GetObjectName()
DATABASE:ForEachStatic( IteratorFunction, FinalizeFunction, ... )
EVENT:Reset( EventObject ) --R2.1
POINT_VEC3:IsLOS( ToPointVec3 ) --R2.1
COORDINATE:New( x, y, LandHeightAdd ) --R2.1 Fixes issue #424.
COORDINATE:NewFromVec2( Vec2, LandHeightAdd ) --R2.1 Fixes issue #424.
COORDINATE:NewFromVec3( Vec3 ) --R2.1 Fixes issue #424.
COORDINATE:ToStringLL( LL_Accuracy, LL_DMS ) --R2.1 Fixes issue #424.
COORDINATE:ToStringMGRS( MGRS_Accuracy ) --R2.1 Fixes issue #424.
COORDINATE:ToString() --R2.1 Fixes issue #424.
COORDINATE:CoordinateMenu( RootMenu ) --R2.1 Fixes issue #424.
COORDINATE:MenuSystem( System ) --R2.1 Fixes issue #424.
COORDINATE:MenuLL_Accuracy( LL_Accuracy ) --R2.1 Fixes issue #424.
COORDINATE:MenuLL_DMS( LL_DMS ) --R2.1 Fixes issue #424.
COORDINATE:MenuMGRS_Accuracy( MGRS_Accuracy ) --R2.1 Fixes issue #424.
SET_BASE:FilterDeads() --R2.1 allow deads to be filtered to automatically handle deads in the collection.
SET_BASE:FilterCrashes() --R2.1 allow crashes to be filtered to automatically handle crashes in the collection.
SET_UNIT:ForEachUnitPerThreatLevel( FromThreatLevel, ToThreatLevel, IteratorFunction, ... ) --R2.1 Threat Level implementation
SET_CARGO:New() --R2.1
SET_CARGO:AddCargosByName( AddCargoNames ) --R2.1
SET_CARGO:RemoveCargosByName( RemoveCargoNames ) --R2.1
SET_CARGO:FindCargo( CargoName ) --R2.1
SET_CARGO:FilterCoalitions( Coalitions ) --R2.1
SET_CARGO:FilterTypes( Types ) --R2.1
SET_CARGO:FilterCountries( Countries ) --R2.1
SET_CARGO:FilterPrefixes( Prefixes ) --R2.1
SET_CARGO:FilterStart() --R2.1
SET_CARGO:AddInDatabase( Event ) --R2.1
SET_CARGO:FindInDatabase( Event ) --R2.1
SET_CARGO:ForEachCargo( IteratorFunction, ... ) --R2.1
SET_CARGO:FindNearestCargoFromPointVec2( PointVec2 ) --R2.1
SET_CARGO:IsIncludeObject( MCargo ) --R2.1
SET_CARGO:OnEventNewCargo( EventData ) --R2.1
SET_CARGO:OnEventDeleteCargo( EventData ) --R2.1 SpawnStatic.lua (5 matches)
SPAWNSTATIC:NewFromStatic( SpawnTemplatePrefix, CountryID ) --R2.1
SPAWNSTATIC:NewFromType( SpawnTypeName, SpawnShapeName, SpawnCategory, CountryID ) --R2.1
SPAWNSTATIC:SpawnFromPointVec2( PointVec2, Heading, NewName ) --R2.1
SPAWNSTATIC:SpawnFromZone( Zone, Heading, NewName ) --R2.1
ZONE_BASE:GetCoordinate( Height ) --R2.1
DESIGNATE:SetFlashStatusMenu( FlashMenu ) --R2.1
DESIGNATE:SetLaserCodes( LaserCodes ) --R2.1
DESIGNATE:GenerateLaserCodes() --R2.1
DESIGNATE:SetAutoLase( AutoLase ) --R2.1
DESIGNATE:SetThreatLevelPrioritization( Prioritize ) --R2.1
DETECTION_BASE:CleanDetectionItems() --R2.1 Clean the DetectionItems list
DETECTION_BASE:GetDetectedItemID( Index ) --R2.1
DETECTION_BASE:GetDetectedID( Index ) --R2.1
DETECTION_AREAS:DetectedReportDetailed() --R2.1 Fixed missing report
REPORT:HasText() --R2.1
REPORT:SetIndent( Indent ) --R2.1
REPORT:AddIndent( Text ) --R2.1
MISSION:GetMenu( TaskGroup ) -- R2.1 -- Changed Menu Structure
TASK:SetMenu( MenuTime ) --R2.1 Mission Reports and Task Reports added. Fixes issue #424.
TASK:ReportSummary() --R2.1 fixed report. Now nicely formatted and contains the info required.
TASK:ReportOverview() --R2.1 fixed report. Now nicely formatted and contains the info required.
TASK:GetPlayerCount() --R2.1 Get a count of the players.
TASK:GetPlayerNames() --R2.1 Get a map of the players.
TASK:ReportDetails() --R2.1 fixed report. Now nicely formatted and contains the info required.
UTILS.tostringMGRS = function(MGRS, acc) --R2.1
POSITIONABLE:GetBoundingBox() --R2.1
POSITIONABLE:GetHeight() --R2.1
POSITIONABLE:GetMessageText( Message, Name ) --R2.1 added
POSITIONABLE:GetMessage( Message, Duration, Name ) --R2.1 changed callsign and name and using GetMessageText
POSITIONABLE:MessageToSetGroup( Message, Duration, MessageSetGroup, Name ) --R2.1
POSITIONABLE:GetRadio() --R2.1
POSITIONABLE:GetBeacon() --R2.1
POSITIONABLE:LaseUnit( Target, LaserCode, Duration ) --R2.1
POSITIONABLE:LaseOff() --R2.1
POSITIONABLE:IsLasing() --R2.1
POSITIONABLE:GetSpot() --R2.1
POSITIONABLE:GetLaserCode() --R2.1
UNIT:IsDetected( TargetUnit ) --R2.1
UNIT:IsLOS( TargetUnit ) --R2.1

View File

@ -1,363 +0,0 @@
# MOOSE Release 2.1.0
Finally it is here, release 2.1.0 of MOOSE!
It took some time to prepare this release, as it was a lot of work to get the building blocks of the framework developed and tested. You'll find in this release a lot of new features as well as a couple of important bug fixes.
Release 2.1.0 is now published into the **master-release-2.1** branch of this repository on github.
You can download the file moose.lua below to use MOOSE in your missions.
The moose.lua file is also located [here](https://github.com/FlightControl-Master/MOOSE/blob/master-release-2.1/Moose%20Mission%20Setup/Moose.lua) in the **master-release-2.1** branch.
Those who are using the **master** branch can continue to beta test, as new bleeding edge features will be added soon in preparation for release 2.2.0! There are many topics on the agenda to be added.
**This release would not have been possible without the help and contribution of many
members of this community. THANK YOU!**
## In summary:
This release brings you **an improved tasking mechanism**.
Tasking is the system in MOOSE that allows to:
* Execute **co-op** missions and tasks
* **Detect** targets dynamically
* Define new tasks **dynamically**
* Execute the tasks
* Complete the mission **goals**
* Extensive menu system and briefings/reports for **player interaction**
* Improved Scoring of mission goal achievements, and task achievements.
On top, release brings you new functionality by the introduction of new classes to:
* **Designate targets** (lase, smoke or illuminate targets) by AI, assisting your attack. Allows to drop laser guides bombs.
* A new **tasking** system to **transport cargo** of various types
* Dynamically **spawn static objects**
* Improved **coordinate system**
* Build **large formations**, like bombers flying to a target area
## 1. TASKING SYSTEM!
A lot of work has been done in improving the tasking framework within MOOSE.
**The tasking system comes with TASK DISPATCHING mechanisms, that DYNAMICALLY
allocate new tasks based on the tactical or strategical situation in the mission!!!
These tasks can then be engaged upon by the players!!!**
The [TASK\_A2G\_DISPATCHER](http://flightcontrol-master.github.io/MOOSE/Documentation/Task_A2G_Dispatcher.html) class implements the dynamic dispatching of tasks upon groups of detected units determined a Set of FAC (groups). The FAC will detect units, will group them, and will dispatch Tasks to groups of players. Depending on the type of target detected, different tasks will be dispatched. Find a summary below describing for which situation a task type is created:
* **CAS Task**: Is created when there are enemy ground units within range of the FAC, while there are friendly units in the FAC perimeter.
* **BAI Task**: Is created when there are enemy ground units within range of the FAC, while there are NO other friendly units within the FAC perimeter.
* **SEAD Task**: Is created when there are enemy ground units wihtin range of the FAC, with air search radars.
More TASK_... dispatcher classes are to come in the future, like A2A, G2G, etc...
Improvements on the TASKING are in summary:
* A COMMANDCENTER has a dedicated menu.
* A MISSION has a dedicated menu system.
* A MISSION has a briefing report.
* A MISSION has dedicated status reports.
* A MISSION has for each TASK TYPE a menu.
* A MISSION has for each TASK TYPE a dedicated menu system for each TASK defined.
* A MISSION has an "assigned" task menu that contains menu actions relevant to the assigned task.
* A TASK (of various types) has a dedicated menu system.
* A TASK has a briefing report.
* A TASK has dedicated status reports.
* Player reports can be retrieved that explain which player is at which task.
* ...
TASKING is vast, and at the moment there is too much to explain.
**The best way to explore the TASKING is to TRY it...**
I suggest you have a look at the [GORI Valley Mission - Iteration 3](https://www.youtube.com/watch?v=v2Us8SS1-44&t=1070s).
Many people have contributed in the testing of the mechanism, especially:
@baluballa, @doom, @whiplash
## 2. New MOOSE classes have been added.
MOOSE 2.1.0 comes with new classes that extends the functionality of the MOOSE framework and allow you to do new things in your missions:
### 2.1. Target designation by laser, smoke or illumination.
[DESIGNATE](http://flightcontrol-master.github.io/MOOSE/Documentation/Designate.html) is orchestrating the designation of potential targets executed by a Recce group,
and communicates these to a dedicated attacking group of players,
so that following a dynamically generated menu system,
each detected set of potential targets can be lased or smoked...
Targets can be:
* **Lased** for a period of time.
* **Smoked**. Artillery or airplanes with Illuminatino ordonance need to be present. (WIP, but early demo ready.)
* **Illuminated** through an illumination bomb. Artillery or airplanes with Illuminatino ordonance need to be present. (WIP, but early demo ready.
This class was made with the help of @EasyEB and many others.
[DESIGNATE is demonstrated on youtube](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0dQ9UKQMb7YL8z2sKSqemH)
DESIGNATE demonstration missions:
* [DES - Designation](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/DES%20-%20Designation)
### 2.2. Transport cargo of different types to various locations as a human task within a mission.
The Moose framework provides various CARGO classes that allow DCS physical or logical objects to be transported or sling loaded by Carriers.
The CARGO_ classes, as part of the moose core, are able to Board, Load, UnBoard and UnLoad cargo between Carrier units.
This collection of classes in this module define tasks for human players to handle these cargo objects.
Cargo can be transported, picked-up, deployed and sling-loaded from and to other places.
[TASK\_CARGO\_TRANSPORT](http://flightcontrol-master.github.io/MOOSE/Documentation/Task_Cargo.html#TASK_CARGO_TRANSPORT) defines a task for a human player to transport a set of cargo between various zones.
It is the first class that forms part of the TASK_CARGO classes suite.
The TASK_CARGO classes provide you with a flexible tasking sytem,
that allows you to transport cargo of various types between various locations
and various dedicated deployment zones.
A human player can join the battle field in a client airborne slot or a ground vehicle within the CA module (ALT-J).
The player needs to accept the task from the task overview list within the mission, using the radio menus.
Once the TASK\_CARGO\_TRANSPORT is assigned to the player and accepted by the player, the player will obtain
an extra **Cargo Handling Radio Menu** that contains the CARGO objects that need to be transported.
Cargo can be transported towards different **Deployment Zones**, but can also be deployed anywhere within the battle field.
The Cargo Handling Radio Menu system allows to execute **various actions** to handle the cargo.
In the menu, you'll find for each CARGO, that is part of the scope of the task, various actions that can be completed.
Depending on the location of your Carrier unit, the menu options will vary.
The [CARGO_GROUP](http://flightcontrol-master.github.io/MOOSE/Documentation/Cargo.html#CARGO_GROUP) class defines a
cargo that is represented by a GROUP object within the simulator, and can be transported by a carrier.
The [CARGO_UNIT](http://flightcontrol-master.github.io/MOOSE/Documentation/Cargo.html#CARGO_UNIT) class defines a
cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier.
Mission designers can use the [SET_CARGO](http://flightcontrol-master.github.io/MOOSE/Documentation/Set.html#SET_CARGO)
class to build sets of cargos.
Note 1: **Various other CARGO classes are defined and are WIP**.
Now that the foundation for Cargo handling is getting form, future releases will bring other types of CARGO handling
classes to the MOOSE framework quickly. Sling-loading, package, beacon and other types of CARGO will be released soon.
Note 2: **AI_CARGO has been renamed to CARGO and now forms part of the Core or MOOSE**.
If you were using AI_CARGO in your missions, please rename AI_CARGO with CARGO...
TASK\_TRANSPORT\_CARGO is demonstrated at the [GORI Valley Mission - Iteration 4](https://www.youtube.com/watch?v=v2Us8SS1-44&t=1070s)
TASK_TRANSPORT_CARGO demonstration missions:
* [TSK-110 - Ground - Transport Cargo Group](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/TSK%20-%20Task%20Modelling/TSK-110%20-%20Ground%20-%20Transport%20Cargo%20Group)
* [TSK-210 - Helicopter - Transport Cargo Group](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/TSK%20-%20Task%20Modelling/TSK-210%20-%20Helicopter%20-%20Transport%20Cargo%20Group)
* [TSK-211 - Helicopter - Transport Multiple Cargo Groups](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/TSK%20-%20Task%20Modelling/TSK-211%20-%20Helicopter%20-%20Transport%20Multiple%20Cargo%20Groups)
* [TSK-212 - Helicopter - Cargo handle PickedUp and Deployed events](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/TSK%20-%20Task%20Modelling/TSK-212%20-%20Helicopter%20-%20Cargo%20handle%20PickedUp%20and%20Deployed%20events)
* [TSK-213 - Helicopter - Cargo Group Destroyed](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/TSK%20-%20Task%20Modelling/TSK-213%20-%20Helicopter%20-%20Cargo%20Group%20Destroyed)
### 2.3. Dynamically spawn STATIC objects into your mission.
The [SPAWNSTATIC](http://flightcontrol-master.github.io/MOOSE/Documentation/SpawnStatic.html#SPAWNSTATIC) class allows to spawn dynamically new Statics.
By creating a copy of an existing static object template as defined in the Mission Editor (ME), SPAWNSTATIC can retireve the properties of the defined static object template (like type, category etc), and "copy" these properties to create a new static object and place it at the desired coordinate.
New spawned Statics get the same name as the name of the template Static, or gets the given name when a new name is provided at the Spawn method.
SPAWNSTATIC demonstration missions:
* [SPS-100 - Simple Spawning](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release-2.1/SPS%20-%20Spawning%20Statics/SPS-100%20-%20Simple%20Spawning)
### 2.4. Better coordinate management in MGRS or LLor LLDecimal.
The [COORDINATE](http://flightcontrol-master.github.io/MOOSE/Documentation/Point.html#COORDINATE) class
defines a 2D coordinate in the simulator. A COORDINATE can be expressed in LL or in MGRS.
### 2.5. Improved scoring system
Scoring is implemented throught the [SCORING](http://flightcontrol-master.github.io/MOOSE/Documentation/Scoring.html) class.
The scoring system has been improved a lot! Now, the scoring is correctly counting scores on normal units, statics and scenary objects.
Specific scores can be registered for specific targets. The scoring works together with the tasking system, so players can achieve
additional scores when they achieve goals!
SCORING demonstration missions:
* [SCO-100 - Scoring of Statics](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCO%20-%20Scoring/SCO-100%20-%20Scoring%20of%20Statics)
* [SCO-101 - Scoring Client to Client](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCO%20-%20Scoring/SCO-101%20-%20Scoring%20Client%20to%20Client)
* [SCO-500 - Scoring Multi Player Demo Mission 1](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCO%20-%20Scoring/SCO-500%20-%20Scoring%20Multi%20Player%20Demo%20Mission%201)
### 2.6. Beacons and Radio
The Radio contains 2 classes : RADIO and BEACON
What are radio communications in DCS ?
* Radio transmissions consist of **sound files** that are broadcasted on a specific **frequency** (e.g. 115MHz) and **modulation** (e.g. AM),
* They can be **subtitled** for a specific **duration**, the **power** in Watts of the transmiter's antenna can be set, and the transmission can be **looped**.
These classes are the work of @Grey-Echo.
RADIO and BEACON demonstration missions:
* [RAD-000 - Transmission from Static](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/RAD%20-%20Radio/RAD-000%20-%20Transmission%20from%20Static)
* [RAD-001 - Transmission from UNIT or GROUP](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/RAD%20-%20Radio/RAD-001%20-%20Transmission%20from%20UNIT%20or%20GROUP)
* [RAD-002 - Transmission Tips and Tricks](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/RAD%20-%20Radio/RAD-002%20-%20Transmission%20Tips%20and%20Tricks)
* [ RAD-010 - Beacons](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/RAD%20-%20Radio/RAD-010%20-%20Beacons)
### 2.7. Build large formations of AI.
[AI_FORMATION](http://flightcontrol-master.github.io/MOOSE/Documentation/AI_Formation.html) makes AI @{GROUP}s fly in formation of various compositions.
The AI_FORMATION class models formations in a different manner than the internal DCS formation logic!!!
The purpose of the class is to:
* Make formation building a process that can be managed while in flight, rather than a task.
* Human players can guide formations, consisting of larget planes.
* Build large formations (like a large bomber field).
* Form formations that DCS does not support off the shelve.
AI_FORMATION Demo Missions: [FOR - AI Group Formation]()
AI\_FORMATION demonstration missions:
* [FOR-100 - Bomber Left Line Formation](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-100%20-%20Bomber%20Left%20Line%20Formation)
* [FOR-101 - Bomber Right Line Formation](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-101%20-%20Bomber%20Right%20Line%20Formation)
* [FOR-102 - Bomber Left Wing Formation](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-102%20-%20Bomber%20Left%20Wing%20Formation)
* [FOR-103 - Bomber Right Wing Formation](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-103%20-%20Bomber%20Right%20Wing%20Formation)
* [FOR-104 - Bomber Center Wing Formation](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-104%20-%20Bomber%20Center%20Wing%20Formation)
* [FOR-105 - Bomber Trail Formation](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-105%20-%20Bomber%20Trail%20Formation)
* [FOR-106 - Bomber Box Formation](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20AI%20Group%20Formation/FOR-106%20-%20Bomber%20Box%20Formation)
Note: The AI_FORMATION is currently a first version showing the potential, a "building block". From this class, further classes will be derived and the class will be fine-tuned.
## 3. A lot of components have been reworked and bugs have been fixed.
### 3.1. Better event handling and event dispatching.
The underlying mechanisms to handle DCS events has been improved. Bugs have been fixed.
The MISSION_END event is now also supported.
### 2.2. Cargo handling has been made much better now.
As a result, some of the WIP cargo classes that were defined earlier are still WIP.
But as mentioned earlier, new CARGO classes can be published faster now.
The framework is now more consistent internally.
## 3. A lot of new methods have been defined in several existing or new classes.
AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefing ) --R2.1
AI_FORMATION:TestSmokeDirectionVector( SmokeDirection ) --R2.1
AI_FORMATION:onafterFormationLine( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationTrail( FollowGroupSet, From , Event , To, XStart, XSpace, YStart ) --R2.1
AI_FORMATION:onafterFormationStack( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace ) --R2.1
AI_FORMATION:onafterFormationLeftLine( FollowGroupSet, From , Event , To, XStart, YStart, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationRightLine( FollowGroupSet, From , Event , To, XStart, YStart, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationLeftWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationRightWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationCenterWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationVic( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) --R2.1
AI_FORMATION:onafterFormationBox( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace, ZLevels ) --R2.1
AI_FORMATION:SetFlightRandomization( FlightRandomization ) --R2.1
AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1
CARGO:GetName()
CARGO:GetObjectName()
DATABASE:ForEachStatic( IteratorFunction, FinalizeFunction, ... )
EVENT:Reset( EventObject ) --R2.1
POINT_VEC3:IsLOS( ToPointVec3 ) --R2.1
COORDINATE:New( x, y, LandHeightAdd ) --R2.1 Fixes issue #424.
COORDINATE:NewFromVec2( Vec2, LandHeightAdd ) --R2.1 Fixes issue #424.
COORDINATE:NewFromVec3( Vec3 ) --R2.1 Fixes issue #424.
COORDINATE:ToStringLL( LL_Accuracy, LL_DMS ) --R2.1 Fixes issue #424.
COORDINATE:ToStringMGRS( MGRS_Accuracy ) --R2.1 Fixes issue #424.
COORDINATE:ToString() --R2.1 Fixes issue #424.
COORDINATE:CoordinateMenu( RootMenu ) --R2.1 Fixes issue #424.
COORDINATE:MenuSystem( System ) --R2.1 Fixes issue #424.
COORDINATE:MenuLL_Accuracy( LL_Accuracy ) --R2.1 Fixes issue #424.
COORDINATE:MenuLL_DMS( LL_DMS ) --R2.1 Fixes issue #424.
COORDINATE:MenuMGRS_Accuracy( MGRS_Accuracy ) --R2.1 Fixes issue #424.
SET_BASE:FilterDeads() --R2.1 allow deads to be filtered to automatically handle deads in the collection.
SET_BASE:FilterCrashes() --R2.1 allow crashes to be filtered to automatically handle crashes in the collection.
SET_UNIT:ForEachUnitPerThreatLevel( FromThreatLevel, ToThreatLevel, IteratorFunction, ... ) --R2.1 Threat Level implementation
SET_CARGO:New() --R2.1
SET_CARGO:AddCargosByName( AddCargoNames ) --R2.1
SET_CARGO:RemoveCargosByName( RemoveCargoNames ) --R2.1
SET_CARGO:FindCargo( CargoName ) --R2.1
SET_CARGO:FilterCoalitions( Coalitions ) --R2.1
SET_CARGO:FilterTypes( Types ) --R2.1
SET_CARGO:FilterCountries( Countries ) --R2.1
SET_CARGO:FilterPrefixes( Prefixes ) --R2.1
SET_CARGO:FilterStart() --R2.1
SET_CARGO:AddInDatabase( Event ) --R2.1
SET_CARGO:FindInDatabase( Event ) --R2.1
SET_CARGO:ForEachCargo( IteratorFunction, ... ) --R2.1
SET_CARGO:FindNearestCargoFromPointVec2( PointVec2 ) --R2.1
SET_CARGO:IsIncludeObject( MCargo ) --R2.1
SET_CARGO:OnEventNewCargo( EventData ) --R2.1
SET_CARGO:OnEventDeleteCargo( EventData ) --R2.1 SpawnStatic.lua (5 matches)
SPAWNSTATIC:NewFromStatic( SpawnTemplatePrefix, CountryID ) --R2.1
SPAWNSTATIC:NewFromType( SpawnTypeName, SpawnShapeName, SpawnCategory, CountryID ) --R2.1
SPAWNSTATIC:SpawnFromPointVec2( PointVec2, Heading, NewName ) --R2.1
SPAWNSTATIC:SpawnFromZone( Zone, Heading, NewName ) --R2.1
ZONE_BASE:GetCoordinate( Height ) --R2.1
DESIGNATE:SetFlashStatusMenu( FlashMenu ) --R2.1
DESIGNATE:SetLaserCodes( LaserCodes ) --R2.1
DESIGNATE:GenerateLaserCodes() --R2.1
DESIGNATE:SetAutoLase( AutoLase ) --R2.1
DESIGNATE:SetThreatLevelPrioritization( Prioritize ) --R2.1
DETECTION_BASE:CleanDetectionItems() --R2.1 Clean the DetectionItems list
DETECTION_BASE:GetDetectedItemID( Index ) --R2.1
DETECTION_BASE:GetDetectedID( Index ) --R2.1
DETECTION_AREAS:DetectedReportDetailed() --R2.1 Fixed missing report
REPORT:HasText() --R2.1
REPORT:SetIndent( Indent ) --R2.1
REPORT:AddIndent( Text ) --R2.1
MISSION:GetMenu( TaskGroup ) -- R2.1 -- Changed Menu Structure
TASK:SetMenu( MenuTime ) --R2.1 Mission Reports and Task Reports added. Fixes issue #424.
TASK:ReportSummary() --R2.1 fixed report. Now nicely formatted and contains the info required.
TASK:ReportOverview() --R2.1 fixed report. Now nicely formatted and contains the info required.
TASK:GetPlayerCount() --R2.1 Get a count of the players.
TASK:GetPlayerNames() --R2.1 Get a map of the players.
TASK:ReportDetails() --R2.1 fixed report. Now nicely formatted and contains the info required.
UTILS.tostringMGRS = function(MGRS, acc) --R2.1
POSITIONABLE:GetBoundingBox() --R2.1
POSITIONABLE:GetHeight() --R2.1
POSITIONABLE:GetMessageText( Message, Name ) --R2.1 added
POSITIONABLE:GetMessage( Message, Duration, Name ) --R2.1 changed callsign and name and using GetMessageText
POSITIONABLE:MessageToSetGroup( Message, Duration, MessageSetGroup, Name ) --R2.1
POSITIONABLE:GetRadio() --R2.1
POSITIONABLE:GetBeacon() --R2.1
POSITIONABLE:LaseUnit( Target, LaserCode, Duration ) --R2.1
POSITIONABLE:LaseOff() --R2.1
POSITIONABLE:IsLasing() --R2.1
POSITIONABLE:GetSpot() --R2.1
POSITIONABLE:GetLaserCode() --R2.1
UNIT:IsDetected( TargetUnit ) --R2.1
UNIT:IsLOS( TargetUnit ) --R2.1

BIN
Release Notes 2.2.0.docx Normal file

Binary file not shown.

BIN
Release Notes 2.2.0.pdf Normal file

Binary file not shown.

565
Utils/DCS_ControlAPI.txt Normal file
View File

@ -0,0 +1,565 @@
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

5
Utils/Generate_Moose.bat Normal file
View File

@ -0,0 +1,5 @@
%~dp0luarocks\lua5.1.exe %1 %2 %3 %4 %5
call %~dp0LuaSrcDiet.bat --basic --opt-emptylines %5\Moose.lua
rem del %5\Moose.lua
rem copy %5\Moose_.lua %5\Moose.lua
rem del Moose_.lua

Binary file not shown.

View File

@ -0,0 +1,178 @@
#!/usr/bin/lua
--------------------------------------------------------------------------------
-- Copyright (c) 2012-2014 Sierra Wireless.
-- All rights reserved. This program and the accompanying materials
-- are made available under the terms of the Eclipse Public License v1.0
-- which accompanies this distribution, and is available at
-- http://www.eclipse.org/legal/epl-v10.html
--
-- Contributors:
-- Kevin KIN-FOO <kkinfoo@sierrawireless.com>
-- - initial API and implementation and initial documentation
--------------------------------------------------------------------------------
-- Check interpreter version
if _VERSION ~= "Lua 5.1" then
print("Luadocumentor is only compatible with Lua 5.1")
return
end
--
-- Defining help message.
--
-- This message is compliant to 'lapp', which will match options and arguments
-- from command line.
local help = [[luadocumentor v0.1.4: tool for Lua Documentation Language
-f, --format (default doc) Define output format :
* doc: Will produce HTML documentation from specified file(s) or directories.
* api: Will produce API file(s) from specified file(s) or directories.
-d, --dir (default docs) Define an output directory. If the given directory doesn't exist, it will be created.
-h, --help Display the help.
-n, --noheuristic Do not use code analysis, use only comments to generate documentation.
-s, --style (default !) The path of your own css file, if you don't want to use the default one. (usefull only for the doc format)
[directories|files] Define the paths or the directories of inputs files. Only Lua or C files containing a @module tag will be considered.
]]
local docgenerator = require 'docgenerator'
local lddextractor = require 'lddextractor'
local lapp = require 'pl.lapp'
local args = lapp( help )
if not args or #args < 1 then
print('No directory provided')
return
elseif args.help then
-- Just print help
print( help )
return
end
--
-- define css file name
--
local cssfilename = "stylesheet.css"
--
-- Parse files from given folders
--
-- Check if all folders exist
local fs = require 'fs.lfs'
local allpresent, missing = fs.checkdirectory(args)
-- Some of given directories are absent
if missing then
-- List missing directories
print 'Unable to open'
for _, file in ipairs( missing ) do
print('\t'.. file)
end
return
end
-- Get files from given directories
local filestoparse, error = fs.filelist( args )
if not filestoparse then
print ( error )
return
end
--
-- Generate documentation only files
--
if args.format == 'api' then
for _, filename in ipairs( filestoparse ) do
-- Loading file content
print('Dealing with "'..filename..'".')
local file, error = io.open(filename, 'r')
if not file then
print ('Unable to open "'..filename.."'.\n"..error)
else
local code = file:read('*all')
file:close()
--
-- Creating comment file
--
local commentfile, error = lddextractor.generatecommentfile(filename, code)
-- Getting module name
-- Optimize me
local module, moduleerror = lddextractor.generateapimodule(filename, code)
if not commentfile then
print('Unable to create documentation file for "'..filename..'"\n'..error)
elseif not module or not module.name then
local error = moduleerror and '\n'..moduleerror or ''
print('Unable to compute module name for "'..filename..'".'..error)
else
--
-- Flush documentation file on disk
--
local path = args.dir..fs.separator..module.name..'.lua'
local status, err = fs.fill(path, commentfile)
if not status then
print(err)
end
end
end
end
print('Done')
return
end
-- Deal only supported output types
if args.format ~= 'doc' then
print ('"'..args.format..'" format is not handled.')
return
end
-- Generate html form files
local parsedfiles, unparsed = docgenerator.generatedocforfiles(filestoparse, cssfilename,args.noheuristic)
-- Show warnings on unparsed files
if #unparsed > 0 then
for _, faultyfile in ipairs( unparsed ) do
print( faultyfile )
end
end
-- This loop is just for counting parsed files
-- TODO: Find a more elegant way to do it
local parsedfilescount = 0
for _, p in pairs(parsedfiles) do
parsedfilescount = parsedfilescount + 1
end
print (parsedfilescount .. ' file(s) parsed.')
-- Create html files
local generated = 0
for _, apifile in pairs ( parsedfiles ) do
local status, err = fs.fill(args.dir..fs.separator..apifile.name..'.html', apifile.body)
if status then
generated = generated + 1
else
print( 'Unable to create '..apifile.name..'.html on disk.')
end
end
print (generated .. ' file(s) generated.')
-- Copying css
local csscontent
if args.style == '!' then
csscontent = require 'defaultcss'
else
local css, error = io.open(args.style, 'r')
if not css then
print('Unable to open "'..args.style .. '".\n'..error)
return
end
csscontent = css:read("*all")
css:close()
end
local status, error = fs.fill(args.dir..fs.separator..cssfilename, csscontent)
if not status then
print(error)
return
end
print('Adding css')
print('Done')

View File

@ -0,0 +1,198 @@
Eclipse Public License - v 1.0
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
1. DEFINITIONS
"Contribution" means:
a) in the case of the initial Contributor, the initial code and documentation
distributed under this Agreement, and
b) in the case of each subsequent Contributor:
i) changes to the Program, and
ii) additions to the Program;
where such changes and/or additions to the Program originate from and are
distributed by that particular Contributor. A Contribution 'originates' from
a Contributor if it was added to the Program by such Contributor itself or
anyone acting on such Contributor's behalf. Contributions do not include
additions to the Program which: (i) are separate modules of software
distributed in conjunction with the Program under their own license
agreement, and (ii) are not derivative works of the Program.
"Contributor" means any person or entity that distributes the Program.
"Licensed Patents" mean patent claims licensable by a Contributor which are
necessarily infringed by the use or sale of its Contribution alone or when
combined with the Program.
"Program" means the Contributions distributed in accordance with this Agreement.
"Recipient" means anyone who receives the Program under this Agreement,
including all Contributors.
2. GRANT OF RIGHTS
a) Subject to the terms of this Agreement, each Contributor hereby grants
Recipient a non-exclusive, worldwide, royalty-free copyright license to
reproduce, prepare derivative works of, publicly display, publicly perform,
distribute and sublicense the Contribution of such Contributor, if any, and
such derivative works, in source code and object code form.
b) Subject to the terms of this Agreement, each Contributor hereby grants
Recipient a non-exclusive, worldwide, royalty-free patent license under
Licensed Patents to make, use, sell, offer to sell, import and otherwise
transfer the Contribution of such Contributor, if any, in source code and
object code form. This patent license shall apply to the combination of the
Contribution and the Program if, at the time the Contribution is added by
the Contributor, such addition of the Contribution causes such combination
to be covered by the Licensed Patents. The patent license shall not apply
to any other combinations which include the Contribution. No hardware per
se is licensed hereunder.
c) Recipient understands that although each Contributor grants the licenses to
its Contributions set forth herein, no assurances are provided by any
Contributor that the Program does not infringe the patent or other
intellectual property rights of any other entity. Each Contributor
disclaims any liability to Recipient for claims brought by any other entity
based on infringement of intellectual property rights or otherwise. As a
condition to exercising the rights and licenses granted hereunder, each
Recipient hereby assumes sole responsibility to secure any other
intellectual property rights needed, if any. For example, if a third party
patent license is required to allow Recipient to distribute the Program, it
is Recipient's responsibility to acquire that license before distributing
the Program.
d) Each Contributor represents that to its knowledge it has sufficient
copyright rights in its Contribution, if any, to grant the copyright
license set forth in this Agreement.
3. REQUIREMENTS
A Contributor may choose to distribute the Program in object code form under its
own license agreement, provided that:
a) it complies with the terms and conditions of this Agreement; and
b) its license agreement:
i) effectively disclaims on behalf of all Contributors all warranties and
conditions, express and implied, including warranties or conditions of
title and non-infringement, and implied warranties or conditions of
merchantability and fitness for a particular purpose;
ii) effectively excludes on behalf of all Contributors all liability for
damages, including direct, indirect, special, incidental and
consequential damages, such as lost profits;
iii) states that any provisions which differ from this Agreement are offered
by that Contributor alone and not by any other party; and
iv) states that source code for the Program is available from such
Contributor, and informs licensees how to obtain it in a reasonable
manner on or through a medium customarily used for software exchange.
When the Program is made available in source code form:
a) it must be made available under this Agreement; and
b) a copy of this Agreement must be included with each copy of the Program.
Contributors may not remove or alter any copyright notices contained within
the Program.
Each Contributor must identify itself as the originator of its Contribution, if
any, in a manner that reasonably allows subsequent Recipients to identify the
originator of the Contribution.
4. COMMERCIAL DISTRIBUTION
Commercial distributors of software may accept certain responsibilities with
respect to end users, business partners and the like. While this license is
intended to facilitate the commercial use of the Program, the Contributor who
includes the Program in a commercial product offering should do so in a manner
which does not create potential liability for other Contributors. Therefore, if
a Contributor includes the Program in a commercial product offering, such
Contributor ("Commercial Contributor") hereby agrees to defend and indemnify
every other Contributor ("Indemnified Contributor") against any losses, damages
and costs (collectively "Losses") arising from claims, lawsuits and other legal
actions brought by a third party against the Indemnified Contributor to the
extent caused by the acts or omissions of such Commercial Contributor in
connection with its distribution of the Program in a commercial product
offering. The obligations in this section do not apply to any claims or Losses
relating to any actual or alleged intellectual property infringement. In order
to qualify, an Indemnified Contributor must: a) promptly notify the Commercial
Contributor in writing of such claim, and b) allow the Commercial Contributor to
control, and cooperate with the Commercial Contributor in, the defense and any
related settlement negotiations. The Indemnified Contributor may participate in
any such claim at its own expense.
For example, a Contributor might include the Program in a commercial product
offering, Product X. That Contributor is then a Commercial Contributor. If that
Commercial Contributor then makes performance claims, or offers warranties
related to Product X, those performance claims and warranties are such
Commercial Contributor's responsibility alone. Under this section, the
Commercial Contributor would have to defend claims against the other
Contributors related to those performance claims and warranties, and if a court
requires any other Contributor to pay any damages as a result, the Commercial
Contributor must pay those damages.
5. NO WARRANTY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
Recipient is solely responsible for determining the appropriateness of using and
distributing the Program and assumes all risks associated with its exercise of
rights under this Agreement , including but not limited to the risks and costs
of program errors, compliance with applicable laws, damage to or loss of data,
programs or equipment, and unavailability or interruption of operations.
6. DISCLAIMER OF LIABILITY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS
GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
7. GENERAL
If any provision of this Agreement is invalid or unenforceable under applicable
law, it shall not affect the validity or enforceability of the remainder of the
terms of this Agreement, and without further action by the parties hereto, such
provision shall be reformed to the minimum extent necessary to make such
provision valid and enforceable.
If Recipient institutes patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Program itself
(excluding combinations of the Program with other software or hardware)
infringes such Recipient's patent(s), then such Recipient's rights granted under
Section 2(b) shall terminate as of the date such litigation is filed.
All Recipient's rights under this Agreement shall terminate if it fails to
comply with any of the material terms or conditions of this Agreement and does
not cure such failure in a reasonable period of time after becoming aware of
such noncompliance. If all Recipient's rights under this Agreement terminate,
Recipient agrees to cease use and distribution of the Program as soon as
reasonably practicable. However, Recipient's obligations under this Agreement
and any licenses granted by Recipient relating to the Program shall continue and
survive.
Everyone is permitted to copy and distribute copies of this Agreement, but in
order to avoid inconsistency the Agreement is copyrighted and may only be
modified in the following manner. The Agreement Steward reserves the right to
publish new versions (including revisions) of this Agreement from time to time.
No one other than the Agreement Steward has the right to modify this Agreement.
The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation
may assign the responsibility to serve as the Agreement Steward to a suitable
separate entity. Each new version of the Agreement will be given a
distinguishing version number. The Program (including Contributions) may always
be distributed subject to the version of the Agreement under which it was
received. In addition, after a new version of the Agreement is published,
Contributor may elect to distribute the Program (including its Contributions)
under the new version. Except as expressly stated in Sections 2(a) and 2(b)
above, Recipient receives no rights or licenses to the intellectual property of
any Contributor under this Agreement, whether expressly, by implication,
estoppel or otherwise. All rights in the Program not expressly granted under
this Agreement are reserved.
This Agreement is governed by the laws of the State of New York and the
intellectual property laws of the United States of America. No party to this
Agreement will bring a legal action under this Agreement more than one year
after the cause of action arose. Each party waives its rights to a jury trial in
any resulting litigation.

View File

@ -0,0 +1,7 @@
# Lua Documentor
LuaDocumentor allow users to generate HTML and API files from code documented
using Lua documentation language.
Documentation is
[available here](http://wiki.eclipse.org/Koneki/LDT/User_Area/LuaDocumentor).

View File

@ -0,0 +1,57 @@
package = 'LuaDocumentor'
version = '0.1.5-1'
description = {
summary = 'LuaDocumentor allow users to generate HTML and API files from code documented using Lua documentation language.',
detailed = [[
This is an example for the LuaRocks tutorial.
Here we would put a detailed, typically
paragraph-long description.
]],
homepage = 'http://wiki.eclipse.org/Koneki/LDT/User_Area/LuaDocumentor',
license = 'EPL'
}
source = {
url = 'git://github.com/LuaDevelopmentTools/luadocumentor.git',
tag = 'v0.1.5-1'
}
dependencies = {
'lua ~> 5.1',
'luafilesystem ~> 1.6',
'markdown ~> 0.32',
'metalua-compiler ~> 0.7',
'penlight ~> 0.9'
}
build = {
type = 'builtin',
install = {
bin = {
luadocumentor = 'luadocumentor.lua'
},
lua = {
['models.internalmodelbuilder'] = 'models/internalmodelbuilder.mlua'
}
},
modules = {
defaultcss = 'defaultcss.lua',
docgenerator = 'docgenerator.lua',
extractors = 'extractors.lua',
lddextractor = 'lddextractor.lua',
templateengine = 'templateengine.lua',
['fs.lfs'] = 'fs/lfs.lua',
['models.apimodel'] = 'models/apimodel.lua',
['models.apimodelbuilder'] = 'models/apimodelbuilder.lua',
['models.internalmodel'] = 'models/internalmodel.lua',
['models.ldparser'] = 'models/ldparser.lua',
['template.file'] = 'template/file.lua',
['template.index'] = 'template/index.lua',
['template.index.recordtypedef'] = 'template/index/recordtypedef.lua',
['template.item'] = 'template/item.lua',
['template.page'] = 'template/page.lua',
['template.recordtypedef'] = 'template/recordtypedef.lua',
['template.usage'] = 'template/usage.lua',
['template.utils'] = 'template/utils.lua',
}
}

View File

@ -0,0 +1,39 @@
rock_manifest = {
bin = {
luadocumentor = "bc5cc07f56db2cf1dbe80f0827332873"
},
doc = {
LICENSE = "52a21f73ac77fd790dc40dc5acda0fc2",
["README.md"] = "fcef1f43c69f3559b347d854b2626deb"
},
lua = {
["defaultcss.lua"] = "dd9b2b89e5080972bbb52056247c0c65",
["docgenerator.lua"] = "92d0a3947d88226340014d2f033be37f",
["extractors.lua"] = "74191695e5217706ee355925e5ca40fa",
fs = {
["lfs.lua"] = "4d00f9bc942b02a86ccea16544d3e85d"
},
["lddextractor.lua"] = "56edde775a5d57818aa0a07b4f723536",
models = {
["apimodel.lua"] = "3c401de18691b1222b0ad253958260ee",
["apimodelbuilder.lua"] = "4c4a3c0b48b404973542dd99f994eb2c",
["internalmodel.lua"] = "a1a21e50af8db0f0a0b9d164ccc08853",
["internalmodelbuilder.mlua"] = "ff95dfca573ccc1c19a79434e96a492d",
["ldparser.lua"] = "538904a3adbfff4ff83deda029847323"
},
template = {
["file.lua"] = "41f095bc049ef161060d8e3b4ac9de63",
index = {
["recordtypedef.lua"] = "0977ff0048a837389c2ac10285eb1ce1"
},
["index.lua"] = "5a3b3cface3b1fd9cb2d56f1edd5487b",
["item.lua"] = "5d5a6d9bffd8935c4ed283105ede331b",
["page.lua"] = "351f4a7215272f7e448faeece4945bc0",
["recordtypedef.lua"] = "69938e1d60e94eed7f95b0999f1386ca",
["usage.lua"] = "979503deb84877cb221130a5be7c1535",
["utils.lua"] = "ad97fb4e3de9fb6480b25cdd877b50d9"
},
["templateengine.lua"] = "09bfc6350e14f4ab509d14fb0fb295c0"
},
["luadocumentor-0.1.5-1.rockspec"] = "4ba1b88898dce89e7fd8fb6a700496a4"
}

View File

@ -0,0 +1,212 @@
body {
margin-left: 1em;
margin-right: 1em;
font-family: arial, helvetica, geneva, sans-serif;
background-color:#ffffff; margin:0px;
}
code {
font-family: "Andale Mono", monospace;
}
tt {
font-family: "Andale Mono", monospace;
}
body, td, th { font-size: 11pt; }
h1, h2, h3, h4 { margin-left: 0em; }
textarea, pre, tt { font-size:10pt; }
body, td, th { color:#000000; }
small { font-size:0.85em; }
h1 { font-size:1.5em; }
h2 { font-size:1.25em; }
h3 { font-size:1.15em; }
h4 { font-size:1.06em; }
a:link { font-weight:bold; color: #004080; text-decoration: none; }
a:visited { font-weight:bold; color: #006699; text-decoration: none; }
a:link:hover { text-decoration:underline; }
hr { color:#cccccc }
img { border-width: 0px; }
h3 { padding-top: 1em; }
p { margin-left: 1em; }
p.name {
font-family: "Andale Mono", monospace;
padding-top: 1em;
margin-left: 0em;
}
blockquote { margin-left: 3em; }
.example {
background-color: rgb(245, 245, 245);
border-top-width: 1px;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
border-top-style: solid;
border-right-style: solid;
border-bottom-style: solid;
border-left-style: solid;
border-top-color: silver;
border-right-color: silver;
border-bottom-color: silver;
border-left-color: silver;
padding: 1em;
margin-left: 1em;
margin-right: 1em;
font-family: "Andale Mono", monospace;
font-size: smaller;
}
hr {
margin-left: 0em;
background: #00007f;
border: 0px;
height: 1px;
}
ul { list-style-type: disc; }
table.index { border: 1px #00007f; }
table.index td { text-align: left; vertical-align: top; }
table.index ul { padding-top: 0em; margin-top: 0em; }
table {
border: 1px solid black;
border-collapse: collapse;
margin-left: auto;
margin-right: auto;
}
th {
border: 1px solid black;
padding: 0.5em;
}
td {
border: 1px solid black;
padding: 0.5em;
}
div.header, div.footer { margin-left: 0em; }
#container {
margin-left: 1em;
margin-right: 1em;
background-color: #f0f0f0;
}
#product {
text-align: center;
border-bottom: 1px solid #cccccc;
background-color: #ffffff;
}
#product big {
font-size: 2em;
}
#product_logo {
}
#product_name {
}
#product_description {
}
#main {
background-color: #f0f0f0;
border-left: 2px solid #cccccc;
}
#navigation {
float: left;
width: 12em;
margin: 0;
vertical-align: top;
background-color: #f0f0f0;
overflow:visible;
}
#navigation h1 {
background-color:#e7e7e7;
font-size:1.1em;
color:#000000;
text-align:left;
margin:0px;
padding:0.2em;
border-top:1px solid #dddddd;
border-bottom:1px solid #dddddd;
}
#navigation ul {
font-size:1em;
list-style-type: none;
padding: 0;
margin: 1px;
}
#navigation li {
text-indent: -1em;
margin: 0em 0em 0em 0.5em;
display: block;
padding: 3px 0px 0px 12px;
}
#navigation li li a {
padding: 0px 3px 0px -1em;
}
#content {
margin-left: 12em;
padding: 1em;
border-left: 2px solid #cccccc;
border-right: 2px solid #cccccc;
background-color: #ffffff;
}
#about {
clear: both;
margin: 0;
padding: 5px;
border-top: 2px solid #cccccc;
background-color: #ffffff;
}
@media print {
body {
font: 10pt "Times New Roman", "TimeNR", Times, serif;
}
a {
font-weight:bold; color: #004080; text-decoration: underline;
}
#main {
background-color: #ffffff; border-left: 0px;
}
#container {
margin-left: 2%; margin-right: 2%; background-color: #ffffff;
}
#content {
margin-left: 0px; padding: 1em; border-left: 0px; border-right: 0px; background-color: #ffffff;
}
#navigation {
display: none;
}
#product_logo {
display: none;
}
#about img {
display: none;
}
.example {
font-family: "Andale Mono", monospace;
font-size: 8pt;
page-break-inside: avoid;
}
}

View File

@ -0,0 +1,103 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>LuaFileSystem</title>
<link rel="stylesheet" href="doc.css" type="text/css"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<div id="container">
<div id="product">
<div id="product_logo">
<a href="http://www.keplerproject.org">
<img alt="LuaFileSystem" src="luafilesystem.png"/>
</a>
</div>
<div id="product_name"><big><strong>LuaFileSystem</strong></big></div>
<div id="product_description">File System Library for the Lua Programming Language</div>
</div> <!-- id="product" -->
<div id="main">
<div id="navigation">
<h1>LuaFileSystem</h1>
<ul>
<li><a href="index.html">Home</a>
<ul>
<li><a href="index.html#overview">Overview</a></li>
<li><a href="index.html#status">Status</a></li>
<li><a href="index.html#download">Download</a></li>
<li><a href="index.html#history">History</a></li>
<li><a href="index.html#credits">Credits</a></li>
<li><a href="index.html#contact">Contact us</a></li>
</ul>
</li>
<li><a href="manual.html">Manual</a>
<ul>
<li><a href="manual.html#introduction">Introduction</a></li>
<li><a href="manual.html#building">Building</a></li>
<li><a href="manual.html#installation">Installation</a></li>
<li><a href="manual.html#reference">Reference</a></li>
</ul>
</li>
<li><strong>Examples</strong></li>
<li><a href="https://github.com/keplerproject/luafilesystem">Project</a>
<ul>
<li><a href="https://github.com/keplerproject/luafilesystem/issues">Bug Tracker</a></li>
<li><a href="https://github.com/keplerproject/luafilesystem">Git</a></li>
</ul>
</li>
<li><a href="license.html">License</a></li>
</ul>
</div> <!-- id="navigation" -->
<div id="content">
<h2><a name="example"></a>Examples</h2>
<h3>Directory iterator</h3>
<p>The following example iterates over a directory and recursively lists the
attributes for each file inside it.</p>
<pre class="example">
local lfs = require"lfs"
function attrdir (path)
for file in lfs.dir(path) do
if file ~= "." and file ~= ".." then
local f = path..'/'..file
print ("\t "..f)
local attr = lfs.attributes (f)
assert (type(attr) == "table")
if attr.mode == "directory" then
attrdir (f)
else
for name, value in pairs(attr) do
print (name, value)
end
end
end
end
end
attrdir (".")
</pre>
</div> <!-- id="content" -->
</div> <!-- id="main" -->
<div id="about">
<p><a href="http://validator.w3.org/check?uri=referer">Valid XHTML 1.0!</a></p>
<p><small>$Id: examples.html,v 1.8 2007/12/14 15:28:04 carregal Exp $</small></p>
</div> <!-- id="about" -->
</div> <!-- id="container" -->
</body>
</html>

View File

@ -0,0 +1,218 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>LuaFileSystem</title>
<link rel="stylesheet" href="doc.css" type="text/css"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<div id="container">
<div id="product">
<div id="product_logo">
<a href="http://www.keplerproject.org">
<img alt="LuaFileSystem" src="luafilesystem.png"/>
</a>
</div>
<div id="product_name"><big><strong>LuaFileSystem</strong></big></div>
<div id="product_description">File System Library for the Lua Programming Language</div>
</div> <!-- id="product" -->
<div id="main">
<div id="navigation">
<h1>LuaFileSystem</h1>
<ul>
<li><strong>Home</strong>
<ul>
<li><a href="index.html#overview">Overview</a></li>
<li><a href="index.html#status">Status</a></li>
<li><a href="index.html#download">Download</a></li>
<li><a href="index.html#history">History</a></li>
<li><a href="index.html#credits">Credits</a></li>
<li><a href="index.html#contact">Contact us</a></li>
</ul>
</li>
<li><a href="manual.html">Manual</a>
<ul>
<li><a href="manual.html#introduction">Introduction</a></li>
<li><a href="manual.html#building">Building</a></li>
<li><a href="manual.html#installation">Installation</a></li>
<li><a href="manual.html#reference">Reference</a></li>
</ul>
</li>
<li><a href="examples.html">Examples</a></li>
<li><a href="https://github.com/keplerproject/luafilesystem">Project</a>
<ul>
<li><a href="https://github.com/keplerproject/luafilesystem/issues">Bug Tracker</a></li>
<li><a href="https://github.com/keplerproject/luafilesystem">Git</a></li>
</ul>
</li>
<li><a href="license.html">License</a></li>
</ul>
</div> <!-- id="navigation" -->
<div id="content">
<h2><a name="overview"></a>Overview</h2>
<p>LuaFileSystem is a <a href="http://www.lua.org">Lua</a> library
developed to complement the set of functions related to file
systems offered by the standard Lua distribution.</p>
<p>LuaFileSystem offers a portable way to access
the underlying directory structure and file attributes.</p>
<p>LuaFileSystem is free software and uses the same
<a href="license.html">license</a> as Lua 5.1.</p>
<h2><a name="status"></a>Status</h2>
<p>Current version is 1.6.3. It works with Lua 5.1, 5.2 and 5.3.</p>
<h2><a name="download"></a>Download</h2>
<p>LuaFileSystem source can be downloaded from its
<a href="http://github.com/keplerproject/luafilesystem">Github</a>
page.</p>
<h2><a name="history"></a>History</h2>
<dl class="history">
<dt><strong>Version 1.6.3</strong> [15/Jan/2015]</dt>
<dd><ul>
<li>Lua 5.3 support.</li>
<li>Assorted bugfixes.</li>
</ul></dd>
<dt><strong>Version 1.6.2</strong> [??/Oct/2012]</dt>
<dd><ul>
<li>Full Lua 5.2 compatibility (with Lua 5.1 fallbacks)</li>
</ul></dd>
<dt><strong>Version 1.6.1</strong> [01/Oct/2012]</dt>
<dd><ul>
<li>fix build for Lua 5.2</li>
</ul></dd>
<dt><strong>Version 1.6.0</strong> [26/Sep/2012]</dt>
<dd><ul>
<li>getcwd fix for Android</li>
<li>support for Lua 5.2</li>
<li>add lfs.link</li>
<li>other bug fixes</li>
</ul></dd>
<dt><strong>Version 1.5.0</strong> [20/Oct/2009]</dt>
<dd><ul>
<li>Added explicit next and close methods to second return value of lfs.dir
(the directory object), for explicit iteration or explicit closing.</li>
<li>Added directory locking via lfs.lock_dir function (see the <a href="manual.html">manual</a>).</li>
</ul></dd>
<dt><strong>Version 1.4.2</strong> [03/Feb/2009]</dt>
<dd>
<ul>
<li>fixed bug [<a href="http://luaforge.net/tracker/?func=detail&amp;group_id=66&amp;aid=13198&amp;atid=356">#13198</a>]
lfs.attributes(filename, 'size') overflow on files > 2 Gb again (bug report and patch by KUBO Takehiro).</li>
<li>fixed bug [<a href="http://luaforge.net/tracker/?group_id=66&amp;atid=356&amp;func=detail&amp;aid=39794">#39794</a>]
Compile error on Solaris 10 (bug report and patch by Aaron B).</li>
<li>fixed compilation problems with Borland C.</li>
</ul>
</dd>
<dt><strong>Version 1.4.1</strong> [07/May/2008]</dt>
<dd>
<ul>
<li>documentation review</li>
<li>fixed Windows compilation issues</li>
<li>fixed bug in the Windows tests (patch by Shmuel Zeigerman)</li>
<li>fixed bug [<a href="http://luaforge.net/tracker/?func=detail&amp;group_id=66&amp;aid=2185&amp;atid=356">#2185</a>]
<code>lfs.attributes(filename, 'size')</code> overflow on files > 2 Gb
</li>
</ul>
</dd>
<dt><strong>Version 1.4.0</strong> [13/Feb/2008]</dt>
<dd>
<ul>
<li>added function
<a href="manual.html#setmode"><code>lfs.setmode</code></a>
(works only in Windows systems).</li>
<li><a href="manual.html#attributes"><code>lfs.attributes</code></a>
raises an error if attribute does not exist</li>
</ul>
</dd>
<dt><strong>Version 1.3.0</strong> [26/Oct/2007]</dt>
<dd>
<ul>
<li>added function
<a href="manual.html#symlinkattributes"><code>lfs.symlinkattributes</code></a>
(works only in non Windows systems).</li>
</ul>
</dd>
<dt><strong>Version 1.2.1</strong> [08/May/2007]</dt>
<dd>
<ul>
<li>compatible only with Lua 5.1 (Lua 5.0 support was dropped)</li>
</ul>
</dd>
<dt><strong>Version 1.2</strong> [15/Mar/2006]</dt>
<dd>
<ul>
<li>added optional argument to
<a href="manual.html#attributes"><code>lfs.attributes</code></a></li>
<li>added function
<a href="manual.html#rmdir"><code>lfs.rmdir</code></a></li>
<li>bug correction on <a href="manual.html#dir"><code>lfs.dir</code></a></li>
</ul>
</dd>
<dt><strong>Version 1.1</strong> [30/May/2005]</dt>
<dd>
<ul>
<li>added function <a href="manual.html#touch"><code>lfs.touch</code></a>.</li>
</ul>
</dd>
<dt><strong>Version 1.0</strong> [21/Jan/2005]</dt>
<dd />
<dt><strong>Version 1.0 Beta</strong> [10/Nov/2004]</dt>
<dd />
</dl>
<h2><a name="credits"></a>Credits</h2>
<p>LuaFileSystem was designed by Roberto Ierusalimschy,
Andr&eacute; Carregal and Tom&aacute;s Guisasola as part of the
<a href="http://www.keplerproject.org">Kepler Project</a>,
which holds its copyright. LuaFileSystem is currently maintained by F&aacute;bio Mascarenhas.</p>
<h2><a name="contact"></a>Contact us</h2>
<p>For more information please
<a href="mailto:info-NO-SPAM-THANKS@keplerproject.org">contact us</a>.
Comments are welcome!</p>
<p>You can also reach other Kepler developers and users on the Kepler Project
<a href="http://luaforge.net/mail/?group_id=104">mailing list</a>.</p>
</div> <!-- id="content" -->
</div> <!-- id="main" -->
<div id="about">
<p><a href="http://validator.w3.org/check?uri=referer">Valid XHTML 1.0!</a></p>
<p><small>$Id: index.html,v 1.44 2009/02/04 21:21:33 carregal Exp $</small></p>
</div> <!-- id="about" -->
</div> <!-- id="container" -->
</body>
</html>

View File

@ -0,0 +1,122 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>LuaFileSystem</title>
<link rel="stylesheet" href="doc.css" type="text/css"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<div id="container">
<div id="product">
<div id="product_logo">
<a href="http://www.keplerproject.org">
<img alt="LuaFileSystem" src="luafilesystem.png"/>
</a>
</div>
<div id="product_name"><big><strong>LuaFileSystem</strong></big></div>
<div id="product_description">File System Library for the Lua Programming Language</div>
</div> <!-- id="product" -->
<div id="main">
<div id="navigation">
<h1>LuaFileSystem</h1>
<ul>
<li><a href="index.html">Home</a>
<ul>
<li><a href="index.html#overview">Overview</a></li>
<li><a href="index.html#status">Status</a></li>
<li><a href="index.html#download">Download</a></li>
<li><a href="index.html#history">History</a></li>
<li><a href="index.html#credits">Credits</a></li>
<li><a href="index.html#contact">Contact us</a></li>
</ul>
</li>
<li><a href="manual.html">Manual</a>
<ul>
<li><a href="manual.html#introduction">Introduction</a></li>
<li><a href="manual.html#building">Building</a></li>
<li><a href="manual.html#installation">Installation</a></li>
<li><a href="manual.html#reference">Reference</a></li>
</ul>
</li>
<li><a href="examples.html">Examples</a></li>
<li><a href="https://github.com/keplerproject/luafilesystem">Project</a>
<ul>
<li><a href="https://github.com/keplerproject/luafilesystem/issues/">Bug Tracker</a></li>
<li><a href="https://github.com/keplerproject/luafilesystem">Git</a></li>
</ul>
</li>
<li><strong>License</strong></li>
</ul>
</div> <!-- id="navigation" -->
<div id="content">
<h1>License</h1>
<p>
LuaFileSystem is free software: it can be used for both academic
and commercial purposes at absolutely no cost. There are no
royalties or GNU-like "copyleft" restrictions. LuaFileSystem
qualifies as
<a href="http://www.opensource.org/docs/definition.html">Open Source</a>
software.
Its licenses are compatible with
<a href="http://www.gnu.org/licenses/gpl.html">GPL</a>.
LuaFileSystem is not in the public domain and the
<a href="http://www.keplerproject.org">Kepler Project</a>
keep its copyright.
The legal details are below.
</p>
<p>The spirit of the license is that you are free to use
LuaFileSystem for any purpose at no cost without having to ask us.
The only requirement is that if you do use LuaFileSystem, then you
should give us credit by including the appropriate copyright notice
somewhere in your product or its documentation.</p>
<p>The LuaFileSystem library is designed and implemented by Roberto
Ierusalimschy, Andr&eacute; Carregal and Tom&aacute;s Guisasola.
The implementation is not derived from licensed software.</p>
<hr/>
<p>Copyright &copy; 2003 Kepler Project.</p>
<p>Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:</p>
<p>The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.</p>
<p>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.</p>
</div> <!-- id="content" -->
</div> <!-- id="main" -->
<div id="about">
<p><a href="http://validator.w3.org/check?uri=referer">Valid XHTML 1.0!</a></p>
<p><small>$Id: license.html,v 1.13 2008/02/11 22:42:21 carregal Exp $</small></p>
</div><!-- id="about" -->
</div><!-- id="container" -->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@ -0,0 +1,280 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>LuaFileSystem</title>
<link rel="stylesheet" href="doc.css" type="text/css"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<div id="container">
<div id="product">
<div id="product_logo">
<a href="http://www.keplerproject.org"><img alt="LuaFileSystem" src="luafilesystem.png"/></a>
</div>
<div id="product_name"><big><strong>LuaFileSystem</strong></big></div>
<div id="product_description">File System Library for the Lua Programming Language</div>
</div> <!-- id="product" -->
<div id="main">
<div id="navigation">
<h1>LuaFileSystem</h1>
<ul>
<li><a href="index.html">Home</a>
<ul>
<li><a href="index.html#overview">Overview</a></li>
<li><a href="index.html#status">Status</a></li>
<li><a href="index.html#download">Download</a></li>
<li><a href="index.html#history">History</a></li>
<li><a href="index.html#credits">Credits</a></li>
<li><a href="index.html#contact">Contact us</a></li>
</ul>
</li>
<li><strong>Manual</strong>
<ul>
<li><a href="manual.html#introduction">Introduction</a></li>
<li><a href="manual.html#building">Building</a></li>
<li><a href="manual.html#installation">Installation</a></li>
<li><a href="manual.html#reference">Reference</a></li>
</ul>
</li>
<li><a href="examples.html">Examples</a></li>
<li><a href="https://github.com/keplerproject/luafilesystem">Project</a>
<ul>
<li><a href="https://github.com/keplerproject/luafilesystem/issues">Bug Tracker</a></li>
<li><a href="https://github.com/keplerproject/luafilesystem">Git</a></li>
</ul>
</li>
<li><a href="license.html">License</a></li>
</ul>
</div> <!-- id="navigation" -->
<div id="content">
<h2><a name="introduction"></a>Introduction</h2>
<p>LuaFileSystem is a <a href="http://www.lua.org">Lua</a> library
developed to complement the set of functions related to file
systems offered by the standard Lua distribution.</p>
<p>LuaFileSystem offers a portable way to access
the underlying directory structure and file attributes.</p>
<h2><a name="building"></a>Building</h2>
<p>
LuaFileSystem should be built with Lua 5.1 so the language library
and header files for the target version must be installed properly.
</p>
<p>
LuaFileSystem offers a Makefile and a separate configuration file,
<code>config</code>,
which should be edited to suit your installation before running
<code>make</code>.
The file has some definitions like paths to the external libraries,
compiler options and the like.
</p>
<p>On Windows, the C runtime used to compile LuaFileSystem must be the same
runtime that Lua uses, or some LuaFileSystem functions will not work.</p>
<h2><a name="installation"></a>Installation</h2>
<p>The easiest way to install LuaFileSystem is to use LuaRocks:</p>
<pre class="example">
luarocks install luafilesystem
</pre>
<p>If you prefer to install LuaFileSystem manually, the compiled binary should be copied to a directory in your
<a href="http://www.lua.org/manual/5.1/manual.html#pdf-package.cpath">C path</a>.</p>
<h2><a name="reference"></a>Reference</h2>
<p>
LuaFileSystem offers the following functions:
</p>
<dl class="reference">
<dt><a name="attributes"></a><strong><code>lfs.attributes (filepath [, aname])</code></strong></dt>
<dd>Returns a table with the file attributes corresponding to
<code>filepath</code> (or <code>nil</code> followed by an error message
in case of error).
If the second optional argument is given, then only the value of the
named attribute is returned (this use is equivalent to
<code>lfs.attributes(filepath).aname</code>, but the table is not created
and only one attribute is retrieved from the O.S.).
The attributes are described as follows;
attribute <code>mode</code> is a string, all the others are numbers,
and the time related attributes use the same time reference of
<a href="http://www.lua.org/manual/5.1/manual.html#pdf-os.time"><code>os.time</code></a>:
<dl>
<dt><strong><code>dev</code></strong></dt>
<dd>on Unix systems, this represents the device that the inode resides on. On Windows systems,
represents the drive number of the disk containing the file</dd>
<dt><strong><code>ino</code></strong></dt>
<dd>on Unix systems, this represents the inode number. On Windows systems this has no meaning</dd>
<dt><strong><code>mode</code></strong></dt>
<dd>string representing the associated protection mode (the values could be
<code>file</code>, <code>directory</code>, <code>link</code>, <code>socket</code>,
<code>named pipe</code>, <code>char device</code>, <code>block device</code> or
<code>other</code>)</dd>
<dt><strong><code>nlink</code></strong></dt>
<dd>number of hard links to the file</dd>
<dt><strong><code>uid</code></strong></dt>
<dd>user-id of owner (Unix only, always 0 on Windows)</dd>
<dt><strong><code>gid</code></strong></dt>
<dd>group-id of owner (Unix only, always 0 on Windows)</dd>
<dt><strong><code>rdev</code></strong></dt>
<dd>on Unix systems, represents the device type, for special file inodes.
On Windows systems represents the same as <code>dev</code></dd>
<dt><strong><code>access</code></strong></dt>
<dd>time of last access</dd>
<dt><strong><code>modification</code></strong></dt>
<dd>time of last data modification</dd>
<dt><strong><code>change</code></strong></dt>
<dd>time of last file status change</dd>
<dt><strong><code>size</code></strong></dt>
<dd>file size, in bytes</dd>
<dt><strong><code>blocks</code></strong></dt>
<dd>block allocated for file; (Unix only)</dd>
<dt><strong><code>blksize</code></strong></dt>
<dd>optimal file system I/O blocksize; (Unix only)</dd>
</dl>
This function uses <code>stat</code> internally thus if the given
<code>filepath</code> is a symbolic link, it is followed (if it points to
another link the chain is followed recursively) and the information
is about the file it refers to.
To obtain information about the link itself, see function
<a href="#symlinkattributes">lfs.symlinkattributes</a>.
</dd>
<dt><a name="chdir"></a><strong><code>lfs.chdir (path)</code></strong></dt>
<dd>Changes the current working directory to the given
<code>path</code>.<br />
Returns <code>true</code> in case of success or <code>nil</code> plus an
error string.</dd>
<dt><a name="chdir"></a><strong><code>lfs.lock_dir(path, [seconds_stale])</code></strong></dt>
<dd>Creates a lockfile (called lockfile.lfs) in <code>path</code> if it does not
exist and returns the lock. If the lock already exists checks if
it's stale, using the second parameter (default for the second
parameter is <code>INT_MAX</code>, which in practice means the lock will never
be stale. To free the the lock call <code>lock:free()</code>. <br/>
In case of any errors it returns nil and the error message. In
particular, if the lock exists and is not stale it returns the
"File exists" message.</dd>
<dt><a name="getcwd"></a><strong><code>lfs.currentdir ()</code></strong></dt>
<dd>Returns a string with the current working directory or <code>nil</code>
plus an error string.</dd>
<dt><a name="dir"></a><strong><code>iter, dir_obj = lfs.dir (path)</code></strong></dt>
<dd>
Lua iterator over the entries of a given directory.
Each time the iterator is called with <code>dir_obj</code> it returns a directory entry's name as a string, or
<code>nil</code> if there are no more entries. You can also iterate by calling <code>dir_obj:next()</code>, and
explicitly close the directory before the iteration finished with <code>dir_obj:close()</code>.
Raises an error if <code>path</code> is not a directory.
</dd>
<dt><a name="lock"></a><strong><code>lfs.lock (filehandle, mode[, start[, length]])</code></strong></dt>
<dd>Locks a file or a part of it. This function works on <em>open files</em>; the
file handle should be specified as the first argument.
The string <code>mode</code> could be either
<code>r</code> (for a read/shared lock) or <code>w</code> (for a
write/exclusive lock). The optional arguments <code>start</code>
and <code>length</code> can be used to specify a starting point and
its length; both should be numbers.<br />
Returns <code>true</code> if the operation was successful; in
case of error, it returns <code>nil</code> plus an error string.
</dd>
<dt><a name="link"></a><strong><code>lfs.link (old, new[, symlink])</code></strong></dt>
<dd>Creates a link. The first argument is the object to link to
and the second is the name of the link. If the optional third
argument is true, the link will by a symbolic link (by default, a
hard link is created).
</dd>
<dt><a name="mkdir"></a><strong><code>lfs.mkdir (dirname)</code></strong></dt>
<dd>Creates a new directory. The argument is the name of the new
directory.<br />
Returns <code>true</code> if the operation was successful;
in case of error, it returns <code>nil</code> plus an error string.
</dd>
<dt><a name="rmdir"></a><strong><code>lfs.rmdir (dirname)</code></strong></dt>
<dd>Removes an existing directory. The argument is the name of the directory.<br />
Returns <code>true</code> if the operation was successful;
in case of error, it returns <code>nil</code> plus an error string.</dd>
<dt><a name="setmode"></a><strong><code>lfs.setmode (file, mode)</code></strong></dt>
<dd>Sets the writing mode for a file. The mode string can be either <code>"binary"</code> or <code>"text"</code>.
Returns <code>true</code> followed the previous mode string for the file, or
<code>nil</code> followed by an error string in case of errors.
On non-Windows platforms, where the two modes are identical,
setting the mode has no effect, and the mode is always returned as <code>binary</code>.
</dd>
<dt><a name="symlinkattributes"></a><strong><code>lfs.symlinkattributes (filepath [, aname])</code></strong></dt>
<dd>Identical to <a href="#attributes">lfs.attributes</a> except that
it obtains information about the link itself (not the file it refers to).
On Windows this function does not yet support links, and is identical to
<code>lfs.attributes</code>.
</dd>
<dt><a name="touch"></a><strong><code>lfs.touch (filepath [, atime [, mtime]])</code></strong></dt>
<dd>Set access and modification times of a file. This function is
a bind to <code>utime</code> function. The first argument is the
filename, the second argument (<code>atime</code>) is the access time,
and the third argument (<code>mtime</code>) is the modification time.
Both times are provided in seconds (which should be generated with
Lua standard function <code>os.time</code>).
If the modification time is omitted, the access time provided is used;
if both times are omitted, the current time is used.<br />
Returns <code>true</code> if the operation was successful;
in case of error, it returns <code>nil</code> plus an error string.
</dd>
<dt><a name="unlock"></a><strong><code>lfs.unlock (filehandle[, start[, length]])</code></strong></dt>
<dd>Unlocks a file or a part of it. This function works on
<em>open files</em>; the file handle should be specified as the first
argument. The optional arguments <code>start</code> and
<code>length</code> can be used to specify a starting point and its
length; both should be numbers.<br />
Returns <code>true</code> if the operation was successful;
in case of error, it returns <code>nil</code> plus an error string.
</dd>
</dl>
</div> <!-- id="content" -->
</div> <!-- id="main" -->
<div id="about">
<p><a href="http://validator.w3.org/check?uri=referer">Valid XHTML 1.0!</a></p>
<p><small>$Id: manual.html,v 1.45 2009/06/03 20:53:55 mascarenhas Exp $</small></p>
</div> <!-- id="about" -->
</div> <!-- id="container" -->
</body>
</html>

View File

@ -0,0 +1,29 @@
package = "LuaFileSystem"
version = "1.6.3-2"
source = {
url = "git://github.com/keplerproject/luafilesystem",
tag = "v_1_6_3"
}
description = {
summary = "File System Library for the Lua Programming Language",
detailed = [[
LuaFileSystem is a Lua library developed to complement the set of
functions related to file systems offered by the standard Lua
distribution. LuaFileSystem offers a portable way to access the
underlying directory structure and file attributes.
]],
homepage = "http://keplerproject.github.io/luafilesystem",
license = "MIT/X11"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
lfs = "src/lfs.c"
},
copy_directories = {
"doc", "tests"
}
}

View File

@ -0,0 +1,19 @@
rock_manifest = {
doc = {
us = {
["doc.css"] = "d0a913514fb190240b3b4033d105cbc0",
["examples.html"] = "5832f72021728374cf57b621d62ce0ff",
["index.html"] = "96885bdda963939f0a363b5fa6b16b59",
["license.html"] = "e3a756835cb7c8ae277d5e513c8e32ee",
["luafilesystem.png"] = "81e923e976e99f894ea0aa8b52baff29",
["manual.html"] = "d6473799b73ce486c3ea436586cb3b34"
}
},
lib = {
["lfs.dll"] = "c0e2145e1ef2815ae5fae01454291b66"
},
["luafilesystem-1.6.3-2.rockspec"] = "eb0ef7c190516892eb8357af799eea5f",
tests = {
["test.lua"] = "7b4ddb5bdb7e0b1b1ed0150d473535c9"
}
}

View File

@ -0,0 +1,175 @@
#!/usr/bin/env lua5.1
local tmp = "/tmp"
local sep = string.match (package.config, "[^\n]+")
local upper = ".."
local lfs = require"lfs"
print (lfs._VERSION)
io.write(".")
io.flush()
function attrdir (path)
for file in lfs.dir(path) do
if file ~= "." and file ~= ".." then
local f = path..sep..file
print ("\t=> "..f.." <=")
local attr = lfs.attributes (f)
assert (type(attr) == "table")
if attr.mode == "directory" then
attrdir (f)
else
for name, value in pairs(attr) do
print (name, value)
end
end
end
end
end
-- Checking changing directories
local current = assert (lfs.currentdir())
local reldir = string.gsub (current, "^.*%"..sep.."([^"..sep.."])$", "%1")
assert (lfs.chdir (upper), "could not change to upper directory")
assert (lfs.chdir (reldir), "could not change back to current directory")
assert (lfs.currentdir() == current, "error trying to change directories")
assert (lfs.chdir ("this couldn't be an actual directory") == nil, "could change to a non-existent directory")
io.write(".")
io.flush()
-- Changing creating and removing directories
local tmpdir = current..sep.."lfs_tmp_dir"
local tmpfile = tmpdir..sep.."tmp_file"
-- Test for existence of a previous lfs_tmp_dir
-- that may have resulted from an interrupted test execution and remove it
if lfs.chdir (tmpdir) then
assert (lfs.chdir (upper), "could not change to upper directory")
assert (os.remove (tmpfile), "could not remove file from previous test")
assert (lfs.rmdir (tmpdir), "could not remove directory from previous test")
end
io.write(".")
io.flush()
-- tries to create a directory
assert (lfs.mkdir (tmpdir), "could not make a new directory")
local attrib, errmsg = lfs.attributes (tmpdir)
if not attrib then
error ("could not get attributes of file `"..tmpdir.."':\n"..errmsg)
end
local f = io.open(tmpfile, "w")
f:close()
io.write(".")
io.flush()
-- Change access time
local testdate = os.time({ year = 2007, day = 10, month = 2, hour=0})
assert (lfs.touch (tmpfile, testdate))
local new_att = assert (lfs.attributes (tmpfile))
assert (new_att.access == testdate, "could not set access time")
assert (new_att.modification == testdate, "could not set modification time")
io.write(".")
io.flush()
-- Change access and modification time
local testdate1 = os.time({ year = 2007, day = 10, month = 2, hour=0})
local testdate2 = os.time({ year = 2007, day = 11, month = 2, hour=0})
assert (lfs.touch (tmpfile, testdate2, testdate1))
local new_att = assert (lfs.attributes (tmpfile))
assert (new_att.access == testdate2, "could not set access time")
assert (new_att.modification == testdate1, "could not set modification time")
io.write(".")
io.flush()
-- Checking link (does not work on Windows)
if lfs.link (tmpfile, "_a_link_for_test_", true) then
assert (lfs.attributes"_a_link_for_test_".mode == "file")
assert (lfs.symlinkattributes"_a_link_for_test_".mode == "link")
assert (lfs.link (tmpfile, "_a_hard_link_for_test_"))
assert (lfs.attributes (tmpfile, "nlink") == 2)
assert (os.remove"_a_link_for_test_")
assert (os.remove"_a_hard_link_for_test_")
end
io.write(".")
io.flush()
-- Checking text/binary modes (only has an effect in Windows)
local f = io.open(tmpfile, "w")
local result, mode = lfs.setmode(f, "binary")
assert(result) -- on non-Windows platforms, mode is always returned as "binary"
result, mode = lfs.setmode(f, "text")
assert(result and mode == "binary")
f:close()
io.write(".")
io.flush()
-- Restore access time to current value
assert (lfs.touch (tmpfile, attrib.access, attrib.modification))
new_att = assert (lfs.attributes (tmpfile))
assert (new_att.access == attrib.access)
assert (new_att.modification == attrib.modification)
io.write(".")
io.flush()
-- Check consistency of lfs.attributes values
local attr = lfs.attributes (tmpfile)
for key, value in pairs(attr) do
assert (value == lfs.attributes (tmpfile, key),
"lfs.attributes values not consistent")
end
-- Remove new file and directory
assert (os.remove (tmpfile), "could not remove new file")
assert (lfs.rmdir (tmpdir), "could not remove new directory")
assert (lfs.mkdir (tmpdir..sep.."lfs_tmp_dir") == nil, "could create a directory inside a non-existent one")
io.write(".")
io.flush()
-- Trying to get attributes of a non-existent file
assert (lfs.attributes ("this couldn't be an actual file") == nil, "could get attributes of a non-existent file")
assert (type(lfs.attributes (upper)) == "table", "couldn't get attributes of upper directory")
io.write(".")
io.flush()
-- Stressing directory iterator
count = 0
for i = 1, 4000 do
for file in lfs.dir (tmp) do
count = count + 1
end
end
io.write(".")
io.flush()
-- Stressing directory iterator, explicit version
count = 0
for i = 1, 4000 do
local iter, dir = lfs.dir(tmp)
local file = dir:next()
while file do
count = count + 1
file = dir:next()
end
assert(not pcall(dir.next, dir))
end
io.write(".")
io.flush()
-- directory explicit close
local iter, dir = lfs.dir(tmp)
dir:close()
assert(not pcall(dir.next, dir))
print"Ok!"

View File

@ -0,0 +1,653 @@
#!/usr/bin/env lua
---------
-- LuaSrcDiet
--
-- Compresses Lua source code by removing unnecessary characters.
-- For Lua 5.1+ source code.
--
-- **Notes:**
--
-- * Remember to update version and date information below (MSG_TITLE).
-- * TODO: passing data tables around is a horrific mess.
-- * TODO: to implement pcall() to properly handle lexer etc. errors.
-- * TODO: need some automatic testing for a semblance of sanity.
-- * TODO: the plugin module is highly experimental and unstable.
----
local equiv = require "luasrcdiet.equiv"
local fs = require "luasrcdiet.fs"
local llex = require "luasrcdiet.llex"
local lparser = require "luasrcdiet.lparser"
local luasrcdiet = require "luasrcdiet.init"
local optlex = require "luasrcdiet.optlex"
local optparser = require "luasrcdiet.optparser"
local byte = string.byte
local concat = table.concat
local find = string.find
local fmt = string.format
local gmatch = string.gmatch
local match = string.match
local print = print
local rep = string.rep
local sub = string.sub
local plugin
local LUA_VERSION = match(_VERSION, " (5%.[123])$") or "5.1"
-- Is --opt-binequiv available for this Lua version?
local BIN_EQUIV_AVAIL = LUA_VERSION == "5.1" and not package.loaded.jit
---------------------- Messages and textual data ----------------------
local MSG_TITLE = fmt([[
LuaSrcDiet: Puts your Lua 5.1+ source code on a diet
Version %s <%s>
]], luasrcdiet._VERSION, luasrcdiet._HOMEPAGE)
local MSG_USAGE = [[
usage: luasrcdiet [options] [filenames]
example:
>luasrcdiet myscript.lua -o myscript_.lua
options:
-v, --version prints version information
-h, --help prints usage information
-o <file> specify file name to write output
-s <suffix> suffix for output files (default '_')
--keep <msg> keep block comment with <msg> inside
--plugin <module> run <module> in plugin/ directory
- stop handling arguments
(optimization levels)
--none all optimizations off (normalizes EOLs only)
--basic lexer-based optimizations only
--maximum maximize reduction of source
(informational)
--quiet process files quietly
--read-only read file and print token stats only
--dump-lexer dump raw tokens from lexer to stdout
--dump-parser dump variable tracking tables from parser
--details extra info (strings, numbers, locals)
features (to disable, insert 'no' prefix like --noopt-comments):
%s
default settings:
%s]]
-- Optimization options, for ease of switching on and off.
--
-- * Positive to enable optimization, negative (no) to disable.
-- * These options should follow --opt-* and --noopt-* style for now.
local OPTION = [[
--opt-comments,'remove comments and block comments'
--opt-whitespace,'remove whitespace excluding EOLs'
--opt-emptylines,'remove empty lines'
--opt-eols,'all above, plus remove unnecessary EOLs'
--opt-strings,'optimize strings and long strings'
--opt-numbers,'optimize numbers'
--opt-locals,'optimize local variable names'
--opt-entropy,'tries to reduce symbol entropy of locals'
--opt-srcequiv,'insist on source (lexer stream) equivalence'
--opt-binequiv,'insist on binary chunk equivalence (only for PUC Lua 5.1)'
--opt-experimental,'apply experimental optimizations'
]]
-- Preset configuration.
local DEFAULT_CONFIG = [[
--opt-comments --opt-whitespace --opt-emptylines
--opt-numbers --opt-locals
--opt-srcequiv --noopt-binequiv
]]
-- Override configurations: MUST explicitly enable/disable everything.
local BASIC_CONFIG = [[
--opt-comments --opt-whitespace --opt-emptylines
--noopt-eols --noopt-strings --noopt-numbers
--noopt-locals --noopt-entropy
--opt-srcequiv --noopt-binequiv
]]
local MAXIMUM_CONFIG = [[
--opt-comments --opt-whitespace --opt-emptylines
--opt-eols --opt-strings --opt-numbers
--opt-locals --opt-entropy
--opt-srcequiv
]] .. (BIN_EQUIV_AVAIL and ' --opt-binequiv' or ' --noopt-binequiv')
local NONE_CONFIG = [[
--noopt-comments --noopt-whitespace --noopt-emptylines
--noopt-eols --noopt-strings --noopt-numbers
--noopt-locals --noopt-entropy
--opt-srcequiv --noopt-binequiv
]]
local DEFAULT_SUFFIX = "_" -- default suffix for file renaming
local PLUGIN_SUFFIX = "luasrcdiet.plugin." -- relative location of plugins
------------- Startup and initialize option list handling -------------
--- Simple error message handler; change to error if traceback wanted.
--
-- @tparam string msg The message to print.
local function die(msg)
print("LuaSrcDiet (error): "..msg); os.exit(1)
end
--die = error--DEBUG
-- Prepare text for list of optimizations, prepare lookup table.
local MSG_OPTIONS = ""
do
local WIDTH = 24
local o = {}
for op, desc in gmatch(OPTION, "%s*([^,]+),'([^']+)'") do
local msg = " "..op
msg = msg..rep(" ", WIDTH - #msg)..desc.."\n"
MSG_OPTIONS = MSG_OPTIONS..msg
o[op] = true
o["--no"..sub(op, 3)] = true
end
OPTION = o -- replace OPTION with lookup table
end
MSG_USAGE = fmt(MSG_USAGE, MSG_OPTIONS, DEFAULT_CONFIG)
--------- Global variable initialization, option set handling ---------
local suffix = DEFAULT_SUFFIX -- file suffix
local option = {} -- program options
local stat_c, stat_l -- statistics tables
--- Sets option lookup table based on a text list of options.
--
-- Note: additional forced settings for --opt-eols is done in optlex.lua.
--
-- @tparam string CONFIG
local function set_options(CONFIG)
for op in gmatch(CONFIG, "(%-%-%S+)") do
if sub(op, 3, 4) == "no" and -- handle negative options
OPTION["--"..sub(op, 5)] then
option[sub(op, 5)] = false
else
option[sub(op, 3)] = true
end
end
end
-------------------------- Support functions --------------------------
-- List of token types, parser-significant types are up to TTYPE_GRAMMAR
-- while the rest are not used by parsers; arranged for stats display.
local TTYPES = {
"TK_KEYWORD", "TK_NAME", "TK_NUMBER", -- grammar
"TK_STRING", "TK_LSTRING", "TK_OP",
"TK_EOS",
"TK_COMMENT", "TK_LCOMMENT", -- non-grammar
"TK_EOL", "TK_SPACE",
}
local TTYPE_GRAMMAR = 7
local EOLTYPES = { -- EOL names for token dump
["\n"] = "LF", ["\r"] = "CR",
["\n\r"] = "LFCR", ["\r\n"] = "CRLF",
}
--- Reads source code from the file.
--
-- @tparam string fname Path of the file to read.
-- @treturn string Content of the file.
local function load_file(fname)
local data, err = fs.read_file(fname, "rb")
if not data then die(err) end
return data
end
--- Saves source code to the file.
--
-- @tparam string fname Path of the destination file.
-- @tparam string dat The data to write into the file.
local function save_file(fname, dat)
local ok, err = fs.write_file(fname, dat, "wb")
if not ok then die(err) end
end
------------------ Functions to deal with statistics ------------------
--- Initializes the statistics table.
local function stat_init()
stat_c, stat_l = {}, {}
for i = 1, #TTYPES do
local ttype = TTYPES[i]
stat_c[ttype], stat_l[ttype] = 0, 0
end
end
--- Adds a token to the statistics table.
--
-- @tparam string tok The token.
-- @param seminfo
local function stat_add(tok, seminfo)
stat_c[tok] = stat_c[tok] + 1
stat_l[tok] = stat_l[tok] + #seminfo
end
--- Computes totals for the statistics table, returns average table.
--
-- @treturn table
local function stat_calc()
local function avg(c, l) -- safe average function
if c == 0 then return 0 end
return l / c
end
local stat_a = {}
local c, l = 0, 0
for i = 1, TTYPE_GRAMMAR do -- total grammar tokens
local ttype = TTYPES[i]
c = c + stat_c[ttype]; l = l + stat_l[ttype]
end
stat_c.TOTAL_TOK, stat_l.TOTAL_TOK = c, l
stat_a.TOTAL_TOK = avg(c, l)
c, l = 0, 0
for i = 1, #TTYPES do -- total all tokens
local ttype = TTYPES[i]
c = c + stat_c[ttype]; l = l + stat_l[ttype]
stat_a[ttype] = avg(stat_c[ttype], stat_l[ttype])
end
stat_c.TOTAL_ALL, stat_l.TOTAL_ALL = c, l
stat_a.TOTAL_ALL = avg(c, l)
return stat_a
end
----------------------------- Main tasks -----------------------------
--- A simple token dumper, minimal translation of seminfo data.
--
-- @tparam string srcfl Path of the source file.
local function dump_tokens(srcfl)
-- Load file and process source input into tokens.
local z = load_file(srcfl)
local toklist, seminfolist = llex.lex(z)
-- Display output.
for i = 1, #toklist do
local tok, seminfo = toklist[i], seminfolist[i]
if tok == "TK_OP" and byte(seminfo) < 32 then
seminfo = "("..byte(seminfo)..")"
elseif tok == "TK_EOL" then
seminfo = EOLTYPES[seminfo]
else
seminfo = "'"..seminfo.."'"
end
print(tok.." "..seminfo)
end--for
end
--- Dumps globalinfo and localinfo tables.
--
-- @tparam string srcfl Path of the source file.
local function dump_parser(srcfl)
-- Load file and process source input into tokens,
local z = load_file(srcfl)
local toklist, seminfolist, toklnlist = llex.lex(z)
-- Do parser optimization here.
local xinfo = lparser.parse(toklist, seminfolist, toklnlist)
local globalinfo, localinfo = xinfo.globalinfo, xinfo.localinfo
-- Display output.
local hl = rep("-", 72)
print("*** Local/Global Variable Tracker Tables ***")
print(hl.."\n GLOBALS\n"..hl)
-- global tables have a list of xref numbers only
for i = 1, #globalinfo do
local obj = globalinfo[i]
local msg = "("..i..") '"..obj.name.."' -> "
local xref = obj.xref
for j = 1, #xref do msg = msg..xref[j].." " end
print(msg)
end
-- Local tables have xref numbers and a few other special
-- numbers that are specially named: decl (declaration xref),
-- act (activation xref), rem (removal xref).
print(hl.."\n LOCALS (decl=declared act=activated rem=removed)\n"..hl)
for i = 1, #localinfo do
local obj = localinfo[i]
local msg = "("..i..") '"..obj.name.."' decl:"..obj.decl..
" act:"..obj.act.." rem:"..obj.rem
if obj.is_special then
msg = msg.." is_special"
end
msg = msg.." -> "
local xref = obj.xref
for j = 1, #xref do msg = msg..xref[j].." " end
print(msg)
end
print(hl.."\n")
end
--- Reads source file(s) and reports some statistics.
--
-- @tparam string srcfl Path of the source file.
local function read_only(srcfl)
-- Load file and process source input into tokens.
local z = load_file(srcfl)
local toklist, seminfolist = llex.lex(z)
print(MSG_TITLE)
print("Statistics for: "..srcfl.."\n")
-- Collect statistics.
stat_init()
for i = 1, #toklist do
local tok, seminfo = toklist[i], seminfolist[i]
stat_add(tok, seminfo)
end--for
local stat_a = stat_calc()
-- Display output.
local function figures(tt)
return stat_c[tt], stat_l[tt], stat_a[tt]
end
local tabf1, tabf2 = "%-16s%8s%8s%10s", "%-16s%8d%8d%10.2f"
local hl = rep("-", 42)
print(fmt(tabf1, "Lexical", "Input", "Input", "Input"))
print(fmt(tabf1, "Elements", "Count", "Bytes", "Average"))
print(hl)
for i = 1, #TTYPES do
local ttype = TTYPES[i]
print(fmt(tabf2, ttype, figures(ttype)))
if ttype == "TK_EOS" then print(hl) end
end
print(hl)
print(fmt(tabf2, "Total Elements", figures("TOTAL_ALL")))
print(hl)
print(fmt(tabf2, "Total Tokens", figures("TOTAL_TOK")))
print(hl.."\n")
end
--- Processes source file(s), writes output and reports some statistics.
--
-- @tparam string srcfl Path of the source file.
-- @tparam string destfl Path of the destination file where to write optimized source.
local function process_file(srcfl, destfl)
-- handle quiet option
local function print(...) --luacheck: ignore 431
if option.QUIET then return end
_G.print(...)
end
if plugin and plugin.init then -- plugin init
option.EXIT = false
plugin.init(option, srcfl, destfl)
if option.EXIT then return end
end
print(MSG_TITLE) -- title message
-- Load file and process source input into tokens.
local z = load_file(srcfl)
if plugin and plugin.post_load then -- plugin post-load
z = plugin.post_load(z) or z
if option.EXIT then return end
end
local toklist, seminfolist, toklnlist = llex.lex(z)
if plugin and plugin.post_lex then -- plugin post-lex
plugin.post_lex(toklist, seminfolist, toklnlist)
if option.EXIT then return end
end
-- Collect 'before' statistics.
stat_init()
for i = 1, #toklist do
local tok, seminfo = toklist[i], seminfolist[i]
stat_add(tok, seminfo)
end--for
local stat1_a = stat_calc()
local stat1_c, stat1_l = stat_c, stat_l
-- Do parser optimization here.
optparser.print = print -- hack
local xinfo = lparser.parse(toklist, seminfolist, toklnlist)
if plugin and plugin.post_parse then -- plugin post-parse
plugin.post_parse(xinfo.globalinfo, xinfo.localinfo)
if option.EXIT then return end
end
optparser.optimize(option, toklist, seminfolist, xinfo)
if plugin and plugin.post_optparse then -- plugin post-optparse
plugin.post_optparse()
if option.EXIT then return end
end
-- Do lexer optimization here, save output file.
local warn = optlex.warn -- use this as a general warning lookup
optlex.print = print -- hack
toklist, seminfolist, toklnlist
= optlex.optimize(option, toklist, seminfolist, toklnlist)
if plugin and plugin.post_optlex then -- plugin post-optlex
plugin.post_optlex(toklist, seminfolist, toklnlist)
if option.EXIT then return end
end
local dat = concat(seminfolist)
-- Depending on options selected, embedded EOLs in long strings and
-- long comments may not have been translated to \n, tack a warning.
if find(dat, "\r\n", 1, 1) or
find(dat, "\n\r", 1, 1) then
warn.MIXEDEOL = true
end
-- Test source and binary chunk equivalence.
equiv.init(option, llex, warn)
equiv.source(z, dat)
if BIN_EQUIV_AVAIL then
equiv.binary(z, dat)
end
local smsg = "before and after lexer streams are NOT equivalent!"
local bmsg = "before and after binary chunks are NOT equivalent!"
-- for reporting, die if option was selected, else just warn
if warn.SRC_EQUIV then
if option["opt-srcequiv"] then die(smsg) end
else
print("*** SRCEQUIV: token streams are sort of equivalent")
if option["opt-locals"] then
print("(but no identifier comparisons since --opt-locals enabled)")
end
print()
end
if warn.BIN_EQUIV then
if option["opt-binequiv"] then die(bmsg) end
elseif BIN_EQUIV_AVAIL then
print("*** BINEQUIV: binary chunks are sort of equivalent")
print()
end
-- Save optimized source stream to output file.
save_file(destfl, dat)
-- Collect 'after' statistics.
stat_init()
for i = 1, #toklist do
local tok, seminfo = toklist[i], seminfolist[i]
stat_add(tok, seminfo)
end--for
local stat_a = stat_calc()
-- Display output.
print("Statistics for: "..srcfl.." -> "..destfl.."\n")
local function figures(tt)
return stat1_c[tt], stat1_l[tt], stat1_a[tt],
stat_c[tt], stat_l[tt], stat_a[tt]
end
local tabf1, tabf2 = "%-16s%8s%8s%10s%8s%8s%10s",
"%-16s%8d%8d%10.2f%8d%8d%10.2f"
local hl = rep("-", 68)
print("*** lexer-based optimizations summary ***\n"..hl)
print(fmt(tabf1, "Lexical",
"Input", "Input", "Input",
"Output", "Output", "Output"))
print(fmt(tabf1, "Elements",
"Count", "Bytes", "Average",
"Count", "Bytes", "Average"))
print(hl)
for i = 1, #TTYPES do
local ttype = TTYPES[i]
print(fmt(tabf2, ttype, figures(ttype)))
if ttype == "TK_EOS" then print(hl) end
end
print(hl)
print(fmt(tabf2, "Total Elements", figures("TOTAL_ALL")))
print(hl)
print(fmt(tabf2, "Total Tokens", figures("TOTAL_TOK")))
print(hl)
-- Report warning flags from optimizing process.
if warn.LSTRING then
print("* WARNING: "..warn.LSTRING)
elseif warn.MIXEDEOL then
print("* WARNING: ".."output still contains some CRLF or LFCR line endings")
elseif warn.SRC_EQUIV then
print("* WARNING: "..smsg)
elseif warn.BIN_EQUIV then
print("* WARNING: "..bmsg)
end
print()
end
---------------------------- Main functions ---------------------------
local arg = {...} -- program arguments
set_options(DEFAULT_CONFIG) -- set to default options at beginning
--- Does per-file handling, ship off to tasks.
--
-- @tparam {string,...} fspec List of source files.
local function do_files(fspec)
for i = 1, #fspec do
local srcfl = fspec[i]
local destfl
-- Find and replace extension for filenames.
local extb, exte = find(srcfl, "%.[^%.%\\%/]*$")
local basename, extension = srcfl, ""
if extb and extb > 1 then
basename = sub(srcfl, 1, extb - 1)
extension = sub(srcfl, extb, exte)
end
destfl = basename..suffix..extension
if #fspec == 1 and option.OUTPUT_FILE then
destfl = option.OUTPUT_FILE
end
if srcfl == destfl then
die("output filename identical to input filename")
end
-- Perform requested operations.
if option.DUMP_LEXER then
dump_tokens(srcfl)
elseif option.DUMP_PARSER then
dump_parser(srcfl)
elseif option.READ_ONLY then
read_only(srcfl)
else
process_file(srcfl, destfl)
end
end--for
end
--- The main function.
local function main()
local fspec = {}
local argn, i = #arg, 1
if argn == 0 then
option.HELP = true
end
-- Handle arguments.
while i <= argn do
local o, p = arg[i], arg[i + 1]
local dash = match(o, "^%-%-?")
if dash == "-" then -- single-dash options
if o == "-h" then
option.HELP = true; break
elseif o == "-v" then
option.VERSION = true; break
elseif o == "-s" then
if not p then die("-s option needs suffix specification") end
suffix = p
i = i + 1
elseif o == "-o" then
if not p then die("-o option needs a file name") end
option.OUTPUT_FILE = p
i = i + 1
elseif o == "-" then
break -- ignore rest of args
else
die("unrecognized option "..o)
end
elseif dash == "--" then -- double-dash options
if o == "--help" then
option.HELP = true; break
elseif o == "--version" then
option.VERSION = true; break
elseif o == "--keep" then
if not p then die("--keep option needs a string to match for") end
option.KEEP = p
i = i + 1
elseif o == "--plugin" then
if not p then die("--plugin option needs a module name") end
if option.PLUGIN then die("only one plugin can be specified") end
option.PLUGIN = p
plugin = require(PLUGIN_SUFFIX..p)
i = i + 1
elseif o == "--quiet" then
option.QUIET = true
elseif o == "--read-only" then
option.READ_ONLY = true
elseif o == "--basic" then
set_options(BASIC_CONFIG)
elseif o == "--maximum" then
set_options(MAXIMUM_CONFIG)
elseif o == "--none" then
set_options(NONE_CONFIG)
elseif o == "--dump-lexer" then
option.DUMP_LEXER = true
elseif o == "--dump-parser" then
option.DUMP_PARSER = true
elseif o == "--details" then
option.DETAILS = true
elseif OPTION[o] then -- lookup optimization options
set_options(o)
else
die("unrecognized option "..o)
end
else
fspec[#fspec + 1] = o -- potential filename
end
i = i + 1
end--while
if option.HELP then
print(MSG_TITLE..MSG_USAGE); return true
elseif option.VERSION then
print(MSG_TITLE); return true
end
if option["opt-binequiv"] and not BIN_EQUIV_AVAIL then
die("--opt-binequiv is available only for PUC Lua 5.1!")
end
if #fspec > 0 then
if #fspec > 1 and option.OUTPUT_FILE then
die("with -o, only one source file can be specified")
end
do_files(fspec)
return true
else
die("nothing to do!")
end
end
-- entry point -> main() -> do_files()
if not main() then
die("Please run with option -h or --help for usage information")
end

View File

@ -0,0 +1,300 @@
= Features and Usage
Kein-Hong Man
2011-09-13
== Features
LuaSrcDiet features include the following:
* Predefined default, _--basic_ (token-only) and _--maximum_ settings.
* Avoid deleting a block comment with a certain message with _--keep_; this is for copyright or license texts.
* Special handling for `#!` (shbang) lines and in functions, `self` implicit parameters.
* Dumping of raw information using _--dump-lexer_ and _--dump-parser_.
See the `samples` directory.
* A HTML plugin: outputs files that highlights globals and locals, useful for eliminating globals. See the `samples` directory.
* An SLOC plugin: counts significant lines of Lua code, like SLOCCount.
* Source and binary equivalence testing with _--opt-srcequiv_ and _--opt-binequiv_.
List of optimizations:
* Line endings are always normalized to LF, except those embedded in comments or strings.
* _--opt-comments_: Removal of comments and comment blocks.
* _--opt-whitespace_: Removal of whitespace, excluding end-of-line characters.
* _--opt-emptylines_: Removal of empty lines.
* _--opt-eols_: Removal of unnecessary end-of-line characters.
* _--opt-strings_: Rewrite strings and long strings. See the `samples` directory.
* _--opt-numbers_: Rewrite numbers. See the `samples` directory.
* _--opt-locals_: Rename local variable names. Does not rename field or method names.
* _--opt-entropy_: Tries to improve symbol entropy when renaming locals by calculating actual letter frequencies.
* _--opt-experimental_: Apply experimental optimizations.
LuaSrcDiet tries to allow each option to be enabled or disabled separately, but they are not completely orthogonal.
If comment removal is disabled, LuaSrcDiet only removes trailing whitespace.
Trailing whitespace is not removed in long strings, a warning is generated instead.
If empty line removal is disabled, LuaSrcDiet keeps all significant code on the same lines.
Thus, a user is able to debug using the original sources as a reference since the line numbering is unchanged.
String optimization deals mainly with optimizing escape sequences, but delimiters can be switched between single quotes and double quotes if the source size of the string can be reduced.
For long strings and long comments, LuaSrcDiet also tries to reduce the `=` separators in the
delimiters if possible.
For number optimization, LuaSrcDiet saves space by trying to generate the shortest possible sequence, and in the process it does not produce “proper” scientific notation (e.g. 1.23e5) but does away with the decimal point (e.g. 123e3) instead.
The local variable name optimizer uses a full parser of Lua 5.1 source code, thus it can rename all local variables, including upvalues and function parameters.
It should handle the implicit `self` parameter gracefully.
In addition, local variable names are either renamed into the shortest possible names following English frequent letter usage or are arranged by calculating entropy with the _--opt-entropy_ option.
Variable names are reused whenever possible, reducing the number of unique variable names.
For example, for `LuaSrcDiet.lua` (version 0.11.0), 683 local identifiers representing 88 unique names were optimized into 32 unique names, all which are one character in length, saving over 2600 bytes.
If you need some kind of reassurance that your app will still work at reduced size, see the section on verification below.
== Usage
LuaSrcDiet needs a Lua 5.1.x (preferably Lua 5.1.4) binary to run.
On Unix machines, one can use the following command line:
[source, sh]
LuaSrcDiet myscript.lua -o myscript_.lua
On Windows machines, the above command line can be used on Cygwin, or you can run Lua with the LuaSrcDiet script like this:
[source, sh]
lua LuaSrcDiet.lua myscript.lua -o myscript_.lua
When run without arguments, LuaSrcDiet prints a list of options.
Also, you can check the `Makefile` for some examples of command lines to use.
For example, for maximum code size reduction and maximum verbosity, use:
[source, sh]
LuaSrcDiet --maximum --details myscript.lua -o myscript_.lua
=== Output Example
A sample output of LuaSrcDiet 0.11.0 for processing `llex.lua` at _--maximum_ settings is as follows:
----
Statistics for: LuaSrcDiet.lua -> sample/LuaSrcDiet.lua
*** local variable optimization summary ***
----------------------------------------------------------
Variable Unique Decl. Token Size Average
Types Names Count Count Bytes Bytes
----------------------------------------------------------
Global 10 0 19 95 5.00
----------------------------------------------------------
Local (in) 88 153 683 3340 4.89
TOTAL (in) 98 153 702 3435 4.89
----------------------------------------------------------
Local (out) 32 153 683 683 1.00
TOTAL (out) 42 153 702 778 1.11
----------------------------------------------------------
*** lexer-based optimizations summary ***
--------------------------------------------------------------------
Lexical Input Input Input Output Output Output
Elements Count Bytes Average Count Bytes Average
--------------------------------------------------------------------
TK_KEYWORD 374 1531 4.09 374 1531 4.09
TK_NAME 795 3963 4.98 795 1306 1.64
TK_NUMBER 54 59 1.09 54 59 1.09
TK_STRING 152 1725 11.35 152 1717 11.30
TK_LSTRING 7 1976 282.29 7 1976 282.29
TK_OP 997 1092 1.10 997 1092 1.10
TK_EOS 1 0 0.00 1 0 0.00
--------------------------------------------------------------------
TK_COMMENT 140 6884 49.17 1 18 18.00
TK_LCOMMENT 7 1723 246.14 0 0 0.00
TK_EOL 543 543 1.00 197 197 1.00
TK_SPACE 1270 2465 1.94 263 263 1.00
--------------------------------------------------------------------
Total Elements 4340 21961 5.06 2841 8159 2.87
--------------------------------------------------------------------
Total Tokens 2380 10346 4.35 2380 7681 3.23
--------------------------------------------------------------------
----
Overall, the file size is reduced by more than 9 kiB.
Tokens in the above report can be classified into “real” or actual tokens, and “fake” or whitespace tokens.
The number of “real” tokens remained the same.
Short comments and long comments were completely eliminated.
The number of line endings was reduced by 59, while all but 152 whitespace characters were optimized away.
So, token separators (whitespace, including line endings) now takes up just 10 % of the total file size.
No optimization of number tokens was possible, while 2 bytes were saved for string tokens.
For local variable name optimization, the report shows that 38 unique local variable names were reduced to 20 unique names.
The number of identifier tokens should stay the same (there is currently no optimization option to optimize away non-essential or unused “real” tokens).
Since there can be at most 53 single-character identifiers, all local variables are now one character in length.
Over 600 bytes was saved.
_--details_ will give a longer report and much more information.
A sample output of LuaSrcDiet 0.12.0 for processing the one-file `LuaSrcDiet.lua` program itself at _--maximum_ and _--opt-experimental_ settings is as follows:
----
*** local variable optimization summary ***
----------------------------------------------------------
Variable Unique Decl. Token Size Average
Types Names Count Count Bytes Bytes
----------------------------------------------------------
Global 27 0 51 280 5.49
----------------------------------------------------------
Local (in) 482 1063 4889 21466 4.39
TOTAL (in) 509 1063 4940 21746 4.40
----------------------------------------------------------
Local (out) 55 1063 4889 4897 1.00
TOTAL (out) 82 1063 4940 5177 1.05
----------------------------------------------------------
*** BINEQUIV: binary chunks are sort of equivalent
Statistics for: LuaSrcDiet.lua -> app_experimental.lua
*** lexer-based optimizations summary ***
--------------------------------------------------------------------
Lexical Input Input Input Output Output Output
Elements Count Bytes Average Count Bytes Average
--------------------------------------------------------------------
TK_KEYWORD 3083 12247 3.97 3083 12247 3.97
TK_NAME 5401 24121 4.47 5401 7552 1.40
TK_NUMBER 467 494 1.06 467 494 1.06
TK_STRING 787 7983 10.14 787 7974 10.13
TK_LSTRING 14 3453 246.64 14 3453 246.64
TK_OP 6381 6861 1.08 6171 6651 1.08
TK_EOS 1 0 0.00 1 0 0.00
--------------------------------------------------------------------
TK_COMMENT 1611 72339 44.90 1 18 18.00
TK_LCOMMENT 18 4404 244.67 0 0 0.00
TK_EOL 4419 4419 1.00 1778 1778 1.00
TK_SPACE 10439 24475 2.34 2081 2081 1.00
--------------------------------------------------------------------
Total Elements 32621 160796 4.93 19784 42248 2.14
--------------------------------------------------------------------
Total Tokens 16134 55159 3.42 15924 38371 2.41
--------------------------------------------------------------------
* WARNING: before and after lexer streams are NOT equivalent!
----
The command line was:
[source, sh]
lua LuaSrcDiet.lua LuaSrcDiet.lua -o app_experimental.lua --maximum --opt-experimental --noopt-srcequiv
The important thing to note is that while the binary chunks are equivalent, the source lexer streams are not equivalent.
Hence, the _--noopt-srcequiv_ makes LuaSrcDiet report a warning for failing the source equivalence test.
`LuaSrcDiet.lua` was reduced from 157 kiB to about 41.3 kiB.
The _--opt-experimental_ option saves an extra 205 bytes over standard _--maximum_.
Note the reduction in `TK_OP` count due to a reduction in semicolons and parentheses.
`TK_SPACE` has actually increased a bit due to semicolons that are changed into single spaces; some of these spaces could not be removed.
For more performance numbers, see the <<performance-stats#, Performance Statistics>> page.
== Verification
Code size reduction can be quite a hairy thing (even I peer at the results in suspicion), so some kind of verification is desirable for users who expect processed files to _not_ blow up.
Since LuaSrcDiet has been talked about as a tool to reduce code size in projects such as WoW add-ons, `eLua` and `nspire`, adding a verification step will reduce risk for all users of LuaSrcDiet.
LuaSrcDiet performs two kinds of equivalence testing as of version 0.12.0.
The two tests can be very, very loosely termed as _source equivalence testing_ and _binary equivalence testing_.
They are controlled by the _--opt-srcequiv_ and _--opt-binequiv_ options and are enabled by default.
Testing behaviour can be summarized as follows:
* Both tests are always executed.
The options control the resulting actions taken.
* Both options are normally enabled.
This will make any failing test to throw an error.
* When an option is disabled, LuaSrcDiet will at most print a warning.
* For passing results, see the following subsections that describe what the tests actually does.
You only need to disable a testing option for experimental optimizations (see the following section for more information on this).
For anything up to and including _--maximum_, both tests should pass.
If any test fail under these conditions, then something has gone wrong with LuaSrcDiet, and I would be interested to know what has blown up.
=== _--opt-srcequiv_ Source Equivalence
The source equivalence test uses LuaSrcDiets lexer to read and compare the _before_ and _after_ lexer token streams.
Numbers and strings are dumped as binary chunks using `loadstring()` and `string.dump()` and the results compared.
If your file passes this test, it means that a Lua 5.1.x binary should see the exact same token streams for both _before_ and _after_ files.
That is, the parser in Lua will see the same lexer sequence coming from the source for both files and thus they _should_ be equivalent.
Touch wood.
Heh.
However, if you are _cross-compiling_, it may be possible for this test to fail.
Experienced Lua developers can modify `equiv.lua` to handle such cases.
=== _--opt-binequiv_ Binary Equivalence
The binary equivalence test uses `loadstring()` and `string.dump()` to generate binary chunks of the entire _before_ and _after_ files.
Also, any shbang (`#!`) lines are removed prior to generation of the binary chunks.
The binary chunks are then run through a fake `undump` routine to verify the integrity of the binary chunks and to compare all parts that ought to be identical.
On a per-function prototype basis (where _ignored_ means that any difference between the two binary chunks is ignored):
* All debug information is ignored.
* The source name is ignored.
* Any line number data is ignored.
For example, `linedefined` and `lastlinedefined`.
The rest of the two binary chunks must be identical.
So, while the two are not binary-exact, they can be loosely termed as “equivalent” and should run in exactly the same manner.
Sort of.
You get the idea.
This test may also cause problems if you are _cross-compiling_.
== Experimental Stuff
The _--opt-experimental_ option applies experimental optimizations that generally, makes changes to “real” tokens.
Such changes may or may not lead to the result failing binary chunk equivalence testing.
They would likely fail source lexer stream equivalence testing, so the _--noopt-srcequiv_ option needs to be applied so that LuaSrcDiet just gives a warning instead of an error.
For sample files, see the `samples` directory.
Currently implemented experimental optimizations are as follows:
=== Semicolon Operator Removal
The semicolon (`;`) operator is an optional operator that is used to separate statements.
The optimization turns all of these operators into single spaces, which are then run through whitespace removal.
At worst, there will be no change to file size.
* _Fails_ source lexer stream equivalence.
* _Passes_ binary chunk equivalence.
=== Function Call Syntax Sugar Optimization
This optimization turns function calls that takes a single string or long string parameter into its syntax-sugar representation, which leaves out the parentheses.
Since strings can abut anything, each instance saves 2 bytes.
For example, the following:
[source, lua]
fish("cow")fish('cow')fish([[cow]])
is turned into:
[source, lua]
fish"cow"fish'cow'fish[[cow]]
* _Fails_ source lexer stream equivalence.
* _Passes_ binary chunk equivalence.
=== Other Experimental Optimizations
There are two more of these optimizations planned, before focus is turned to the Lua 5.2.x series:
* Simple `local` keyword removal.
Planned to work for a few kinds of patterns only.
* User directed name replacement, which will need user input to modify names or identifiers used in table keys and function methods or fields.

View File

@ -0,0 +1,128 @@
= Performance Statistics
Kein-Hong Man
2011-09-13
== Size Comparisons
The following is the result of processing `llex.lua` from LuaSrcDiet 0.11.0 using various optimization options:
|===
| LuaSrcDiet Option | Size (bytes)
| Original | 12,421
| Empty lines only | 12,395
| Whitespace only | 9,372
| Local rename only | 11,794
| _--basic_ setting | 3,835
| Program default | 3,208
| _--maximum_ setting | 3,130
|===
The programs default settings does not remove all unnecessary EOLs.
The _--basic_ setting is more conservative than the default settings, it disables optimization of strings and numbers and renaming of locals.
For version 0.12.0, the following is the result of processing `LuaSrcDiet.lua` using various optimization options:
|===
| LuaSrcDiet Option | Size (bytes)
| Original | 160,796
| _--basic_ setting | 60,219
| Program default | 43,650
| _--maximum_ setting | 42,453
| max + experimental | 42,248
|===
The above best size can go a lot lower with simple `local` keyword removal and user directed name replacement, which will be the subject of the next release of LuaSrcDiet.
== Compression and luac
File sizes of LuaSrcDiet 0.11.0 main files in various forms:
[cols="m,5*d", options="header,footer"]
|===
| Source File | Original Size (bytes) | `luac` normal (bytes) | `luac` stripped (bytes) | LuaSrcDiet _--basic_ (bytes) | LuaSrcDiet _--maximum_ (bytes)
| LuaSrcDiet.lua | 21,961 | 20,952 | 11,000 | 11,005 | 8,159
| llex.lua | 12,421 | 8,613 | 4,247 | 3,835 | 3,130
| lparser.lua | 41,757 | 27,215 | 12,506 | 11,755 | 7,666
| optlex.lua | 31,009 | 16,992 | 8,021 | 9,129 | 6,858
| optparser.lua | 16,511 | 9,021 | 3,520 | 5,087 | 2,999
| Total | 123,659 | 82,793 | 39,294 | 40,811 | 28,812
|===
* “LuaSrcDiet --maximum” has the smallest total file size.
* The ratio of “Original Size” to “LuaSrcDiet --maximum” is *4.3*.
* The ratio of “Original Size” to “luac stripped” is *3.1*.
* The ratio of “luac stripped” to “LuaSrcDiet --maximum” is *1.4*.
Compressibility of LuaSrcDiet 0.11.0 main files in various forms:
|===
| Compression Method | Original Size | `luac` normal | `luac` stripped | LuaSrcDiet _--basic_ | LuaSrcDiet _--maximum_
| Uncompressed originals | 123,659 | 82,793 | 39,294 | 40,811 | 28,812
| gzip -9 | 28,288 | 29,210 | 17,732 | 12,041 | 10,451
| bzip2 -9 | 24,407 | 27,232 | 16,856 | 11,480 | 9,815
| lzma (7-zip max) | 25,530 | 23,908 | 15,741 | 11,241 | 9,685
|===
* “LuaSrcDiet --maximum” has the smallest total file size (but a binary chunk loads faster and works with a smaller Lua executable).
* The ratio of “Original size” to “Original size + bzip2” is *5.1*.
* The ratio of “Original size” to “LuaSrcDiet --maximum + bzip2” is *12.6*.
* The ratio of “LuaSrcDiet --maximum” to “LuaSrcDiet --maximum + bzip2” is *2.9*.
* The ratio of “Original size” to “luac stripped + bzip2” is *7.3*.
* The ratio of “luac stripped” to “luac stripped + bzip2” is *2.3*.
* The ratio of “luac stripped + bzip2” to “LuaSrcDiet --maximum + bzip2” is *1.7*.
So, squeezed source code are smaller than stripped binary chunks and compresses better than stripped binary chunks, at a ratio of 2.9 for squeezed source code versus 2.3 for stripped binary chunks.
Compressed binary chunks is still a very efficient way of storing Lua scripts, because using only binary chunks allow for the parts of Lua needed to compile from sources to be omitted (`llex.o`, `lparser.o`, `lcode.o`, `ldump.o`), saving over 24KB in the process.
Note that LuaSrcDiet _does not_ answer the question of whether embedding source code is better or embedding binary chunks is better.
It is simply a utility for producing smaller source code files and an exercise in processing Lua source code using a Lua-based lexer and parser skeleton.
== Compile Speed
The following is a primitive attempt to analyze in-memory Lua script loading performance (using the `loadstring` function in Lua).
The LuaSrcDiet 0.11.0 files (original, squeezed with _--maximum_ and stripped binary chunks versions) are loaded into memory first before a loop runs to repeatedly load the script files for 10 seconds.
A null loop is also performed (processing empty strings) and the time taken per null iteration is subtracted as a form of null adjustment.
Then, various performance parameters are calculated.
Note that `LuaSrcDiet.lua` was slightly modified (`#!` line removed) to let the `loadstring` function run.
The results below were obtained with a Lua 5.1.3 executable compiled using `make generic` on Cygwin/Windows XP SP2 on a Sempron 3000+ (1.8GHz).
The LuaSrcDiet 0.11.0 source files have 11,180 “real” tokens in total.
[cols="<h,4*d", options="header"]
|===
| | Null loop | Stripped binary chunk | Original Sources | Squeezed Sources
| Total Size (bytes) | 0 | 39,294 | 123,640 | 28,793
| Iterations | 312,155 | 9,680 | 1306 | 1,592
| Duration (sec) | 10 | 10 | 10 | 10
| Time/iteration (msec) | 0.032 | 1.033 | 7.657 | 6.281
| _Time/iteration, null adjusted (msec)_ | | 1.001 | 7.625 | 6.249
| _Load rate (MiB/sec)_ | | 37.44 | 15.46 | 4.39
| Load time per byte (ns) | | 25.5 | 61.7 | 217.0
| Load time per token (ns) | | | 682 | 559
| Source time vs binary chunk time ratio | | 1.00 | 7.62 | 6.24
| Binary chunk rate vs. source rate ratio | | 1.00 | 2.42 | 8.53
|===
The above shows that stripped binary chunks is still, in many ways, the highest-performance form of fixed Lua scripts.
On a very average machine, scripts load at over 37 MiB/sec (in memory).
This is very comparable to the burst speeds of common desktop hard disks of 2008.
If instant response is paramount, stripped binary chunks has little competition.
By contrast, source code that is squeezed to the maximum using LuaSrcDiet can only muster an in-memory load rate of 4.4 MiB/sec.
The original sources load at about 15.5 MiB/sec, but most of the speed is from the lexer scanning over comments and whitespace.
A quick calculation indicates that the speed of the lexer over comments and whitespace can be as much as 65 MiB/sec, but note that the speed is all for naught.
What really matters are the real tokens, and the squeezed source code manages to load faster than the original sources by 18 %.
So, the loading of stripped binary chunks is faster than squeezed source code by a bit over 6×.
The 4.4 MiB/sec speed for squeezed source code is still quite respectable.
When an application considers the time taken to load data from the disk and perhaps the time taken to decompress, loading source code may be perfectly fine in terms of performance.
For programs that already embed source code, using LuaSrcDiet to squeeze the source code probably speeds loading up by a tiny bit in addition to making programs smaller.

View File

@ -0,0 +1,386 @@
= Technical Notes
Kein-Hong Man
2011-09-13
== Lexer Notes
The lexer (`llex.lua`) is a version of the native 5.1.x lexer from Yueliang 0.4.0, with significant modifications.
It does have several limitations:
* The decimal point must be `.` (period).
There is no localized decimal point replacement magic.
* There is no support for nested `[[`...`]]` long strings (no `LUA_COMPAT_LSTR`).
* The lexer may not properly lex source code with characters beyond the normal ASCII character set.
Identifiers with accented characters (or any character beyond a byte value of 127) cannot be recognized.
Instead of returning one token on each call, `llex.lua` processes an entire string (all data from an entire file) and returns.
Two lists (tokens and semantic information items) are set up in the module for use by the caller.
For maximum flexibility during processing, the lexer returns non-grammar lexical elements as tokens too.
Non-grammar elements, such as comments, whitespace, line endings, are classified along with “normal” tokens.
The lexer classifies 7 kinds of grammar tokens and 4 kinds of non-grammar tokens, as follows:
[cols="m,d"]
|===
| Grammar Token | Description
| TK_KEYWORD | keywords
| TK_NAME | identifiers
| TK_NUMBER | numbers (unconverted, kept in original form)
| TK_STRING | strings (no translation is done, includes delimiters)
| TK_LSTRING | long strings (no translation is done, includes delimiters)
| TK_OP | operators and punctuation (most single-char, some double)
| TK_EOS | end-of-stream (there is only one for each file/stream)
|===
[cols="m,d"]
|===
| Whitespace Token | Description
| TK_SPACE | whitespace (generally, spaces, \t, \v and \f)
| TK_COMMENT | comments (includes delimiters, also includes special first line shbang, which is handled specially in the optimizer)
| TK_LCOMMENT | block comments (includes delimiters)
| TK_EOL | end-of-lines (excludes those embedded in strings)
|===
A list of tokens can be generated by using the _--dump-lexer_ option, like this:
[source, sh]
lua LuaSrcDiet.lua --dump-lexer llex.lua > dump_llex.dat
== Lexer Optimizations
We aim to keep lexer-based optimizations free of parser considerations, i.e. we allow for generalized optimization of token sequences.
The table below considers the requirements for all combinations of significant tokens (except `TK_EOS`).
Other tokens are whitespace-like.
Comments can be considered to be a special kind of whitespace, e.g. a short comment needs to have a following EOL token, if we do not want to optimize away short comments.
[cols="h,6*m", options="header"]
|===
| _1st  2nd Token_ | Keyword | Name | Number | String | LString | Oper
| Keyword | [S] | [S] | [S] | | |
| Name | [S] | [S] | [S] | | |
| Number | [S] | [S] | [S] | | | [1]
| String | | | | | |
| LString | | | | | |
| Oper | | | [1] | | | [2]
|===
A dash (`-`) in the above means that the first token can abut the second token.
`*[S]*`:: Need at least one whitespace, set as either a space or kept as an EOL.
`*[1]*`::
Need a space if operator is a `.`, all others okay.
A `+` or `-` is used as part of a floating-point spec, but there does not appear to be any way of creating a float by joining with number with a `+` or `-` plus another number.
Since an `e` has to be somewhere in the first token, this cant be done.
`*[2]*`::
Normally there cannot be consecutive operators, but we plan to allow for generalized optimization of token sequences, i.e. even sequences that are grammatically illegal; so disallow adjacent operators if:
* the first is in `[=<>]` and the second is `=`
* disallow dot sequences to be adjacent, but `...` first okay
* disallow `[` followed by `=` or `[` (not optimal)
Also, a minus `-` cannot preceed a Comment or LComment, because comments start with a `--` prefix.
Apart from that, all Comment or LComment tokens can be set abut with a real token.
== Local Variable Renaming
The following discusses the problem of local variable optimization, specifically _local variable renaming_ in order to reduce source code size.
=== TK_NAME Token Considerations
A `TK_NAME` token means a number of things, and some of these cannot be renamed without analyzing the source code.
We are interested in the use of `TK_NAME` in the following:
[loweralpha]
. global variable access,
. local variable declaration, including `local` statements, `local` functions, function parameters, implicit `self` locals,
. local variable access, including upvalue access.
`TK_NAME` is also used in parts of the grammar as constant strings these tokens cannot be optimized without user assistance.
These include usage as:
[loweralpha, start=4]
. keys in `key=value` pairs in table construction,
. field or method names in `a:b` or `a.b` syntax forms.
For the local variable name optimization scheme used, we do not consider (d) and (e), and while global variables cannot be renamed without some kind of user assistance, they need to be considered or tracked as part of Luas variable access scheme.
=== Lifetime of a Local Variable
Consider the following example:
[source, lua]
local string, table = string, table
In the example, the two locals are assigned the values of the globals with the same names.
When Lua encounters the declaration portion:
[source, lua]
local string, table
the parser cannot immediately make the two local variable available to following code.
In the parser and code generator, locals are inactive when entries are created.
They are activated only when the function `adjustlocalvars()` is called to activate the appropriate local variables.
NOTE: The terminology used here may not be identical to the ones used in the Dragon Book they merely follow the LuaSrcDiet code as it was written before I have read the Dragon Book.
In the example, the two local variables are activated only after the whole statement has been parsed, that is, after the last `table` token.
Hence, the statement works as expected.
Also, once the two local variables goes out of scope, `removevars()` is called to deactivate them, allowing other variables of the same name to become visible again.
Another example worth mentioning is:
[source, lua]
local a, a, a, = 1, 2, 3
The above will assign 3 to `a`.
Thus, when optimizing local variable names, (1) we need to consider accesses of global variable names affecting the namespace, (2) for the local variable names themselves, we need to consider when they are declared, activated and removed, and (3) within the “live” time of locals, we need to know when they are accessed (since locals that are never accessed dont really matter.)
=== Local Variable Tracking
Every local variable declaration is considered an object to be renamed.
From the parser, we have the original name of the local variable, the token positions for declaration, activation and removal, and the token position for all the `TK_NAME` tokens which references this local.
All instances of the implicit `self` local variable are also flagged as such.
In addition to local variable information, all global variable accesses are tabled, one object entry for one name, and each object has a corresponding list of token positions for the `TK_NAME` tokens, which is where the global variables were accessed.
The key criteria is: *Our act of renaming cannot change the visibility of any of these locals and globals at the time they are accessed*.
However, _their scope of visibility may be changed during which they are not accessed_, so someone who tries to insert a variable reference somewhere into a program that has its locals renamed may find that it now refers to a different variable.
Of course, if every variable has a unique name, then there is no need for a name allocation algorithm, as there will be no conflict.
But, in order to maximize utilization of short identifier names to reduce the final code size, we want to reuse the names as much as possible.
In addition, fewer names will likely reduce symbol entropy and may slightly improve compressibility of the source code.
LuaSrcDiet avoids the use of non-ASCII letters, so there are only 53 single-character variable names.
=== Name Allocation Theory
To understand the renaming algorithm, first we need to establish how different local and global variables can operate happily without interfering with each other.
Consider three objects, local object A, local object B and global object G.
A and B involve declaration, activation and removal, and within the period it is active, there may be zero or more accesses of the local.
For G, there are only global variable accesses to look into.
Assume that we have assigned a new name to A and we wish to consider its effects on other locals and globals, for which we choose B and G as examples.
We assume local B has not been assigned a new name as we expect our algorithm to take care of collisions.
As lifetime is something like this:
----
Decl Act Rem
+ +-------------------------------+
-------------------------------------------------
----
where “Decl” is the time of declaration, “Act” is the time of activation, and “Rem” is the time of removal.
Between “Act” and “Rem”, the local is alive or “live” and Lua can see it if its corresponding `TK_NAME` identifier comes up.
----
Decl Act Rem
+ +-------------------------------+
-------------------------------------------------
* * * *
(1) (2) (3) (4)
----
Recall that the key criteria is to not change the visibility of globals and locals during when they are accessed.
Consider local and global accesses at (1), (2), (3) and (4).
A global G of the same name as A will only collide at (3), where Lua will see A and not G.
Since G must be accessed at (3) according to what the parser says, and we cannot modify the positions of “Decl”, “Act” and “Rem”, it follows that A cannot have the same name as G.
----
Decl Act Rem
+ +-----------------------+
---------------------------------
(1)+ +---+ (2)+ +---+ (3)+ +---+ (4)+ +---+
--------- --------- --------- ---------
----
For the case of A and B having the same names and colliding, consider the cases for which B is at (1), (2), (3) or (4) in the above.
(1) and (4) means that A and B are completely isolated from each other, hence in the two cases, A and B can safely use the same variable names.
To be specific, since we have assigned A, B is considered completely isolated from A if Bs activation-to-removal period is isolated from the time of As first access to last access, meaning Bs active time will never affect any of As accesses.
For (2) and (3), we have two cases where we need to consider which one has been activated first.
For (2), B is active before A, so A cannot impose on B.
But As accesses are valid while B is active, since A can override B.
For no collision in the case of (2), we simply need to ensure that the last access of B occurs before A is activated.
For (3), B is activated before A, hence B can override As accesses.
For no collision, all of As accesses cannot happen while B is active.
Thus position (3) follows the “A is never accessed when B is active” rule in a general way.
Local variables of a child function are in the position of (3).
To illustrate, the local B can use the same name as local A and live in a child function or block scope if each time A is accessed, Lua sees A and not B.
So we have to check all accesses of A and see whether they collide with the active period of B.
If A is not accessed during that period, then B can be active with the same name.
The above appears to resolve all sorts of cases where the active times of A and B overlap.
Note that in the above, the allocator does not need to know how locals are separated according to function prototypes.
Perhaps the allocator can be simplified if knowledge of function structure is utilized.
This scheme was implemented in a hurry in 2008 — it could probably be simpler if Lua grammar is considered, but LuaSrcDiet mainly processes various index values in tables.
=== Name Allocation Algorithm
To begin with, the name generator is mostly separate from the name allocation algorithm.
The name generator returns the next shortest name for the algorithm to apply to local variables.
To attempt to reduce symbol entropy (which benefit compression algorithms), the name generator follows English frequent letter usage.
There is also an option to calculate an actual symbol entropy table from the input data.
Since there are 53 one-character identifiers and (53 * 63 - 4) two-character identifiers (minus a few keywords), there isnt a pressing need to optimally maximize name reuse.
The single-file version of LuaSrcDiet 0.12.0, at just over 3000 SLOC and 156 kiB in size, currently allocates around 55 unique local variable names.
In theory, we should need no more than 260 local identifiers by default.
Why?
Since `LUAI_MAXVARS` is 200 and `LUAI_MAXUPVALUES` is 60, at any block scope, there can be at most `(LUAI_MAXVARS + LUAI_MAXUPVALUES)` locals referenced, or 260.
Also, those from outer scopes not referenced in inner scopes can reuse identifiers.
The net effect of this is that a local variable name allocation method should not allocate more than 260 identifier names for locals.
The current algorithm is a simple first-come first-served scheme:
[loweralpha]
. One local object that use the most tokens is named first.
. Any other non-conflicting locals with respect to the first object are assigned the same name.
. Assigned locals are removed from consideration and the procedure is repeated for objects that have not been assigned new names.
. Steps (a) to (c) repeats until no local objects are left.
In addition, there are a few extra issues to take care of:
[loweralpha, start=5]
. Implicit `self` locals that have been flagged as such are already “assigned to” and so they are left unmodified.
. The name generator skips `self` to avoid conflicts.
This is not optimal but it is unlikely a script will use so many local variables as to reach `self`.
. Keywords are also skipped for the name generator.
. Global name conflict resolution.
For (h), global name conflict resolution is handled just after the new name is generated.
The name can still be used for some locals even if it conflicts with other locals.
To remove conflicts, global variable accesses for the particular identifier name is checked.
Any local variables that are active when a global access is made is marked to be skipped.
The rest of the local objects can then use that name.
The algorithm has additional code for handling locals that use the same name in the same scope.
This extends the basic algorithm that was discussed earlier.
For example:
[source, lua]
----
local foo = 10 -- <1>
...
local foo = 20 -- <2>
...
print(e)
----
Since we are considering name visibility, the first `foo` does not really cease to exist when the second `foo` is declared, because if we were to make that assumption, and the first `foo` is removed before (2), then I should be able to use `e` as the name for the first `foo` and after (2), it should not conflict with variables in the outer scope with the same name.
To illustrate:
[source, lua]
----
local e = 10 -- 'foo' renamed to 'e'
...
local t = 20 -- error if we assumed 'e' removed here
...
print(e)
----
Since `e` is a global in the example, we now have an error as the name as been taken over by a local.
Thus, the first `foo` local must have its active time extend to the end of the current scope.
If there is no conflict between the first and second `foo`, the algorithm may still assign the same names to them.
The current fix to deal with the above chains local objects in order to find the removal position.
It may be possible to handle this in a clean manner LuaSrcDiet handles it as a fix to the basic algorithm.
== Ideas
The following is a list of optimization ideas that do not require heavy-duty source code parsing and comprehension.
=== Lexer-Based Optimization Ideas
* Convert long strings to normal strings, vice versa. +
_A little desperate for a few bytes, can be done, but not real keen on implementing it._
* Special number forms to take advantage of constant number folding. +
_For example, 65536 can be represented using 2^16^, and so on.
An expression must be evaluated in the same way, otherwise this seems unsafe._
* Warn if a number has too many digits. +
_Should we warn or “test and truncate”?
Not really an optimization that will see much use._
* Warn of opportunity for using a `local` to zap a bunch of globals. +
_Current recommendation is to use the HTML plugin to display globals in red.
The developer can then visually analyze the source code and make the appropriate fixes.
I think this is better than having the program guess the intentions of the developer._
* Spaces to tabs in comments, long comments, or long strings. +
_For long strings, need to know users intention.
Would rather not implement._
=== Parser-Based Optimization Ideas
Heavy-duty optimizations will need more data to be generated by the parser.
A full AST may eventually be needed.
The most attractive idea that can be quickly implemented with a significant code size “win” is to reduce the number of `local` keywords.
* Remove unused ``local``s that can be removed in the source. +
_Need to consider unused ``local``s in multiple assignments._
* Simplify declaration of ``local``s that can be merged. +
_From:_
+
[source, lua]
----
-- separate locals
local foo
local bar
-- separate locals with assignments
local foo = 123
local bar = "pqr"
----
+
_To:_
+
[source, lua]
----
-- merged locals
local foo,bar
-- merged locals with assignments
local foo,bar=123,"pqr"
----
* Simplify declarations using `nil`. +
_From:_
[source, lua]
local foo, bar = nil, nil
+
_To:_
[source, lua]
local foo,bar
* Simplify ``return``s using `nil`. +
_How desirable is this? From Lua list discussions, it seems to be potentially unsafe unless all return locations are known and checked._
* Removal of optional semicolons in statements and removal of commas or semicolons in table constructors. +
_Yeah, this might save a few bytes._
* Remove table constructor elements using `nil`. +
_Not sure if this is safe to do._
* Simplify logical or relational operator expressions. +
_This is more suitable for an optimizing compiler project._

Some files were not shown because too many files have changed in this diff Show More